diff --git a/MPChartLib/build.gradle b/MPChartLib/build.gradle index a77c339e8c..ec437f6692 100644 --- a/MPChartLib/build.gradle +++ b/MPChartLib/build.gradle @@ -28,8 +28,11 @@ android { consumerProguardFiles 'proguard-lib.pro' } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 + } + kotlinOptions { + jvmTarget = "21" } buildTypes { release { diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/animation/ChartAnimator.java b/MPChartLib/src/main/java/com/github/mikephil/charting/animation/ChartAnimator.java deleted file mode 100644 index e5b82db0b6..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/animation/ChartAnimator.java +++ /dev/null @@ -1,207 +0,0 @@ -package com.github.mikephil.charting.animation; - -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import androidx.annotation.RequiresApi; - -import com.github.mikephil.charting.animation.Easing.EasingFunction; - -/** - * Object responsible for all animations in the Chart. Animations require API level 11. - * - * @author Philipp Jahoda - * @author Mick Ashton - */ -public class ChartAnimator { - - /** object that is updated upon animation update */ - private AnimatorUpdateListener mListener; - - /** The phase of drawn values on the y-axis. 0 - 1 */ - @SuppressWarnings("WeakerAccess") - protected float mPhaseY = 1f; - - /** The phase of drawn values on the x-axis. 0 - 1 */ - @SuppressWarnings("WeakerAccess") - protected float mPhaseX = 1f; - - public ChartAnimator() { } - - @RequiresApi(11) - public ChartAnimator(AnimatorUpdateListener listener) { - mListener = listener; - } - - @RequiresApi(11) - private ObjectAnimator xAnimator(int duration, EasingFunction easing) { - - ObjectAnimator animatorX = ObjectAnimator.ofFloat(this, "phaseX", 0f, 1f); - animatorX.setInterpolator(easing); - animatorX.setDuration(duration); - - return animatorX; - } - - @RequiresApi(11) - private ObjectAnimator yAnimator(int duration, EasingFunction easing) { - - ObjectAnimator animatorY = ObjectAnimator.ofFloat(this, "phaseY", 0f, 1f); - animatorY.setInterpolator(easing); - animatorY.setDuration(duration); - - return animatorY; - } - - /** - * Animates values along the X axis, in a linear fashion. - * - * @param durationMillis animation duration - */ - @RequiresApi(11) - public void animateX(int durationMillis) { - animateX(durationMillis, Easing.Linear); - } - - /** - * Animates values along the X axis. - * - * @param durationMillis animation duration - * @param easing EasingFunction - */ - @RequiresApi(11) - public void animateX(int durationMillis, EasingFunction easing) { - - ObjectAnimator animatorX = xAnimator(durationMillis, easing); - animatorX.addUpdateListener(mListener); - animatorX.start(); - } - - /** - * Animates values along both the X and Y axes, in a linear fashion. - * - * @param durationMillisX animation duration along the X axis - * @param durationMillisY animation duration along the Y axis - */ - @RequiresApi(11) - public void animateXY(int durationMillisX, int durationMillisY) { - animateXY(durationMillisX, durationMillisY, Easing.Linear, Easing.Linear); - } - - /** - * Animates values along both the X and Y axes. - * - * @param durationMillisX animation duration along the X axis - * @param durationMillisY animation duration along the Y axis - * @param easing EasingFunction for both axes - */ - @RequiresApi(11) - public void animateXY(int durationMillisX, int durationMillisY, EasingFunction easing) { - - ObjectAnimator xAnimator = xAnimator(durationMillisX, easing); - ObjectAnimator yAnimator = yAnimator(durationMillisY, easing); - - if (durationMillisX > durationMillisY) { - xAnimator.addUpdateListener(mListener); - } else { - yAnimator.addUpdateListener(mListener); - } - - xAnimator.start(); - yAnimator.start(); - } - - /** - * Animates values along both the X and Y axes. - * - * @param durationMillisX animation duration along the X axis - * @param durationMillisY animation duration along the Y axis - * @param easingX EasingFunction for the X axis - * @param easingY EasingFunction for the Y axis - */ - @RequiresApi(11) - public void animateXY(int durationMillisX, int durationMillisY, EasingFunction easingX, - EasingFunction easingY) { - - ObjectAnimator xAnimator = xAnimator(durationMillisX, easingX); - ObjectAnimator yAnimator = yAnimator(durationMillisY, easingY); - - if (durationMillisX > durationMillisY) { - xAnimator.addUpdateListener(mListener); - } else { - yAnimator.addUpdateListener(mListener); - } - - xAnimator.start(); - yAnimator.start(); - } - - /** - * Animates values along the Y axis, in a linear fashion. - * - * @param durationMillis animation duration - */ - @RequiresApi(11) - public void animateY(int durationMillis) { - animateY(durationMillis, Easing.Linear); - } - - /** - * Animates values along the Y axis. - * - * @param durationMillis animation duration - * @param easing EasingFunction - */ - @RequiresApi(11) - public void animateY(int durationMillis, EasingFunction easing) { - - ObjectAnimator animatorY = yAnimator(durationMillis, easing); - animatorY.addUpdateListener(mListener); - animatorY.start(); - } - - /** - * Gets the Y axis phase of the animation. - * - * @return float value of {@link #mPhaseY} - */ - public float getPhaseY() { - return mPhaseY; - } - - /** - * Sets the Y axis phase of the animation. - * - * @param phase float value between 0 - 1 - */ - public void setPhaseY(float phase) { - if (phase > 1f) { - phase = 1f; - } else if (phase < 0f) { - phase = 0f; - } - mPhaseY = phase; - } - - /** - * Gets the X axis phase of the animation. - * - * @return float value of {@link #mPhaseX} - */ - public float getPhaseX() { - return mPhaseX; - } - - /** - * Sets the X axis phase of the animation. - * - * @param phase float value between 0 - 1 - */ - public void setPhaseX(float phase) { - if (phase > 1f) { - phase = 1f; - } else if (phase < 0f) { - phase = 0f; - } - mPhaseX = phase; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/animation/ChartAnimator.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/animation/ChartAnimator.kt new file mode 100644 index 0000000000..c4967b74b4 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/animation/ChartAnimator.kt @@ -0,0 +1,186 @@ +package com.github.mikephil.charting.animation + +import android.animation.ObjectAnimator +import android.animation.ValueAnimator.AnimatorUpdateListener +import com.github.mikephil.charting.animation.Easing.EasingFunction + +/** + * Object responsible for all animations in the Chart. Animations require API level 11. + * + * @author Philipp Jahoda + * @author Mick Ashton + */ +open class ChartAnimator { + /** object that is updated upon animation update */ + private var mListener: AnimatorUpdateListener? = null + + /** The phase of drawn values on the y-axis. 0 - 1 */ + protected var mPhaseY: Float = 1f + + /** The phase of drawn values on the x-axis. 0 - 1 */ + protected var mPhaseX: Float = 1f + + constructor() + + constructor(listener: AnimatorUpdateListener?) { + mListener = listener + } + + private fun xAnimator(duration: Int, easing: EasingFunction?): ObjectAnimator { + val animatorX = ObjectAnimator.ofFloat(this, "phaseX", 0f, 1f) + animatorX.interpolator = easing + animatorX.setDuration(duration.toLong()) + + return animatorX + } + + private fun yAnimator(duration: Int, easing: EasingFunction?): ObjectAnimator { + val animatorY = ObjectAnimator.ofFloat(this, "phaseY", 0f, 1f) + animatorY.interpolator = easing + animatorY.setDuration(duration.toLong()) + + return animatorY + } + + /** + * Animates values along the X axis, in a linear fashion. + * + * @param durationMillis animation duration + */ + fun animateX(durationMillis: Int) { + animateX(durationMillis, Easing.Linear) + } + + /** + * Animates values along the X axis. + * + * @param durationMillis animation duration + * @param easing EasingFunction + */ + fun animateX(durationMillis: Int, easing: EasingFunction?) { + val animatorX = xAnimator(durationMillis, easing) + animatorX.addUpdateListener(mListener) + animatorX.start() + } + + /** + * Animates values along both the X and Y axes, in a linear fashion. + * + * @param durationMillisX animation duration along the X axis + * @param durationMillisY animation duration along the Y axis + */ + fun animateXY(durationMillisX: Int, durationMillisY: Int) { + animateXY(durationMillisX, durationMillisY, Easing.Linear, Easing.Linear) + } + + /** + * Animates values along both the X and Y axes. + * + * @param durationMillisX animation duration along the X axis + * @param durationMillisY animation duration along the Y axis + * @param easing EasingFunction for both axes + */ + fun animateXY(durationMillisX: Int, durationMillisY: Int, easing: EasingFunction?) { + val xAnimator = xAnimator(durationMillisX, easing) + val yAnimator = yAnimator(durationMillisY, easing) + + if (durationMillisX > durationMillisY) { + xAnimator.addUpdateListener(mListener) + } else { + yAnimator.addUpdateListener(mListener) + } + + xAnimator.start() + yAnimator.start() + } + + /** + * Animates values along both the X and Y axes. + * + * @param durationMillisX animation duration along the X axis + * @param durationMillisY animation duration along the Y axis + * @param easingX EasingFunction for the X axis + * @param easingY EasingFunction for the Y axis + */ + fun animateXY( + durationMillisX: Int, durationMillisY: Int, easingX: EasingFunction?, + easingY: EasingFunction? + ) { + val xAnimator = xAnimator(durationMillisX, easingX) + val yAnimator = yAnimator(durationMillisY, easingY) + + if (durationMillisX > durationMillisY) { + xAnimator.addUpdateListener(mListener) + } else { + yAnimator.addUpdateListener(mListener) + } + + xAnimator.start() + yAnimator.start() + } + + /** + * Animates values along the Y axis, in a linear fashion. + * + * @param durationMillis animation duration + */ + fun animateY(durationMillis: Int) { + animateY(durationMillis, Easing.Linear) + } + + /** + * Animates values along the Y axis. + * + * @param durationMillis animation duration + * @param easing EasingFunction + */ + fun animateY(durationMillis: Int, easing: EasingFunction?) { + val animatorY = yAnimator(durationMillis, easing) + animatorY.addUpdateListener(mListener) + animatorY.start() + } + + var phaseY: Float + /** + * Gets the Y axis phase of the animation. + * + * @return float value of [.mPhaseY] + */ + get() = mPhaseY + /** + * Sets the Y axis phase of the animation. + * + * @param phase float value between 0 - 1 + */ + set(phase) { + var phase = phase + if (phase > 1f) { + phase = 1f + } else if (phase < 0f) { + phase = 0f + } + mPhaseY = phase + } + + var phaseX: Float + /** + * Gets the X axis phase of the animation. + * + * @return float value of [.mPhaseX] + */ + get() = mPhaseX + /** + * Sets the X axis phase of the animation. + * + * @param phase float value between 0 - 1 + */ + set(phase) { + var phase = phase + if (phase > 1f) { + phase = 1f + } else if (phase < 0f) { + phase = 0f + } + mPhaseX = phase + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/animation/Easing.java b/MPChartLib/src/main/java/com/github/mikephil/charting/animation/Easing.java deleted file mode 100644 index acb7dcc965..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/animation/Easing.java +++ /dev/null @@ -1,309 +0,0 @@ -package com.github.mikephil.charting.animation; - -import android.animation.TimeInterpolator; -import androidx.annotation.RequiresApi; - -/** - * Easing options. - * - * @author Daniel Cohen Gindi - * @author Mick Ashton - */ -@SuppressWarnings("WeakerAccess") -@RequiresApi(11) -public class Easing { - - public interface EasingFunction extends TimeInterpolator { - @Override - float getInterpolation(float input); - } - - private static final float DOUBLE_PI = 2f * (float) Math.PI; - - @SuppressWarnings("unused") - public static final EasingFunction Linear = new EasingFunction() { - public float getInterpolation(float input) { - return input; - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInQuad = new EasingFunction() { - public float getInterpolation(float input) { - return input * input; - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutQuad = new EasingFunction() { - public float getInterpolation(float input) { - return -input * (input - 2f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutQuad = new EasingFunction() { - public float getInterpolation(float input) { - input *= 2f; - - if (input < 1f) { - return 0.5f * input * input; - } - - return -0.5f * ((--input) * (input - 2f) - 1f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInCubic = new EasingFunction() { - public float getInterpolation(float input) { - return (float) Math.pow(input, 3); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutCubic = new EasingFunction() { - public float getInterpolation(float input) { - input--; - return (float) Math.pow(input, 3) + 1f; - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutCubic = new EasingFunction() { - public float getInterpolation(float input) { - input *= 2f; - if (input < 1f) { - return 0.5f * (float) Math.pow(input, 3); - } - input -= 2f; - return 0.5f * ((float) Math.pow(input, 3) + 2f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInQuart = new EasingFunction() { - - public float getInterpolation(float input) { - return (float) Math.pow(input, 4); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutQuart = new EasingFunction() { - public float getInterpolation(float input) { - input--; - return -((float) Math.pow(input, 4) - 1f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutQuart = new EasingFunction() { - public float getInterpolation(float input) { - input *= 2f; - if (input < 1f) { - return 0.5f * (float) Math.pow(input, 4); - } - input -= 2f; - return -0.5f * ((float) Math.pow(input, 4) - 2f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInSine = new EasingFunction() { - public float getInterpolation(float input) { - return -(float) Math.cos(input * (Math.PI / 2f)) + 1f; - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutSine = new EasingFunction() { - public float getInterpolation(float input) { - return (float) Math.sin(input * (Math.PI / 2f)); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutSine = new EasingFunction() { - public float getInterpolation(float input) { - return -0.5f * ((float) Math.cos(Math.PI * input) - 1f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInExpo = new EasingFunction() { - public float getInterpolation(float input) { - return (input == 0) ? 0f : (float) Math.pow(2f, 10f * (input - 1f)); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutExpo = new EasingFunction() { - public float getInterpolation(float input) { - return (input == 1f) ? 1f : (-(float) Math.pow(2f, -10f * (input + 1f))); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutExpo = new EasingFunction() { - public float getInterpolation(float input) { - if (input == 0) { - return 0f; - } else if (input == 1f) { - return 1f; - } - - input *= 2f; - if (input < 1f) { - return 0.5f * (float) Math.pow(2f, 10f * (input - 1f)); - } - return 0.5f * (-(float) Math.pow(2f, -10f * --input) + 2f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInCirc = new EasingFunction() { - public float getInterpolation(float input) { - return -((float) Math.sqrt(1f - input * input) - 1f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutCirc = new EasingFunction() { - public float getInterpolation(float input) { - input--; - return (float) Math.sqrt(1f - input * input); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutCirc = new EasingFunction() { - public float getInterpolation(float input) { - input *= 2f; - if (input < 1f) { - return -0.5f * ((float) Math.sqrt(1f - input * input) - 1f); - } - return 0.5f * ((float) Math.sqrt(1f - (input -= 2f) * input) + 1f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInElastic = new EasingFunction() { - public float getInterpolation(float input) { - if (input == 0) { - return 0f; - } else if (input == 1) { - return 1f; - } - - float p = 0.3f; - float s = p / DOUBLE_PI * (float) Math.asin(1f); - return -((float) Math.pow(2f, 10f * (input -= 1f)) - *(float) Math.sin((input - s) * DOUBLE_PI / p)); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutElastic = new EasingFunction() { - public float getInterpolation(float input) { - if (input == 0) { - return 0f; - } else if (input == 1) { - return 1f; - } - - float p = 0.3f; - float s = p / DOUBLE_PI * (float) Math.asin(1f); - return 1f - + (float) Math.pow(2f, -10f * input) - * (float) Math.sin((input - s) * DOUBLE_PI / p); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutElastic = new EasingFunction() { - public float getInterpolation(float input) { - if (input == 0) { - return 0f; - } - - input *= 2f; - if (input == 2) { - return 1f; - } - - float p = 1f / 0.45f; - float s = 0.45f / DOUBLE_PI * (float) Math.asin(1f); - if (input < 1f) { - return -0.5f - * ((float) Math.pow(2f, 10f * (input -= 1f)) - * (float) Math.sin((input * 1f - s) * DOUBLE_PI * p)); - } - return 1f + 0.5f - * (float) Math.pow(2f, -10f * (input -= 1f)) - * (float) Math.sin((input * 1f - s) * DOUBLE_PI * p); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInBack = new EasingFunction() { - public float getInterpolation(float input) { - final float s = 1.70158f; - return input * input * ((s + 1f) * input - s); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutBack = new EasingFunction() { - public float getInterpolation(float input) { - final float s = 1.70158f; - input--; - return (input * input * ((s + 1f) * input + s) + 1f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutBack = new EasingFunction() { - public float getInterpolation(float input) { - float s = 1.70158f; - input *= 2f; - if (input < 1f) { - return 0.5f * (input * input * (((s *= (1.525f)) + 1f) * input - s)); - } - return 0.5f * ((input -= 2f) * input * (((s *= (1.525f)) + 1f) * input + s) + 2f); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInBounce = new EasingFunction() { - public float getInterpolation(float input) { - return 1f - EaseOutBounce.getInterpolation(1f - input); - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseOutBounce = new EasingFunction() { - public float getInterpolation(float input) { - float s = 7.5625f; - if (input < (1f / 2.75f)) { - return s * input * input; - } else if (input < (2f / 2.75f)) { - return s * (input -= (1.5f / 2.75f)) * input + 0.75f; - } else if (input < (2.5f / 2.75f)) { - return s * (input -= (2.25f / 2.75f)) * input + 0.9375f; - } - return s * (input -= (2.625f / 2.75f)) * input + 0.984375f; - } - }; - - @SuppressWarnings("unused") - public static final EasingFunction EaseInOutBounce = new EasingFunction() { - public float getInterpolation(float input) { - if (input < 0.5f) { - return EaseInBounce.getInterpolation(input * 2f) * 0.5f; - } - return EaseOutBounce.getInterpolation(input * 2f - 1f) * 0.5f + 0.5f; - } - }; - -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/animation/Easing.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/animation/Easing.kt new file mode 100644 index 0000000000..d3a6192236 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/animation/Easing.kt @@ -0,0 +1,319 @@ +package com.github.mikephil.charting.animation + +import android.animation.TimeInterpolator +import kotlin.math.asin +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin +import kotlin.math.sqrt + +/** + * Easing options. + * + * @author Daniel Cohen Gindi + * @author Mick Ashton + */ +object Easing { + private const val DOUBLE_PI = 2f * Math.PI.toFloat() + + @Suppress("unused") + val Linear: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return input + } + } + + @Suppress("unused") + val EaseInQuad: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return input * input + } + } + + @Suppress("unused") + val EaseOutQuad: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return -input * (input - 2f) + } + } + + @JvmField + @Suppress("unused") + val EaseInOutQuad: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + input *= 2f + + if (input < 1f) { + return 0.5f * input * input + } + + return -0.5f * ((--input) * (input - 2f) - 1f) + } + } + + @Suppress("unused") + val EaseInCubic: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return input.toDouble().pow(3.0).toFloat() + } + } + + @Suppress("unused") + val EaseOutCubic: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + input-- + return input.toDouble().pow(3.0).toFloat() + 1f + } + } + + @JvmField + @Suppress("unused") + val EaseInOutCubic: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + input *= 2f + if (input < 1f) { + return 0.5f * input.toDouble().pow(3.0).toFloat() + } + input -= 2f + return 0.5f * (input.toDouble().pow(3.0).toFloat() + 2f) + } + } + + @Suppress("unused") + val EaseInQuart: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return input.toDouble().pow(4.0).toFloat() + } + } + + @Suppress("unused") + val EaseOutQuart: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + input-- + return -(input.toDouble().pow(4.0).toFloat() - 1f) + } + } + + @Suppress("unused") + val EaseInOutQuart: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + input *= 2f + if (input < 1f) { + return 0.5f * input.toDouble().pow(4.0).toFloat() + } + input -= 2f + return -0.5f * (input.toDouble().pow(4.0).toFloat() - 2f) + } + } + + @Suppress("unused") + val EaseInSine: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return -cos(input * (Math.PI / 2f)).toFloat() + 1f + } + } + + @Suppress("unused") + val EaseOutSine: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return sin(input * (Math.PI / 2f)).toFloat() + } + } + + @Suppress("unused") + val EaseInOutSine: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return -0.5f * (cos(Math.PI * input).toFloat() - 1f) + } + } + + @Suppress("unused") + val EaseInExpo: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return if (input == 0f) 0f else 2.0.pow((10f * (input - 1f)).toDouble()).toFloat() + } + } + + @Suppress("unused") + val EaseOutExpo: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return if (input == 1f) 1f else (-2.0.pow((-10f * (input + 1f)).toDouble()).toFloat()) + } + } + + @Suppress("unused") + val EaseInOutExpo: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + if (input == 0f) { + return 0f + } else if (input == 1f) { + return 1f + } + + input *= 2f + if (input < 1f) { + return 0.5f * 2.0.pow((10f * (input - 1f)).toDouble()).toFloat() + } + return 0.5f * (-2.0.pow((-10f * --input).toDouble()).toFloat() + 2f) + } + } + + @Suppress("unused") + val EaseInCirc: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return -(sqrt((1f - input * input).toDouble()).toFloat() - 1f) + } + } + + @Suppress("unused") + val EaseOutCirc: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + input-- + return sqrt((1f - input * input).toDouble()).toFloat() + } + } + + @Suppress("unused") + val EaseInOutCirc: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + input *= 2f + if (input < 1f) { + return -0.5f * (sqrt((1f - input * input).toDouble()).toFloat() - 1f) + } + return 0.5f * (sqrt((1f - (2f.let { input -= it; input }) * input).toDouble()).toFloat() + 1f) + } + } + + @Suppress("unused") + val EaseInElastic: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + if (input == 0f) { + return 0f + } else if (input == 1f) { + return 1f + } + + val p = 0.3f + val s = p / DOUBLE_PI * asin(1.0).toFloat() + return -(2.0.pow((10f * (1f.let { input -= it; input })).toDouble()).toFloat() * sin(((input - s) * DOUBLE_PI / p).toDouble()).toFloat()) + } + } + + @Suppress("unused") + val EaseOutElastic: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + if (input == 0f) { + return 0f + } else if (input == 1f) { + return 1f + } + + val p = 0.3f + val s = p / DOUBLE_PI * asin(1.0).toFloat() + return (1f + + 2.0.pow((-10f * input).toDouble()).toFloat() * sin(((input - s) * DOUBLE_PI / p).toDouble()).toFloat()) + } + } + + @Suppress("unused") + val EaseInOutElastic: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + if (input == 0f) { + return 0f + } + + input *= 2f + if (input == 2f) { + return 1f + } + + val p = 1f / 0.45f + val s = 0.45f / DOUBLE_PI * asin(1.0).toFloat() + if (input < 1f) { + return (-0.5f + * (2.0.pow((10f * (1f.let { input -= it; input })).toDouble()) + .toFloat() * sin(((input * 1f - s) * DOUBLE_PI * p).toDouble()).toFloat())) + } + return 1f + (0.5f + * 2.0.pow((-10f * (1f.let { input -= it; input })).toDouble()).toFloat() * sin(((input * 1f - s) * DOUBLE_PI * p).toDouble()).toFloat()) + } + } + + @Suppress("unused") + val EaseInBack: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + val s = 1.70158f + return input * input * ((s + 1f) * input - s) + } + } + + @Suppress("unused") + val EaseOutBack: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + val s = 1.70158f + input-- + return (input * input * ((s + 1f) * input + s) + 1f) + } + } + + @Suppress("unused") + val EaseInOutBack: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + var s = 1.70158f + input *= 2f + if (input < 1f) { + return 0.5f * (input * input * (((1.525f.let { s *= it; s }) + 1f) * input - s)) + } + return 0.5f * ((2f.let { input -= it; input }) * input * (((1.525f.let { s *= it; s }) + 1f) * input + s) + 2f) + } + } + + @Suppress("unused") + val EaseInBounce: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + return 1f - EaseOutBounce.getInterpolation(1f - input) + } + } + + @Suppress("unused") + val EaseOutBounce: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + var input = input + val s = 7.5625f + if (input < (1f / 2.75f)) { + return s * input * input + } else if (input < (2f / 2.75f)) { + return s * (1.5f / 2.75f.let { input -= it; input }) * input + 0.75f + } else if (input < (2.5f / 2.75f)) { + return s * (2.25f / 2.75f.let { input -= it; input }) * input + 0.9375f + } + return s * (2.625f / 2.75f.let { input -= it; input }) * input + 0.984375f + } + } + + @Suppress("unused") + val EaseInOutBounce: EasingFunction = object : EasingFunction { + override fun getInterpolation(input: Float): Float { + if (input < 0.5f) { + return EaseInBounce.getInterpolation(input * 2f) * 0.5f + } + return EaseOutBounce.getInterpolation(input * 2f - 1f) * 0.5f + 0.5f + } + } + + interface EasingFunction : TimeInterpolator { + override fun getInterpolation(input: Float): Float + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarChart.java deleted file mode 100644 index 3dd6eac65f..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarChart.java +++ /dev/null @@ -1,300 +0,0 @@ -package com.github.mikephil.charting.charts; - -import android.content.Context; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.util.Log; - -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.highlight.BarHighlighter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.renderer.BarChartRenderer; - -import java.util.Locale; - -/** - * Chart that draws bars. - * - * @author Philipp Jahoda - */ -public class BarChart extends BarLineChartBase implements BarDataProvider { - - /** - * flag that indicates whether the highlight should be full-bar oriented, or single-value? - */ - protected boolean mHighlightFullBarEnabled = false; - - /** - * if set to true, all values are drawn above their bars, instead of below their top - */ - private boolean mDrawValueAboveBar = true; - - /** - * if set to true, a grey area is drawn behind each bar that indicates the maximum value - */ - private boolean mDrawBarShadow = false; - - /** - * if set to true, the bar chart's bars would be round on all corners instead of rectangular - */ - private boolean mDrawRoundedBars; - - /** - * the radius of the rounded bar chart bars - */ - private float mRoundedBarRadius = 0f; - - private boolean mFitBars = false; - - public BarChart(Context context) { - super(context); - } - - public BarChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BarChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void init() { - super.init(); - - mRenderer = new BarChartRenderer(this, mAnimator, mViewPortHandler, mDrawRoundedBars, mRoundedBarRadius); - - setHighlighter(new BarHighlighter(this)); - - getXAxis().setSpaceMin(0.5f); - getXAxis().setSpaceMax(0.5f); - } - - @Override - protected void calcMinMax() { - - if (mFitBars) { - mXAxis.calculate(mData.getXMin() - mData.getBarWidth() / 2f, mData.getXMax() + mData.getBarWidth() / 2f); - } else { - mXAxis.calculate(mData.getXMin(), mData.getXMax()); - } - - // calculate axis range (min / max) according to provided data - mAxisLeft.calculate(mData.getYMin(YAxis.AxisDependency.LEFT), mData.getYMax(YAxis.AxisDependency.LEFT)); - mAxisRight.calculate(mData.getYMin(YAxis.AxisDependency.RIGHT), mData.getYMax(YAxis.AxisDependency - .RIGHT)); - } - - /** - * Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch - * point - * inside the BarChart. - * - * @param x - * @param y - */ - @Override - public Highlight getHighlightByTouchPoint(float x, float y) { - - if (mData == null) { - Log.e(LOG_TAG, "Can't select by touch. No data set."); - return null; - } else { - Highlight h = getHighlighter().getHighlight(x, y); - if (h == null || !isHighlightFullBarEnabled()) return h; - - // For isHighlightFullBarEnabled, remove stackIndex - return new Highlight(h.getX(), h.getY(), - h.getXPx(), h.getYPx(), - h.getDataSetIndex(), -1, h.getAxis()); - } - } - - /** - * Returns the bounding box of the specified Entry in the specified DataSet. Returns null if the Entry could not be - * found in the charts data. Performance-intensive code should use void getBarBounds(BarEntry, RectF) instead. - * - * @param barEntry - */ - public RectF getBarBounds(BarEntry barEntry) { - - RectF bounds = new RectF(); - getBarBounds(barEntry, bounds); - - return bounds; - } - - /** - * The passed outputRect will be assigned the values of the bounding box of the specified Entry in the specified DataSet. - * The rect will be assigned Float.MIN_VALUE in all locations if the Entry could not be found in the charts data. - * - * @param barEntry - */ - public void getBarBounds(BarEntry barEntry, RectF outputRect) { - - IBarDataSet set = mData.getDataSetForEntry(barEntry); - - if (set == null) { - outputRect.set(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE); - return; - } - - float y = barEntry.getY(); - float x = barEntry.getX(); - - float barWidth = mData.getBarWidth(); - - float left = x - barWidth / 2f; - float right = x + barWidth / 2f; - float top = y >= 0 ? y : 0; - float bottom = y <= 0 ? y : 0; - - outputRect.set(left, top, right, bottom); - - getTransformer(set.getAxisDependency()).rectValueToPixel(outputRect); - } - - /** - * If set to true, all values are drawn above their bars, instead of below their top. - * - */ - public void setDrawValueAboveBar(boolean enabled) { - mDrawValueAboveBar = enabled; - } - - /** - * returns true if drawing values above bars is enabled, false if not - * - */ - public boolean isDrawValueAboveBarEnabled() { - return mDrawValueAboveBar; - } - - /** - * If set to true, a grey area is drawn behind each bar that indicates the maximum value. Enabling his will reduce - * performance by about 50%. - * - */ - public void setDrawBarShadow(boolean enabled) { - mDrawBarShadow = enabled; - } - - /** - * returns true if drawing shadows (maxvalue) for each bar is enabled, false if not - * - */ - public boolean isDrawBarShadowEnabled() { - return mDrawBarShadow; - } - - /** - * Set this to true to make the highlight operation full-bar oriented, false to make it highlight single values (relevant - * only for stacked). If enabled, highlighting operations will highlight the whole bar, even if only a single stack entry - * was tapped. - * Default: false - * - */ - public void setHighlightFullBarEnabled(boolean enabled) { - mHighlightFullBarEnabled = enabled; - } - - /** - * @return true the highlight operation is be full-bar oriented, false if single-value - */ - @Override - public boolean isHighlightFullBarEnabled() { - return mHighlightFullBarEnabled; - } - - /** - * Highlights the value at the given x-value in the given DataSet. Provide - * -1 as the dataSetIndex to undo all highlighting. - * - * @param x - * @param dataSetIndex - * @param stackIndex the index inside the stack - only relevant for stacked entries - */ - public void highlightValue(float x, int dataSetIndex, int stackIndex) { - highlightValue(new Highlight(x, dataSetIndex, stackIndex), false); - } - - @Override - public BarData getBarData() { - return mData; - } - - /** - * Adds half of the bar width to each side of the x-axis range in order to allow the bars of the barchart to be - * fully displayed. - * Default: false - * - */ - public void setFitBars(boolean enabled) { - mFitBars = enabled; - } - - /** - * Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries. - * Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified - * by the parameters. - * Calls notifyDataSetChanged() afterwards. - * - * @param fromX the starting point on the x-axis where the grouping should begin - * @param groupSpace the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f - * @param barSpace the space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f - */ - public void groupBars(float fromX, float groupSpace, float barSpace) { - - if (getBarData() == null) { - throw new RuntimeException("You need to set data for the chart before grouping bars."); - } else { - getBarData().groupBars(fromX, groupSpace, barSpace); - notifyDataSetChanged(); - } - } - /** - * Used to enable rounded bar chart bars and set the radius of the rounded bars - * - * @param mRoundedBarRadius - the radius of the rounded bars - */ - public void setRoundedBarRadius(float mRoundedBarRadius) { - this.mRoundedBarRadius = mRoundedBarRadius; - this.mDrawRoundedBars = true; - init(); - } - - @Override - public String getAccessibilityDescription() { - - BarData barData = getBarData(); - if (barData == null) { - return ""; - } - - int entryCount = barData.getEntryCount(); - - // Find the min and max index - IAxisValueFormatter yAxisValueFormatter = getAxisLeft().getValueFormatter(); - String minVal = yAxisValueFormatter.getFormattedValue(barData.getYMin(), null); - String maxVal = yAxisValueFormatter.getFormattedValue(barData.getYMax(), null); - - // Data range... - IAxisValueFormatter xAxisValueFormatter = getXAxis().getValueFormatter(); - String minRange = xAxisValueFormatter.getFormattedValue(barData.getXMin(), null); - String maxRange = xAxisValueFormatter.getFormattedValue(barData.getXMax(), null); - - String entries = entryCount == 1 ? "entry" : "entries"; - - // Format the values of min and max; to recite them back - - return String.format(Locale.getDefault(), "The bar chart has %d %s. " + - "The minimum value is %s and maximum value is %s." + - "Data ranges from %s to %s.", - entryCount, entries, minVal, maxVal, minRange, maxRange); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarChart.kt new file mode 100644 index 0000000000..26086364b1 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarChart.kt @@ -0,0 +1,262 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.graphics.RectF +import android.util.AttributeSet +import android.util.Log +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.highlight.BarHighlighter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.renderer.BarChartRenderer +import java.util.Locale + +/** + * Chart that draws bars. + * + * @author Philipp Jahoda + */ +open class BarChart : BarLineChartBase, BarDataProvider { + /** + * flag that indicates whether the highlight should be full-bar oriented, or single-value? + */ + protected var mHighlightFullBarEnabled: Boolean = false + + /** + * if set to true, all values are drawn above their bars, instead of below their top + */ + private var mDrawValueAboveBar = true + + /** + * if set to true, a grey area is drawn behind each bar that indicates the maximum value + */ + private var mDrawBarShadow = false + + /** + * if set to true, the bar chart's bars would be round on all corners instead of rectangular + */ + private var mDrawRoundedBars = false + + /** + * the radius of the rounded bar chart bars + */ + private var mRoundedBarRadius = 0f + + private var mFitBars = false + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + init { + mRenderer = BarChartRenderer(this, mAnimator, viewPortHandler, mDrawRoundedBars, mRoundedBarRadius) + + setHighlighter(BarHighlighter(this)) + + xAxis.spaceMin = 0.5f + xAxis.spaceMax = 0.5f + } + + override fun calcMinMax() { + mData?.let { data -> + if (mFitBars) { + mXAxis.calculate(data.xMin - data.barWidth / 2f, data.xMax + data.barWidth / 2f) + } else { + mXAxis.calculate(data.xMin, data.xMax) + } + + // calculate axis range (min / max) according to provided data + mAxisLeft.calculate(data.getYMin(AxisDependency.LEFT), data.getYMax(AxisDependency.LEFT)) + mAxisRight.calculate(data.getYMin(AxisDependency.RIGHT), data.getYMax(AxisDependency.RIGHT)) + } + } + + /** + * Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch + * point + * inside the BarChart. + * + * @param x + * @param y + */ + override fun getHighlightByTouchPoint(x: Float, y: Float): Highlight? { + if (mData == null) { + Log.e(LOG_TAG, "Can't select by touch. No data set.") + return null + } else { + val h = highlighter?.getHighlight(x, y) + if (h == null || !isHighlightFullBarEnabled) return h + + // For isHighlightFullBarEnabled, remove stackIndex + return Highlight( + h.x, h.y, + h.xPx, h.yPx, + h.dataSetIndex, -1, h.axis + ) + } + } + + /** + * Returns the bounding box of the specified Entry in the specified DataSet. Returns null if the Entry could not be + * found in the charts data. Performance-intensive code should use void getBarBounds(BarEntry, RectF) instead. + * + * @param barEntry + */ + fun getBarBounds(barEntry: BarEntry): RectF { + val bounds = RectF() + getBarBounds(barEntry, bounds) + + return bounds + } + + /** + * The passed outputRect will be assigned the values of the bounding box of the specified Entry in the specified DataSet. + * The rect will be assigned Float.MIN_VALUE in all locations if the Entry could not be found in the charts data. + * + * @param barEntry + */ + open fun getBarBounds(barEntry: BarEntry, outputRect: RectF) { + val set = mData?.getDataSetForEntry(barEntry) + + if (set == null) { + outputRect.set(Float.Companion.MIN_VALUE, Float.Companion.MIN_VALUE, Float.Companion.MIN_VALUE, Float.Companion.MIN_VALUE) + return + } + + val y = barEntry.y + val x = barEntry.x + + val barWidth = mData?.barWidth ?: return + + val left = x - barWidth / 2f + val right = x + barWidth / 2f + val top = if (y >= 0) y else 0f + val bottom = if (y <= 0) y else 0f + + outputRect.set(left, top, right, bottom) + + getTransformer(set.axisDependency).rectValueToPixel(outputRect) + } + + /** + * returns true if drawing values above bars is enabled, false if not + * + */ + override var isDrawValueAboveBarEnabled: Boolean + get() = mDrawValueAboveBar + set(value) { + mDrawValueAboveBar = value + } + + /** + * returns true if drawing shadows (maxvalue) for each bar is enabled, false if not + * + */ + override var isDrawBarShadowEnabled: Boolean + get() = mDrawBarShadow + set(value) { + mDrawBarShadow = value + } + + /** + * @return true the highlight operation is be full-bar oriented, false if single-value + */ + override var isHighlightFullBarEnabled: Boolean + get() = mHighlightFullBarEnabled + set(value) { + mHighlightFullBarEnabled = value + } + + /** + * Highlights the value at the given x-value in the given DataSet. Provide + * -1 as the dataSetIndex to undo all highlighting. + * + * @param x + * @param dataSetIndex + * @param dataIndex the index inside the stack - only relevant for stacked entries + */ + override fun highlightValue(x: Float, y: Float, dataSetIndex: Int, dataIndex: Int, callListener: Boolean) { + super.highlightValue(Highlight(x, dataSetIndex, dataIndex), false) + } + + override var barData: BarData? + get() = mData + set(value) { + mData = value + } + + /** + * Adds half of the bar width to each side of the x-axis range in order to allow the bars of the barchart to be + * fully displayed. + * Default: false + * + */ + fun setFitBars(enabled: Boolean) { + mFitBars = enabled + } + + /** + * Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries. + * Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified + * by the parameters. + * Calls notifyDataSetChanged() afterwards. + * + * @param fromX the starting point on the x-axis where the grouping should begin + * @param groupSpace the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f + * @param barSpace the space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f + */ + fun groupBars(fromX: Float, groupSpace: Float, barSpace: Float) { + if (barData == null) { + throw RuntimeException("You need to set data for the chart before grouping bars.") + } else { + barData!!.groupBars(fromX, groupSpace, barSpace) + notifyDataSetChanged() + } + } + + /** + * Used to enable rounded bar chart bars and set the radius of the rounded bars + * + * @param mRoundedBarRadius - the radius of the rounded bars + */ + fun setRoundedBarRadius(mRoundedBarRadius: Float) { + this.mRoundedBarRadius = mRoundedBarRadius + this.mDrawRoundedBars = true + mRenderer = BarChartRenderer(this, mAnimator, viewPortHandler, mDrawRoundedBars, mRoundedBarRadius) + } + + override val accessibilityDescription: String? + get() { + val barData = barData + if (barData == null) { + return "" + } + + val entryCount = barData.entryCount + + // Find the min and max index + val yAxisValueFormatter = axisLeft.valueFormatter + val minVal = yAxisValueFormatter.getFormattedValue(barData.yMin, null) + val maxVal = yAxisValueFormatter.getFormattedValue(barData.yMax, null) + + // Data range... + val xAxisValueFormatter = xAxis.valueFormatter + val minRange = xAxisValueFormatter.getFormattedValue(barData.xMin, null) + val maxRange = xAxisValueFormatter.getFormattedValue(barData.xMax, null) + + val entries = if (entryCount == 1) "entry" else "entries" + + // Format the values of min and max; to recite them back + return String.format( + Locale.getDefault(), "The bar chart has %d %s. " + + "The minimum value is %s and maximum value is %s." + + "Data ranges from %s to %s.", + entryCount, entries, minVal, maxVal, minRange, maxRange + ) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.java deleted file mode 100644 index 8ef4c5e2b0..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.java +++ /dev/null @@ -1,1701 +0,0 @@ - -package com.github.mikephil.charting.charts; - -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Paint; -import android.graphics.Paint.Style; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.util.Log; -import android.view.MotionEvent; - -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.ChartHighlighter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.dataprovider.BarLineScatterCandleBubbleDataProvider; -import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet; -import com.github.mikephil.charting.jobs.AnimatedMoveViewJob; -import com.github.mikephil.charting.jobs.AnimatedZoomJob; -import com.github.mikephil.charting.jobs.MoveViewJob; -import com.github.mikephil.charting.jobs.ZoomJob; -import com.github.mikephil.charting.listener.BarLineChartTouchListener; -import com.github.mikephil.charting.listener.OnDrawListener; -import com.github.mikephil.charting.renderer.XAxisRenderer; -import com.github.mikephil.charting.renderer.YAxisRenderer; -import com.github.mikephil.charting.utils.MPPointD; -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.Transformer; -import com.github.mikephil.charting.utils.Utils; - -/** - * Base-class of LineChart, BarChart, ScatterChart and CandleStickChart. - * - * @author Philipp Jahoda - */ -@SuppressLint("RtlHardcoded") -public abstract class BarLineChartBase>> - extends Chart implements BarLineScatterCandleBubbleDataProvider { - - /** - * the maximum number of entries to which values will be drawn - * (entry numbers greater than this value will cause value-labels to disappear) - */ - protected int mMaxVisibleCount = 100; - - /** - * flag that indicates if auto scaling on the y axis is enabled - */ - protected boolean mAutoScaleMinMaxEnabled = false; - - /** - * flag that indicates if pinch-zoom is enabled. if true, both x and y axis - * can be scaled with 2 fingers, if false, x and y axis can be scaled - * separately - */ - protected boolean mPinchZoomEnabled = false; - - /** - * flag that indicates if double tap zoom is enabled or not - */ - protected boolean mDoubleTapToZoomEnabled = true; - - /** - * flag that indicates if highlighting per dragging over a fully zoomed out - * chart is enabled - */ - protected boolean mHighlightPerDragEnabled = true; - - /** - * if true, dragging is enabled for the chart - */ - private boolean mDragXEnabled = true; - private boolean mDragYEnabled = true; - - private boolean mScaleXEnabled = true; - private boolean mScaleYEnabled = true; - - /** - * if true, fling gesture is enabled for the chart - */ - private boolean mFlingEnabled = false; - - /** - * paint object for the (by default) lightgrey background of the grid - */ - protected Paint mGridBackgroundPaint; - - protected Paint mBorderPaint; - - /** - * flag indicating if the grid background should be drawn or not - */ - protected boolean mDrawGridBackground = false; - - protected boolean mDrawBorders = false; - - protected boolean mClipValuesToContent = false; - - protected boolean mClipDataToContent = true; - - /** - * Sets the minimum offset (padding) around the chart, defaults to 15 - */ - protected float mMinOffset = 15.f; - - /** - * flag indicating if the chart should stay at the same position after a rotation. Default is false. - */ - protected boolean mKeepPositionOnRotation = false; - - /** - * the listener for user drawing on the chart - */ - protected OnDrawListener mDrawListener; - - /** - * the object representing the labels on the left y-axis - */ - protected YAxis mAxisLeft; - - /** - * the object representing the labels on the right y-axis - */ - protected YAxis mAxisRight; - - protected YAxisRenderer mAxisRendererLeft; - protected YAxisRenderer mAxisRendererRight; - - protected Transformer mLeftAxisTransformer; - protected Transformer mRightAxisTransformer; - - protected XAxisRenderer mXAxisRenderer; - - // /** the approximator object used for data filtering */ - // private Approximator mApproximator; - - public BarLineChartBase(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - public BarLineChartBase(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BarLineChartBase(Context context) { - super(context); - } - - @Override - protected void init() { - super.init(); - - mAxisLeft = new YAxis(AxisDependency.LEFT); - mAxisRight = new YAxis(AxisDependency.RIGHT); - - mLeftAxisTransformer = new Transformer(mViewPortHandler); - mRightAxisTransformer = new Transformer(mViewPortHandler); - - mAxisRendererLeft = new YAxisRenderer(mViewPortHandler, mAxisLeft, mLeftAxisTransformer); - mAxisRendererRight = new YAxisRenderer(mViewPortHandler, mAxisRight, mRightAxisTransformer); - - mXAxisRenderer = new XAxisRenderer(mViewPortHandler, mXAxis, mLeftAxisTransformer); - - setHighlighter(new ChartHighlighter(this)); - - mChartTouchListener = new BarLineChartTouchListener(this, mViewPortHandler.getMatrixTouch(), 3f); - - mGridBackgroundPaint = new Paint(); - mGridBackgroundPaint.setStyle(Style.FILL); - // mGridBackgroundPaint.setColor(Color.WHITE); - mGridBackgroundPaint.setColor(Color.rgb(240, 240, 240)); // light - // grey - - mBorderPaint = new Paint(); - mBorderPaint.setStyle(Style.STROKE); - mBorderPaint.setColor(Color.BLACK); - mBorderPaint.setStrokeWidth(Utils.convertDpToPixel(1f)); - } - - // for performance tracking - private long totalTime = 0; - private long drawCycles = 0; - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mData == null) - return; - - long starttime = System.currentTimeMillis(); - - // execute all drawing commands - drawGridBackground(canvas); - - if (mAutoScaleMinMaxEnabled) { - autoScale(); - } - - if (mAxisLeft.isEnabled()) - mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum, mAxisLeft.isInverted()); - - if (mAxisRight.isEnabled()) - mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum, mAxisRight.isInverted()); - - if (mXAxis.isEnabled()) - mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false); - - // Y-axis labels could have changed in size affecting the offsets - if (mAutoScaleMinMaxEnabled) { - calculateOffsets(); - mViewPortHandler.refresh(mViewPortHandler.getMatrixTouch(), this, false); - } - - mXAxisRenderer.renderAxisLine(canvas); - mAxisRendererLeft.renderAxisLine(canvas); - mAxisRendererRight.renderAxisLine(canvas); - - if (mXAxis.isDrawGridLinesBehindDataEnabled()) - mXAxisRenderer.renderGridLines(canvas); - - if (mAxisLeft.isDrawGridLinesBehindDataEnabled()) - mAxisRendererLeft.renderGridLines(canvas); - - if (mAxisRight.isDrawGridLinesBehindDataEnabled()) - mAxisRendererRight.renderGridLines(canvas); - - if (mXAxis.isEnabled() && mXAxis.isDrawLimitLinesBehindDataEnabled()) - mXAxisRenderer.renderLimitLines(canvas); - - if (mAxisLeft.isEnabled() && mAxisLeft.isDrawLimitLinesBehindDataEnabled()) - mAxisRendererLeft.renderLimitLines(canvas); - - if (mAxisRight.isEnabled() && mAxisRight.isDrawLimitLinesBehindDataEnabled()) - mAxisRendererRight.renderLimitLines(canvas); - - int clipRestoreCount = canvas.save(); - - if (isClipDataToContentEnabled()) { - // make sure the data cannot be drawn outside the content-rect - canvas.clipRect(mViewPortHandler.getContentRect()); - } - - mRenderer.drawData(canvas); - - if (!mXAxis.isDrawGridLinesBehindDataEnabled()) - mXAxisRenderer.renderGridLines(canvas); - - if (!mAxisLeft.isDrawGridLinesBehindDataEnabled()) - mAxisRendererLeft.renderGridLines(canvas); - - if (!mAxisRight.isDrawGridLinesBehindDataEnabled()) - mAxisRendererRight.renderGridLines(canvas); - - // if highlighting is enabled - if (valuesToHighlight()) - mRenderer.drawHighlighted(canvas, mIndicesToHighlight); - - // Removes clipping rectangle - canvas.restoreToCount(clipRestoreCount); - - mRenderer.drawExtras(canvas); - - if (mXAxis.isEnabled() && !mXAxis.isDrawLimitLinesBehindDataEnabled()) - mXAxisRenderer.renderLimitLines(canvas); - - if (mAxisLeft.isEnabled() && !mAxisLeft.isDrawLimitLinesBehindDataEnabled()) - mAxisRendererLeft.renderLimitLines(canvas); - - if (mAxisRight.isEnabled() && !mAxisRight.isDrawLimitLinesBehindDataEnabled()) - mAxisRendererRight.renderLimitLines(canvas); - - mXAxisRenderer.renderAxisLabels(canvas); - mAxisRendererLeft.renderAxisLabels(canvas); - mAxisRendererRight.renderAxisLabels(canvas); - - if (isClipValuesToContentEnabled()) { - clipRestoreCount = canvas.save(); - canvas.clipRect(mViewPortHandler.getContentRect()); - - mRenderer.drawValues(canvas); - - canvas.restoreToCount(clipRestoreCount); - } else { - mRenderer.drawValues(canvas); - } - - mLegendRenderer.renderLegend(canvas); - - drawDescription(canvas); - - drawMarkers(canvas); - - if (mLogEnabled) { - long drawtime = (System.currentTimeMillis() - starttime); - totalTime += drawtime; - drawCycles += 1; - long average = totalTime / drawCycles; - Log.i(LOG_TAG, "Drawtime: " + drawtime + " ms, average: " + average + " ms, cycles: " - + drawCycles); - } - } - - /** - * RESET PERFORMANCE TRACKING FIELDS - */ - public void resetTracking() { - totalTime = 0; - drawCycles = 0; - } - - protected void prepareValuePxMatrix() { - - if (mLogEnabled) - Log.i(LOG_TAG, "Preparing Value-Px Matrix, xmin: " + mXAxis.mAxisMinimum + ", xmax: " - + mXAxis.mAxisMaximum + ", xdelta: " + mXAxis.mAxisRange); - - mRightAxisTransformer.prepareMatrixValuePx(mXAxis.mAxisMinimum, - mXAxis.mAxisRange, - mAxisRight.mAxisRange, - mAxisRight.mAxisMinimum); - mLeftAxisTransformer.prepareMatrixValuePx(mXAxis.mAxisMinimum, - mXAxis.mAxisRange, - mAxisLeft.mAxisRange, - mAxisLeft.mAxisMinimum); - } - - protected void prepareOffsetMatrix() { - - mRightAxisTransformer.prepareMatrixOffset(mAxisRight.isInverted()); - mLeftAxisTransformer.prepareMatrixOffset(mAxisLeft.isInverted()); - } - - @Override - public void notifyDataSetChanged() { - - if (mData == null) { - if (mLogEnabled) - Log.i(LOG_TAG, "Preparing... DATA NOT SET."); - return; - } else { - if (mLogEnabled) - Log.i(LOG_TAG, "Preparing..."); - } - - if (mRenderer != null) - mRenderer.initBuffers(); - - calcMinMax(); - - mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum, mAxisLeft.isInverted()); - mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum, mAxisRight.isInverted()); - mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false); - - if (mLegend != null) - mLegendRenderer.computeLegend(mData); - - calculateOffsets(); - } - - /** - * Performs auto scaling of the axis by recalculating the minimum and maximum y-values based on the entries currently in view. - */ - protected void autoScale() { - - final float fromX = getLowestVisibleX(); - final float toX = getHighestVisibleX(); - - mData.calcMinMaxY(fromX, toX); - - calcMinMax(); - } - - @Override - protected void calcMinMax() { - - mXAxis.calculate(mData.getXMin(), mData.getXMax()); - - // calculate axis range (min / max) according to provided data - mAxisLeft.calculate(mData.getYMin(AxisDependency.LEFT), mData.getYMax(AxisDependency.LEFT)); - mAxisRight.calculate(mData.getYMin(AxisDependency.RIGHT), mData.getYMax(AxisDependency - .RIGHT)); - } - - protected void calculateLegendOffsets(RectF offsets) { - - offsets.left = 0.f; - offsets.right = 0.f; - offsets.top = 0.f; - offsets.bottom = 0.f; - - if (mLegend == null || !mLegend.isEnabled() || mLegend.isDrawInsideEnabled()) - return; - - switch (mLegend.getOrientation()) { - case VERTICAL: - - switch (mLegend.getHorizontalAlignment()) { - case LEFT: - offsets.left += Math.min(mLegend.mNeededWidth, - mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) - + mLegend.getXOffset(); - break; - - case RIGHT: - offsets.right += Math.min(mLegend.mNeededWidth, - mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) - + mLegend.getXOffset(); - break; - - case CENTER: - - switch (mLegend.getVerticalAlignment()) { - case TOP: - offsets.top += Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) - + mLegend.getYOffset(); - break; - - case BOTTOM: - offsets.bottom += Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) - + mLegend.getYOffset(); - break; - - default: - break; - } - } - - break; - - case HORIZONTAL: - - switch (mLegend.getVerticalAlignment()) { - case TOP: - offsets.top += Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) - + mLegend.getYOffset(); - - - break; - - case BOTTOM: - offsets.bottom += Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) - + mLegend.getYOffset(); - - - break; - - default: - break; - } - break; - } - } - - private RectF mOffsetsBuffer = new RectF(); - - @Override - public void calculateOffsets() { - - if (!mCustomViewPortEnabled) { - - float offsetLeft = 0f, offsetRight = 0f, offsetTop = 0f, offsetBottom = 0f; - - calculateLegendOffsets(mOffsetsBuffer); - - offsetLeft += mOffsetsBuffer.left; - offsetTop += mOffsetsBuffer.top; - offsetRight += mOffsetsBuffer.right; - offsetBottom += mOffsetsBuffer.bottom; - - // offsets for y-labels - if (mAxisLeft.needsOffset()) { - offsetLeft += mAxisLeft.getRequiredWidthSpace(mAxisRendererLeft - .getPaintAxisLabels()); - } - - if (mAxisRight.needsOffset()) { - offsetRight += mAxisRight.getRequiredWidthSpace(mAxisRendererRight - .getPaintAxisLabels()); - } - - if (mXAxis.isEnabled() && mXAxis.isDrawLabelsEnabled()) { - - float xLabelHeight = mXAxis.mLabelHeight + mXAxis.getYOffset(); - - // offsets for x-labels - if (mXAxis.getPosition() == XAxisPosition.BOTTOM) { - - offsetBottom += xLabelHeight; - - } else if (mXAxis.getPosition() == XAxisPosition.TOP) { - - offsetTop += xLabelHeight; - - } else if (mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) { - - offsetBottom += xLabelHeight; - offsetTop += xLabelHeight; - } - } - - offsetTop += getExtraTopOffset(); - offsetRight += getExtraRightOffset(); - offsetBottom += getExtraBottomOffset(); - offsetLeft += getExtraLeftOffset(); - - float minOffset = Utils.convertDpToPixel(mMinOffset); - - mViewPortHandler.restrainViewPort( - Math.max(minOffset, offsetLeft), - Math.max(minOffset, offsetTop), - Math.max(minOffset, offsetRight), - Math.max(minOffset, offsetBottom)); - - if (mLogEnabled) { - Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop - + ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom); - Log.i(LOG_TAG, "Content: " + mViewPortHandler.getContentRect().toString()); - } - } - - prepareOffsetMatrix(); - prepareValuePxMatrix(); - } - - /** - * draws the grid background - */ - protected void drawGridBackground(Canvas c) { - - if (mDrawGridBackground) { - - // draw the grid background - c.drawRect(mViewPortHandler.getContentRect(), mGridBackgroundPaint); - } - - if (mDrawBorders) { - c.drawRect(mViewPortHandler.getContentRect(), mBorderPaint); - } - } - - /** - * Returns the Transformer class that contains all matrices and is - * responsible for transforming values into pixels on the screen and - * backwards. - * - * @return - */ - public Transformer getTransformer(AxisDependency which) { - if (which == AxisDependency.LEFT) - return mLeftAxisTransformer; - else - return mRightAxisTransformer; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - super.onTouchEvent(event); - - if (mChartTouchListener == null || mData == null) - return false; - - // check if touch gestures are enabled - if (!mTouchEnabled) - return false; - else - return mChartTouchListener.onTouch(this, event); - } - - @Override - public void computeScroll() { - - if (mChartTouchListener instanceof BarLineChartTouchListener) - ((BarLineChartTouchListener) mChartTouchListener).computeScroll(); - } - - /** - * ################ ################ ################ ################ - */ - /** - * CODE BELOW THIS RELATED TO SCALING AND GESTURES AND MODIFICATION OF THE - * VIEWPORT - */ - - protected Matrix mZoomMatrixBuffer = new Matrix(); - - /** - * Zooms in by 1.4f, into the charts center. - */ - public void zoomIn() { - - MPPointF center = mViewPortHandler.getContentCenter(); - - mViewPortHandler.zoomIn(center.x, -center.y, mZoomMatrixBuffer); - mViewPortHandler.refresh(mZoomMatrixBuffer, this, false); - - MPPointF.recycleInstance(center); - - // Range might have changed, which means that Y-axis labels - // could have changed in size, affecting Y-axis size. - // So we need to recalculate offsets. - calculateOffsets(); - postInvalidate(); - } - - /** - * Zooms out by 0.7f, from the charts center. - */ - public void zoomOut() { - - MPPointF center = mViewPortHandler.getContentCenter(); - - mViewPortHandler.zoomOut(center.x, -center.y, mZoomMatrixBuffer); - mViewPortHandler.refresh(mZoomMatrixBuffer, this, false); - - MPPointF.recycleInstance(center); - - // Range might have changed, which means that Y-axis labels - // could have changed in size, affecting Y-axis size. - // So we need to recalculate offsets. - calculateOffsets(); - postInvalidate(); - } - - /** - * Zooms out to original size. - */ - public void resetZoom() { - - mViewPortHandler.resetZoom(mZoomMatrixBuffer); - mViewPortHandler.refresh(mZoomMatrixBuffer, this, false); - - // Range might have changed, which means that Y-axis labels - // could have changed in size, affecting Y-axis size. - // So we need to recalculate offsets. - calculateOffsets(); - postInvalidate(); - } - - /** - * Zooms in or out by the given scale factor. x and y are the coordinates - * (in pixels) of the zoom center. - * - * @param scaleX if < 1f --> zoom out, if > 1f --> zoom in - * @param scaleY if < 1f --> zoom out, if > 1f --> zoom in - * @param x - * @param y - */ - public void zoom(float scaleX, float scaleY, float x, float y) { - - mViewPortHandler.zoom(scaleX, scaleY, x, -y, mZoomMatrixBuffer); - mViewPortHandler.refresh(mZoomMatrixBuffer, this, false); - - // Range might have changed, which means that Y-axis labels - // could have changed in size, affecting Y-axis size. - // So we need to recalculate offsets. - calculateOffsets(); - postInvalidate(); - } - - /** - * Zooms in or out by the given scale factor. - * x and y are the values (NOT PIXELS) of the zoom center.. - * - * @param scaleX - * @param scaleY - * @param xValue - * @param yValue - * @param axis the axis relative to which the zoom should take place - */ - public void zoom(float scaleX, float scaleY, float xValue, float yValue, AxisDependency axis) { - - Runnable job = ZoomJob.getInstance(mViewPortHandler, scaleX, scaleY, xValue, yValue, getTransformer(axis), axis, this); - addViewportJob(job); - } - - /** - * Zooms to the center of the chart with the given scale factor. - * - * @param scaleX - * @param scaleY - */ - public void zoomToCenter(float scaleX, float scaleY) { - - MPPointF center = getCenterOffsets(); - - Matrix save = mZoomMatrixBuffer; - mViewPortHandler.zoom(scaleX, scaleY, center.x, -center.y, save); - mViewPortHandler.refresh(save, this, false); - } - - /** - * Zooms by the specified scale factor to the specified values on the specified axis. - * - * @param scaleX - * @param scaleY - * @param xValue - * @param yValue - * @param axis - * @param duration - */ - @TargetApi(11) - public void zoomAndCenterAnimated(float scaleX, float scaleY, float xValue, float yValue, AxisDependency axis, - long duration) { - - MPPointD origin = getValuesByTouchPoint(mViewPortHandler.contentLeft(), mViewPortHandler.contentTop(), axis); - - Runnable job = AnimatedZoomJob.getInstance(mViewPortHandler, this, getTransformer(axis), getAxis(axis), mXAxis - .mAxisRange, scaleX, scaleY, mViewPortHandler.getScaleX(), mViewPortHandler.getScaleY(), - xValue, yValue, (float) origin.x, (float) origin.y, duration); - addViewportJob(job); - - MPPointD.recycleInstance(origin); - } - - protected Matrix mFitScreenMatrixBuffer = new Matrix(); - - /** - * Resets all zooming and dragging and makes the chart fit exactly it's - * bounds. - */ - public void fitScreen() { - Matrix save = mFitScreenMatrixBuffer; - mViewPortHandler.fitScreen(save); - mViewPortHandler.refresh(save, this, false); - - calculateOffsets(); - postInvalidate(); - } - - /** - * Sets the minimum scale factor value to which can be zoomed out. 1f = - * fitScreen - * - * @param scaleX - * @param scaleY - */ - public void setScaleMinima(float scaleX, float scaleY) { - mViewPortHandler.setMinimumScaleX(scaleX); - mViewPortHandler.setMinimumScaleY(scaleY); - } - - /** - * Sets the size of the area (range on the x-axis) that should be maximum - * visible at once (no further zooming out allowed). If this is e.g. set to - * 10, no more than a range of 10 on the x-axis can be viewed at once without - * scrolling. - * - * @param maxXRange The maximum visible range of x-values. - */ - public void setVisibleXRangeMaximum(float maxXRange) { - float xScale = mXAxis.mAxisRange / (maxXRange); - mViewPortHandler.setMinimumScaleX(xScale); - } - - /** - * Sets the size of the area (range on the x-axis) that should be minimum - * visible at once (no further zooming in allowed). If this is e.g. set to - * 10, no less than a range of 10 on the x-axis can be viewed at once without - * scrolling. - * - * @param minXRange The minimum visible range of x-values. - */ - public void setVisibleXRangeMinimum(float minXRange) { - float xScale = mXAxis.mAxisRange / (minXRange); - mViewPortHandler.setMaximumScaleX(xScale); - } - - /** - * Limits the maximum and minimum x range that can be visible by pinching and zooming. e.g. minRange=10, maxRange=100 the - * smallest range to be displayed at once is 10, and no more than a range of 100 values can be viewed at once without - * scrolling - * - * @param minXRange - * @param maxXRange - */ - public void setVisibleXRange(float minXRange, float maxXRange) { - float minScale = mXAxis.mAxisRange / minXRange; - float maxScale = mXAxis.mAxisRange / maxXRange; - mViewPortHandler.setMinMaxScaleX(minScale, maxScale); - } - - /** - * Sets the size of the area (range on the y-axis) that should be maximum - * visible at once. - * - * @param maxYRange the maximum visible range on the y-axis - * @param axis the axis for which this limit should apply - */ - public void setVisibleYRangeMaximum(float maxYRange, AxisDependency axis) { - float yScale = getAxisRange(axis) / maxYRange; - mViewPortHandler.setMinimumScaleY(yScale); - } - - /** - * Sets the size of the area (range on the y-axis) that should be minimum visible at once, no further zooming in possible. - * - * @param minYRange - * @param axis the axis for which this limit should apply - */ - public void setVisibleYRangeMinimum(float minYRange, AxisDependency axis) { - float yScale = getAxisRange(axis) / minYRange; - mViewPortHandler.setMaximumScaleY(yScale); - } - - /** - * Limits the maximum and minimum y range that can be visible by pinching and zooming. - * - * @param minYRange - * @param maxYRange - * @param axis - */ - public void setVisibleYRange(float minYRange, float maxYRange, AxisDependency axis) { - float minScale = getAxisRange(axis) / minYRange; - float maxScale = getAxisRange(axis) / maxYRange; - mViewPortHandler.setMinMaxScaleY(minScale, maxScale); - } - - - /** - * Moves the left side of the current viewport to the specified x-position. - * This also refreshes the chart by calling invalidate(). - * - * @param xValue - */ - public void moveViewToX(float xValue) { - - Runnable job = MoveViewJob.getInstance(mViewPortHandler, xValue, 0f, - getTransformer(AxisDependency.LEFT), this); - - addViewportJob(job); - } - - /** - * This will move the left side of the current viewport to the specified - * x-value on the x-axis, and center the viewport to the specified y value on the y-axis. - * This also refreshes the chart by calling invalidate(). - * - * @param xValue - * @param yValue - * @param axis - which axis should be used as a reference for the y-axis - */ - public void moveViewTo(float xValue, float yValue, AxisDependency axis) { - - float yInView = getAxisRange(axis) / mViewPortHandler.getScaleY(); - - Runnable job = MoveViewJob.getInstance(mViewPortHandler, xValue, yValue + yInView / 2f, - getTransformer(axis), this); - - addViewportJob(job); - } - - /** - * This will move the left side of the current viewport to the specified x-value - * and center the viewport to the y value animated. - * This also refreshes the chart by calling invalidate(). - * - * @param xValue - * @param yValue - * @param axis - * @param duration the duration of the animation in milliseconds - */ - @TargetApi(11) - public void moveViewToAnimated(float xValue, float yValue, AxisDependency axis, long duration) { - - MPPointD bounds = getValuesByTouchPoint(mViewPortHandler.contentLeft(), mViewPortHandler.contentTop(), axis); - - float yInView = getAxisRange(axis) / mViewPortHandler.getScaleY(); - - Runnable job = AnimatedMoveViewJob.getInstance(mViewPortHandler, xValue, yValue + yInView / 2f, - getTransformer(axis), this, (float) bounds.x, (float) bounds.y, duration); - - addViewportJob(job); - - MPPointD.recycleInstance(bounds); - } - - /** - * Centers the viewport to the specified y value on the y-axis. - * This also refreshes the chart by calling invalidate(). - * - * @param yValue - * @param axis - which axis should be used as a reference for the y-axis - */ - public void centerViewToY(float yValue, AxisDependency axis) { - - float valsInView = getAxisRange(axis) / mViewPortHandler.getScaleY(); - - Runnable job = MoveViewJob.getInstance(mViewPortHandler, 0f, yValue + valsInView / 2f, - getTransformer(axis), this); - - addViewportJob(job); - } - - /** - * This will move the center of the current viewport to the specified - * x and y value. - * This also refreshes the chart by calling invalidate(). - * - * @param xValue - * @param yValue - * @param axis - which axis should be used as a reference for the y axis - */ - public void centerViewTo(float xValue, float yValue, AxisDependency axis) { - - float yInView = getAxisRange(axis) / mViewPortHandler.getScaleY(); - float xInView = getXAxis().mAxisRange / mViewPortHandler.getScaleX(); - - Runnable job = MoveViewJob.getInstance(mViewPortHandler, - xValue - xInView / 2f, yValue + yInView / 2f, - getTransformer(axis), this); - - addViewportJob(job); - } - - /** - * This will move the center of the current viewport to the specified - * x and y value animated. - * - * @param xValue - * @param yValue - * @param axis - * @param duration the duration of the animation in milliseconds - */ - @TargetApi(11) - public void centerViewToAnimated(float xValue, float yValue, AxisDependency axis, long duration) { - - MPPointD bounds = getValuesByTouchPoint(mViewPortHandler.contentLeft(), mViewPortHandler.contentTop(), axis); - - float yInView = getAxisRange(axis) / mViewPortHandler.getScaleY(); - float xInView = getXAxis().mAxisRange / mViewPortHandler.getScaleX(); - - Runnable job = AnimatedMoveViewJob.getInstance(mViewPortHandler, - xValue - xInView / 2f, yValue + yInView / 2f, - getTransformer(axis), this, (float) bounds.x, (float) bounds.y, duration); - - addViewportJob(job); - - MPPointD.recycleInstance(bounds); - } - - /** - * flag that indicates if a custom viewport offset has been set - */ - private boolean mCustomViewPortEnabled = false; - - /** - * Sets custom offsets for the current ViewPort (the offsets on the sides of - * the actual chart window). Setting this will prevent the chart from - * automatically calculating it's offsets. Use resetViewPortOffsets() to - * undo this. ONLY USE THIS WHEN YOU KNOW WHAT YOU ARE DOING, else use - * setExtraOffsets(...). - * - * @param left - * @param top - * @param right - * @param bottom - */ - public void setViewPortOffsets(final float left, final float top, - final float right, final float bottom) { - - mCustomViewPortEnabled = true; - post(new Runnable() { - - @Override - public void run() { - - mViewPortHandler.restrainViewPort(left, top, right, bottom); - prepareOffsetMatrix(); - prepareValuePxMatrix(); - } - }); - } - - /** - * Resets all custom offsets set via setViewPortOffsets(...) method. Allows - * the chart to again calculate all offsets automatically. - */ - public void resetViewPortOffsets() { - mCustomViewPortEnabled = false; - calculateOffsets(); - } - - /** - * ################ ################ ################ ################ - */ - /** CODE BELOW IS GETTERS AND SETTERS */ - - /** - * Returns the range of the specified axis. - * - * @param axis - * @return - */ - protected float getAxisRange(AxisDependency axis) { - if (axis == AxisDependency.LEFT) - return mAxisLeft.mAxisRange; - else - return mAxisRight.mAxisRange; - } - - /** - * Sets the OnDrawListener - * - * @param drawListener - */ - public void setOnDrawListener(OnDrawListener drawListener) { - this.mDrawListener = drawListener; - } - - /** - * Gets the OnDrawListener. May be null. - * - * @return - */ - public OnDrawListener getDrawListener() { - return mDrawListener; - } - - protected float[] mGetPositionBuffer = new float[2]; - - /** - * Returns a recyclable MPPointF instance. - * Returns the position (in pixels) the provided Entry has inside the chart - * view or null, if the provided Entry is null. - * - * @param e - * @return - */ - public MPPointF getPosition(Entry e, AxisDependency axis) { - - if (e == null) - return null; - - mGetPositionBuffer[0] = e.getX(); - mGetPositionBuffer[1] = e.getY(); - - getTransformer(axis).pointValuesToPixel(mGetPositionBuffer); - - return MPPointF.getInstance(mGetPositionBuffer[0], mGetPositionBuffer[1]); - } - - /** - * sets the number of maximum visible drawn values on the chart only active - * when setDrawValues() is enabled - * - * @param count - */ - public void setMaxVisibleValueCount(int count) { - this.mMaxVisibleCount = count; - } - - public int getMaxVisibleCount() { - return mMaxVisibleCount; - } - - /** - * Set this to true to allow highlighting per dragging over the chart - * surface when it is fully zoomed out. Default: true - * - * @param enabled - */ - public void setHighlightPerDragEnabled(boolean enabled) { - mHighlightPerDragEnabled = enabled; - } - - public boolean isHighlightPerDragEnabled() { - return mHighlightPerDragEnabled; - } - - /** - * Sets the color for the background of the chart-drawing area (everything - * behind the grid lines). - * - * @param color - */ - public void setGridBackgroundColor(int color) { - mGridBackgroundPaint.setColor(color); - } - - /** - * Set this to true to enable dragging (moving the chart with the finger) - * for the chart (this does not effect scaling). - * - * @param enabled - */ - public void setDragEnabled(boolean enabled) { - this.mDragXEnabled = enabled; - this.mDragYEnabled = enabled; - } - - /** - * Returns true if dragging is enabled for the chart, false if not. - * - * @return - */ - public boolean isDragEnabled() { - return mDragXEnabled || mDragYEnabled; - } - - /** - * Set this to true to enable dragging on the X axis - * - * @param enabled - */ - public void setDragXEnabled(boolean enabled) { - this.mDragXEnabled = enabled; - } - - /** - * Returns true if dragging on the X axis is enabled for the chart, false if not. - * - * @return - */ - public boolean isDragXEnabled() { - return mDragXEnabled; - } - - /** - * Set this to true to enable dragging on the Y axis - * - * @param enabled - */ - public void setDragYEnabled(boolean enabled) { - this.mDragYEnabled = enabled; - } - - /** - * Returns true if dragging on the Y axis is enabled for the chart, false if not. - * - * @return - */ - public boolean isDragYEnabled() { - return mDragYEnabled; - } - - /** - * Set this to true to enable scaling (zooming in and out by gesture) for - * the chart (this does not effect dragging) on both X- and Y-Axis. - * - * @param enabled - */ - public void setScaleEnabled(boolean enabled) { - this.mScaleXEnabled = enabled; - this.mScaleYEnabled = enabled; - } - - public void setScaleXEnabled(boolean enabled) { - mScaleXEnabled = enabled; - } - - public void setScaleYEnabled(boolean enabled) { - mScaleYEnabled = enabled; - } - - public boolean isScaleXEnabled() { - return mScaleXEnabled; - } - - public boolean isScaleYEnabled() { - return mScaleYEnabled; - } - - /** - * Set this to true to enable fling gesture for the chart - * - * @param enabled - */ - public void setFlingEnabled(boolean enabled) { this.mFlingEnabled = enabled; } - - /** - * Returns true if fling gesture is enabled for the chart, false if not. - * - * @return - */ - public boolean isFlingEnabled() { - return mFlingEnabled; - } - - /** - * Set this to true to enable zooming in by double-tap on the chart. - * Default: enabled - * - * @param enabled - */ - public void setDoubleTapToZoomEnabled(boolean enabled) { - mDoubleTapToZoomEnabled = enabled; - } - - /** - * Returns true if zooming via double-tap is enabled false if not. - * - * @return - */ - public boolean isDoubleTapToZoomEnabled() { - return mDoubleTapToZoomEnabled; - } - - /** - * set this to true to draw the grid background, false if not - * - * @param enabled - */ - public void setDrawGridBackground(boolean enabled) { - mDrawGridBackground = enabled; - } - - /** - * When enabled, the borders rectangle will be rendered. - * If this is enabled, there is no point drawing the axis-lines of x- and y-axis. - * - * @param enabled - */ - public void setDrawBorders(boolean enabled) { - mDrawBorders = enabled; - } - - /** - * When enabled, the borders rectangle will be rendered. - * If this is enabled, there is no point drawing the axis-lines of x- and y-axis. - * - * @return - */ - public boolean isDrawBordersEnabled() { - return mDrawBorders; - } - - /** - * When enabled, the values will be clipped to contentRect, - * otherwise they can bleed outside the content rect. - * - * @param enabled - */ - public void setClipValuesToContent(boolean enabled) { - mClipValuesToContent = enabled; - } - - /** - * When disabled, the data and/or highlights will not be clipped to contentRect. Disabling this option can - * be useful, when the data lies fully within the content rect, but is drawn in such a way (such as thick lines) - * that there is unwanted clipping. - * - * @param enabled - */ - public void setClipDataToContent(boolean enabled) { - mClipDataToContent = enabled; - } - - /** - * When enabled, the values will be clipped to contentRect, - * otherwise they can bleed outside the content rect. - * - * @return - */ - public boolean isClipValuesToContentEnabled() { - return mClipValuesToContent; - } - - /** - * When disabled, the data and/or highlights will not be clipped to contentRect. Disabling this option can - * be useful, when the data lies fully within the content rect, but is drawn in such a way (such as thick lines) - * that there is unwanted clipping. - * - * @return - */ - public boolean isClipDataToContentEnabled() { - return mClipDataToContent; - } - - /** - * Sets the width of the border lines in dp. - * - * @param width - */ - public void setBorderWidth(float width) { - mBorderPaint.setStrokeWidth(Utils.convertDpToPixel(width)); - } - - /** - * Sets the color of the chart border lines. - * - * @param color - */ - public void setBorderColor(int color) { - mBorderPaint.setColor(color); - } - - /** - * Gets the minimum offset (padding) around the chart, defaults to 15.f - */ - public float getMinOffset() { - return mMinOffset; - } - - /** - * Sets the minimum offset (padding) around the chart, defaults to 15.f - */ - public void setMinOffset(float minOffset) { - mMinOffset = minOffset; - } - - /** - * Returns true if keeping the position on rotation is enabled and false if not. - */ - public boolean isKeepPositionOnRotation() { - return mKeepPositionOnRotation; - } - - /** - * Sets whether the chart should keep its position (zoom / scroll) after a rotation (orientation change) - */ - public void setKeepPositionOnRotation(boolean keepPositionOnRotation) { - mKeepPositionOnRotation = keepPositionOnRotation; - } - - /** - * Returns a recyclable MPPointD instance - * Returns the x and y values in the chart at the given touch point - * (encapsulated in a MPPointD). This method transforms pixel coordinates to - * coordinates / values in the chart. This is the opposite method to - * getPixelForValues(...). - * - * @param x - * @param y - * @return - */ - public MPPointD getValuesByTouchPoint(float x, float y, AxisDependency axis) { - MPPointD result = MPPointD.getInstance(0, 0); - getValuesByTouchPoint(x, y, axis, result); - return result; - } - - public void getValuesByTouchPoint(float x, float y, AxisDependency axis, MPPointD outputPoint) { - getTransformer(axis).getValuesByTouchPoint(x, y, outputPoint); - } - - /** - * Returns a recyclable MPPointD instance - * Transforms the given chart values into pixels. This is the opposite - * method to getValuesByTouchPoint(...). - * - * @param x - * @param y - * @return - */ - public MPPointD getPixelForValues(float x, float y, AxisDependency axis) { - return getTransformer(axis).getPixelForValues(x, y); - } - - /** - * returns the Entry object displayed at the touched position of the chart - * - * @param x - * @param y - * @return - */ - public Entry getEntryByTouchPoint(float x, float y) { - Highlight h = getHighlightByTouchPoint(x, y); - if (h != null) { - return mData.getEntryForHighlight(h); - } - return null; - } - - /** - * returns the DataSet object displayed at the touched position of the chart - * - * @param x - * @param y - * @return - */ - public IBarLineScatterCandleBubbleDataSet getDataSetByTouchPoint(float x, float y) { - Highlight h = getHighlightByTouchPoint(x, y); - if (h != null) { - return mData.getDataSetByIndex(h.getDataSetIndex()); - } - return null; - } - - /** - * buffer for storing lowest visible x point - */ - protected MPPointD posForGetLowestVisibleX = MPPointD.getInstance(0, 0); - - /** - * Returns the lowest x-index (value on the x-axis) that is still visible on - * the chart. - * - * @return - */ - @Override - public float getLowestVisibleX() { - getTransformer(AxisDependency.LEFT).getValuesByTouchPoint(mViewPortHandler.contentLeft(), - mViewPortHandler.contentBottom(), posForGetLowestVisibleX); - float result = (float) Math.max(mXAxis.mAxisMinimum, posForGetLowestVisibleX.x); - return result; - } - - /** - * buffer for storing highest visible x point - */ - protected MPPointD posForGetHighestVisibleX = MPPointD.getInstance(0, 0); - - /** - * Returns the highest x-index (value on the x-axis) that is still visible - * on the chart. - * - * @return - */ - @Override - public float getHighestVisibleX() { - getTransformer(AxisDependency.LEFT).getValuesByTouchPoint(mViewPortHandler.contentRight(), - mViewPortHandler.contentBottom(), posForGetHighestVisibleX); - float result = (float) Math.min(mXAxis.mAxisMaximum, posForGetHighestVisibleX.x); - return result; - } - - /** - * Returns the range visible on the x-axis. - * - * @return - */ - public float getVisibleXRange() { - return Math.abs(getHighestVisibleX() - getLowestVisibleX()); - } - - /** - * returns the current x-scale factor - */ - public float getScaleX() { - if (mViewPortHandler == null) - return 1f; - else - return mViewPortHandler.getScaleX(); - } - - /** - * returns the current y-scale factor - */ - public float getScaleY() { - if (mViewPortHandler == null) - return 1f; - else - return mViewPortHandler.getScaleY(); - } - - /** - * if the chart is fully zoomed out, return true - * - * @return - */ - public boolean isFullyZoomedOut() { - return mViewPortHandler.isFullyZoomedOut(); - } - - /** - * Returns the left y-axis object. In the horizontal bar-chart, this is the - * top axis. - * - * @return - */ - public YAxis getAxisLeft() { - return mAxisLeft; - } - - /** - * Returns the right y-axis object. In the horizontal bar-chart, this is the - * bottom axis. - * - * @return - */ - public YAxis getAxisRight() { - return mAxisRight; - } - - /** - * Returns the y-axis object to the corresponding AxisDependency. In the - * horizontal bar-chart, LEFT == top, RIGHT == BOTTOM - * - * @param axis - * @return - */ - public YAxis getAxis(AxisDependency axis) { - if (axis == AxisDependency.LEFT) - return mAxisLeft; - else - return mAxisRight; - } - - @Override - public boolean isInverted(AxisDependency axis) { - return getAxis(axis).isInverted(); - } - - /** - * If set to true, both x and y axis can be scaled simultaneously with 2 fingers, if false, - * x and y axis can be scaled separately. default: false - * - * @param enabled - */ - public void setPinchZoom(boolean enabled) { - mPinchZoomEnabled = enabled; - } - - /** - * returns true if pinch-zoom is enabled, false if not - * - * @return - */ - public boolean isPinchZoomEnabled() { - return mPinchZoomEnabled; - } - - /** - * Set an offset in dp that allows the user to drag the chart over it's - * bounds on the x-axis. - * - * @param offset - */ - public void setDragOffsetX(float offset) { - mViewPortHandler.setDragOffsetX(offset); - } - - /** - * Set an offset in dp that allows the user to drag the chart over it's - * bounds on the y-axis. - * - * @param offset - */ - public void setDragOffsetY(float offset) { - mViewPortHandler.setDragOffsetY(offset); - } - - /** - * Returns true if both drag offsets (x and y) are zero or smaller. - * - * @return - */ - public boolean hasNoDragOffset() { - return mViewPortHandler.hasNoDragOffset(); - } - - public XAxisRenderer getRendererXAxis() { - return mXAxisRenderer; - } - - /** - * Sets a custom XAxisRenderer and overrides the existing (default) one. - * - * @param xAxisRenderer - */ - public void setXAxisRenderer(XAxisRenderer xAxisRenderer) { - mXAxisRenderer = xAxisRenderer; - } - - public YAxisRenderer getRendererLeftYAxis() { - return mAxisRendererLeft; - } - - /** - * Sets a custom axis renderer for the left axis and overwrites the existing one. - * - * @param rendererLeftYAxis - */ - public void setRendererLeftYAxis(YAxisRenderer rendererLeftYAxis) { - mAxisRendererLeft = rendererLeftYAxis; - } - - public YAxisRenderer getRendererRightYAxis() { - return mAxisRendererRight; - } - - /** - * Sets a custom axis renderer for the right acis and overwrites the existing one. - * - * @param rendererRightYAxis - */ - public void setRendererRightYAxis(YAxisRenderer rendererRightYAxis) { - mAxisRendererRight = rendererRightYAxis; - } - - @Override - public float getYChartMax() { - return Math.max(mAxisLeft.mAxisMaximum, mAxisRight.mAxisMaximum); - } - - @Override - public float getYChartMin() { - return Math.min(mAxisLeft.mAxisMinimum, mAxisRight.mAxisMinimum); - } - - /** - * Returns true if either the left or the right or both axes are inverted. - * - * @return - */ - public boolean isAnyAxisInverted() { - if (mAxisLeft.isInverted()) - return true; - if (mAxisRight.isInverted()) - return true; - return false; - } - - /** - * Flag that indicates if auto scaling on the y axis is enabled. This is - * especially interesting for charts displaying financial data. - * - * @param enabled the y axis automatically adjusts to the min and max y - * values of the current x axis range whenever the viewport - * changes - */ - public void setAutoScaleMinMaxEnabled(boolean enabled) { - mAutoScaleMinMaxEnabled = enabled; - } - - /** - * @return true if auto scaling on the y axis is enabled. - * @default false - */ - public boolean isAutoScaleMinMaxEnabled() { - return mAutoScaleMinMaxEnabled; - } - - @Override - public void setPaint(Paint p, int which) { - super.setPaint(p, which); - - switch (which) { - case PAINT_GRID_BACKGROUND: - mGridBackgroundPaint = p; - break; - } - } - - @Override - public Paint getPaint(int which) { - Paint p = super.getPaint(which); - if (p != null) - return p; - - switch (which) { - case PAINT_GRID_BACKGROUND: - return mGridBackgroundPaint; - } - - return null; - } - - protected float[] mOnSizeChangedBuffer = new float[2]; - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - - // Saving current position of chart. - mOnSizeChangedBuffer[0] = mOnSizeChangedBuffer[1] = 0; - - if (mKeepPositionOnRotation) { - mOnSizeChangedBuffer[0] = mViewPortHandler.contentLeft(); - mOnSizeChangedBuffer[1] = mViewPortHandler.contentTop(); - getTransformer(AxisDependency.LEFT).pixelsToValue(mOnSizeChangedBuffer); - } - - //Superclass transforms chart. - super.onSizeChanged(w, h, oldw, oldh); - - if (mKeepPositionOnRotation) { - - //Restoring old position of chart. - getTransformer(AxisDependency.LEFT).pointValuesToPixel(mOnSizeChangedBuffer); - mViewPortHandler.centerViewPort(mOnSizeChangedBuffer, this); - } else { - mViewPortHandler.refresh(mViewPortHandler.getMatrixTouch(), this, true); - } - } - - /** - * Sets the text color to use for the labels. Make sure to use - * getResources().getColor(...) when using a color from the resources. - * - * @param color - */ - public void setTextColor(int color) { - mAxisRendererLeft.setTextColor(color); - mAxisRendererRight.setTextColor(color); - mXAxisRenderer.setTextColor(color); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.kt new file mode 100644 index 0000000000..378790ce0b --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BarLineChartBase.kt @@ -0,0 +1,1456 @@ +package com.github.mikephil.charting.charts + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Matrix +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.util.Log +import android.view.MotionEvent +import com.github.mikephil.charting.components.Legend.LegendHorizontalAlignment +import com.github.mikephil.charting.components.Legend.LegendOrientation +import com.github.mikephil.charting.components.Legend.LegendVerticalAlignment +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.components.YAxis +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.ChartHighlighter +import com.github.mikephil.charting.interfaces.dataprovider.BarLineScatterCandleBubbleDataProvider +import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet +import com.github.mikephil.charting.jobs.AnimatedMoveViewJob +import com.github.mikephil.charting.jobs.AnimatedZoomJob +import com.github.mikephil.charting.jobs.MoveViewJob +import com.github.mikephil.charting.jobs.ZoomJob +import com.github.mikephil.charting.listener.BarLineChartTouchListener +import com.github.mikephil.charting.listener.OnDrawListener +import com.github.mikephil.charting.renderer.XAxisRenderer +import com.github.mikephil.charting.renderer.YAxisRenderer +import com.github.mikephil.charting.utils.MPPointD +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Transformer +import com.github.mikephil.charting.utils.Utils +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * Base-class of LineChart, BarChart, ScatterChart and CandleStickChart. + * + * @author Philipp Jahoda + */ +@SuppressLint("RtlHardcoded") +abstract class BarLineChartBase, T : BarLineScatterCandleBubbleData> + : Chart, BarLineScatterCandleBubbleDataProvider { + /** + * the maximum number of entries to which values will be drawn + * (entry numbers greater than this value will cause value-labels to disappear) + */ + protected var mMaxVisibleCount: Int = 100 + + /** + * @return true if auto scaling on the y axis is enabled. + * @default false + */ + /** + * Flag that indicates if auto scaling on the y axis is enabled. This is + * especially interesting for charts displaying financial data. + * + * @param enabled the y axis automatically adjusts to the min and max y + * values of the current x axis range whenever the viewport + * changes + */ + /** + * flag that indicates if auto scaling on the y axis is enabled + */ + var isAutoScaleMinMaxEnabled: Boolean = false + + /** + * returns true if pinch-zoom is enabled, false if not + * + * @return + */ + /** + * flag that indicates if pinch-zoom is enabled. if true, both x and y axis + * can be scaled with 2 fingers, if false, x and y axis can be scaled + * separately + */ + var isPinchZoomEnabled: Boolean = false + protected set + + /** + * Returns true if zooming via double-tap is enabled false if not. + * + * @return + */ + /** + * Set this to true to enable zooming in by double-tap on the chart. + * Default: enabled + * + * @param enabled + */ + /** + * flag that indicates if double tap zoom is enabled or not + */ + var isDoubleTapToZoomEnabled: Boolean = true + + /** + * Set this to true to allow highlighting per dragging over the chart + * surface when it is fully zoomed out. Default: true + * + * @param enabled + */ + /** + * flag that indicates if highlighting per dragging over a fully zoomed out + * chart is enabled + */ + var isHighlightPerDragEnabled: Boolean = true + + /** + * Returns true if dragging on the X axis is enabled for the chart, false if not. + * + * @return + */ + /** + * Set this to true to enable dragging on the X axis + * + * @param enabled + */ + /** + * if true, dragging is enabled for the chart + */ + var isDragXEnabled: Boolean = true + /** + * Returns true if dragging on the Y axis is enabled for the chart, false if not. + * + * @return + */ + /** + * Set this to true to enable dragging on the Y axis + * + * @param enabled + */ + var isDragYEnabled: Boolean = true + + var isScaleXEnabled: Boolean = true + var isScaleYEnabled: Boolean = true + + /** + * Returns true if fling gesture is enabled for the chart, false if not. + * + * @return + */ + /** + * Set this to true to enable fling gesture for the chart + * + * @param enabled + */ + /** + * if true, fling gesture is enabled for the chart + */ + var isFlingEnabled: Boolean = false + + /** + * paint object for the (by default) lightgrey background of the grid + */ + protected var mGridBackgroundPaint: Paint = Paint() + + protected val mBorderPaint: Paint = Paint() + + /** + * flag indicating if the grid background should be drawn or not + */ + var drawGridBackground: Boolean = false + + /** + * When enabled, the borders rectangle will be rendered. + * If this is enabled, there is no point drawing the axis-lines of x- and y-axis. + * + * @return + */ + var isDrawBordersEnabled: Boolean = false + + /** + * When enabled, the values will be clipped to contentRect, + * otherwise they can bleed outside the content rect. + * + * @return + */ + var isClipValuesToContentEnabled: Boolean = false + + /** + * When disabled, the data and/or highlights will not be clipped to contentRect. Disabling this option can + * be useful, when the data lies fully within the content rect, but is drawn in such a way (such as thick lines) + * that there is unwanted clipping. + * + * @return + */ + var isClipDataToContentEnabled: Boolean = true + + /** + * Gets the minimum offset (padding) around the chart, defaults to 15.f + */ + /** + * Sets the minimum offset (padding) around the chart, defaults to 15.f + */ + /** + * Sets the minimum offset (padding) around the chart, defaults to 15 + */ + var minOffset: Float = 15f + + /** + * Returns true if keeping the position on rotation is enabled and false if not. + */ + /** + * Sets whether the chart should keep its position (zoom / scroll) after a rotation (orientation change) + */ + /** + * flag indicating if the chart should stay at the same position after a rotation. Default is false. + */ + var isKeepPositionOnRotation: Boolean = false + + /** + * Gets the OnDrawListener. May be null. + * + * @return + */ + /** + * the listener for user drawing on the chart + */ + var drawListener: OnDrawListener? = null + protected set + + /** + * the object representing the labels on the left y-axis + */ + protected val mAxisLeft: YAxis = YAxis(AxisDependency.LEFT) + + /** + * the object representing the labels on the right y-axis + */ + protected val mAxisRight: YAxis = YAxis(AxisDependency.RIGHT) + + protected var mLeftAxisTransformer: Transformer = Transformer(viewPortHandler) + protected var mRightAxisTransformer: Transformer = Transformer(viewPortHandler) + + protected var mAxisRendererLeft: YAxisRenderer = YAxisRenderer(viewPortHandler, mAxisLeft, mLeftAxisTransformer) + protected var mAxisRendererRight: YAxisRenderer = YAxisRenderer(viewPortHandler, mAxisRight, mRightAxisTransformer) + + protected var mXAxisRenderer: XAxisRenderer = XAxisRenderer(viewPortHandler, mXAxis, mLeftAxisTransformer) + + // /** the approximator object used for data filtering */ + // private Approximator mApproximator; + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context) : super(context) + + init { + setHighlighter(ChartHighlighter(this)) + + mChartTouchListener = BarLineChartTouchListener(this, viewPortHandler.matrixTouch, 3f) + + mGridBackgroundPaint.style = Paint.Style.FILL + // mGridBackgroundPaint.setColor(Color.WHITE); + mGridBackgroundPaint.setColor(Color.rgb(240, 240, 240)) // light + + // grey + mBorderPaint.style = Paint.Style.STROKE + mBorderPaint.setColor(Color.BLACK) + mBorderPaint.strokeWidth = Utils.convertDpToPixel(1f) + } + + // for performance tracking + private var totalTime: Long = 0 + private var drawCycles: Long = 0 + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (mData == null) return + + val starttime = System.currentTimeMillis() + + // execute all drawing commands + drawGridBackground(canvas) + + if (this.isAutoScaleMinMaxEnabled) { + autoScale() + } + + if (mAxisLeft.isEnabled) mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum, mAxisLeft.isInverted) + + if (mAxisRight.isEnabled) mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum, mAxisRight.isInverted) + + if (mXAxis.isEnabled) mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false) + + // Y-axis labels could have changed in size affecting the offsets + if (this.isAutoScaleMinMaxEnabled) { + calculateOffsets() + viewPortHandler.refresh(viewPortHandler.matrixTouch, this, false) + } + + mXAxisRenderer.renderAxisLine(canvas) + mAxisRendererLeft.renderAxisLine(canvas) + mAxisRendererRight.renderAxisLine(canvas) + + if (mXAxis.isDrawGridLinesBehindDataEnabled) mXAxisRenderer.renderGridLines(canvas) + + if (mAxisLeft.isDrawGridLinesBehindDataEnabled) mAxisRendererLeft.renderGridLines(canvas) + + if (mAxisRight.isDrawGridLinesBehindDataEnabled) mAxisRendererRight.renderGridLines(canvas) + + if (mXAxis.isEnabled && mXAxis.isDrawLimitLinesBehindDataEnabled) mXAxisRenderer.renderLimitLines(canvas) + + if (mAxisLeft.isEnabled && mAxisLeft.isDrawLimitLinesBehindDataEnabled) mAxisRendererLeft.renderLimitLines(canvas) + + if (mAxisRight.isEnabled && mAxisRight.isDrawLimitLinesBehindDataEnabled) mAxisRendererRight.renderLimitLines(canvas) + + var clipRestoreCount = canvas.save() + + if (this.isClipDataToContentEnabled) { + // make sure the data cannot be drawn outside the content-rect + canvas.clipRect(viewPortHandler.contentRect) + } + + mRenderer?.drawData(canvas) + + if (!mXAxis.isDrawGridLinesBehindDataEnabled) mXAxisRenderer.renderGridLines(canvas) + + if (!mAxisLeft.isDrawGridLinesBehindDataEnabled) mAxisRendererLeft.renderGridLines(canvas) + + if (!mAxisRight.isDrawGridLinesBehindDataEnabled) mAxisRendererRight.renderGridLines(canvas) + + // if highlighting is enabled + if (valuesToHighlight()) mRenderer?.drawHighlighted(canvas, highlighted!!) + + // Removes clipping rectangle + canvas.restoreToCount(clipRestoreCount) + + mRenderer?.drawExtras(canvas) + + if (mXAxis.isEnabled && !mXAxis.isDrawLimitLinesBehindDataEnabled) mXAxisRenderer.renderLimitLines(canvas) + + if (mAxisLeft.isEnabled && !mAxisLeft.isDrawLimitLinesBehindDataEnabled) mAxisRendererLeft.renderLimitLines(canvas) + + if (mAxisRight.isEnabled && !mAxisRight.isDrawLimitLinesBehindDataEnabled) mAxisRendererRight.renderLimitLines(canvas) + + mXAxisRenderer.renderAxisLabels(canvas) + mAxisRendererLeft.renderAxisLabels(canvas) + mAxisRendererRight.renderAxisLabels(canvas) + + if (this.isClipValuesToContentEnabled) { + clipRestoreCount = canvas.save() + canvas.clipRect(viewPortHandler.contentRect) + + mRenderer?.drawValues(canvas) + + canvas.restoreToCount(clipRestoreCount) + } else { + mRenderer?.drawValues(canvas) + } + + legendRenderer.renderLegend(canvas) + + drawDescription(canvas) + + drawMarkers(canvas) + + if (isLogEnabled) { + val drawtime = (System.currentTimeMillis() - starttime) + totalTime += drawtime + drawCycles += 1 + val average = totalTime / drawCycles + Log.i( + LOG_TAG, ("Drawtime: " + drawtime + " ms, average: " + average + " ms, cycles: " + + drawCycles) + ) + } + } + + /** + * RESET PERFORMANCE TRACKING FIELDS + */ + fun resetTracking() { + totalTime = 0 + drawCycles = 0 + } + + protected open fun prepareValuePxMatrix() { + if (isLogEnabled) Log.i( + LOG_TAG, ("Preparing Value-Px Matrix, xmin: " + mXAxis.mAxisMinimum + ", xmax: " + + mXAxis.mAxisMaximum + ", xdelta: " + mXAxis.mAxisRange) + ) + + mRightAxisTransformer.prepareMatrixValuePx( + mXAxis.mAxisMinimum, + mXAxis.mAxisRange, + mAxisRight.mAxisRange, + mAxisRight.mAxisMinimum + ) + mLeftAxisTransformer.prepareMatrixValuePx( + mXAxis.mAxisMinimum, + mXAxis.mAxisRange, + mAxisLeft.mAxisRange, + mAxisLeft.mAxisMinimum + ) + } + + protected fun prepareOffsetMatrix() { + mRightAxisTransformer.prepareMatrixOffset(mAxisRight.isInverted) + mLeftAxisTransformer.prepareMatrixOffset(mAxisLeft.isInverted) + } + + override fun notifyDataSetChanged() { + if (mData == null) { + if (isLogEnabled) Log.i(LOG_TAG, "Preparing... DATA NOT SET.") + return + } else { + if (isLogEnabled) Log.i(LOG_TAG, "Preparing...") + } + + mRenderer?.initBuffers() + + calcMinMax() + + mAxisRendererLeft.computeAxis(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisMaximum, mAxisLeft.isInverted) + mAxisRendererRight.computeAxis(mAxisRight.mAxisMinimum, mAxisRight.mAxisMaximum, mAxisRight.isInverted) + mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false) + + legendRenderer.computeLegend(mData!!) + + calculateOffsets() + } + + /** + * Performs auto scaling of the axis by recalculating the minimum and maximum y-values based on the entries currently in view. + */ + protected fun autoScale() { + val fromX = lowestVisibleX + val toX = highestVisibleX + + mData?.calcMinMaxY(fromX, toX) + + calcMinMax() + } + + override fun calcMinMax() { + mData?.let { mData -> + mXAxis.calculate(mData.xMin, mData.xMax) + + // calculate axis range (min / max) according to provided data + mAxisLeft.calculate(mData.getYMin(AxisDependency.LEFT), mData.getYMax(AxisDependency.LEFT)) + mAxisRight.calculate(mData.getYMin(AxisDependency.RIGHT), mData.getYMax(AxisDependency.RIGHT)) + } + } + + protected open fun calculateLegendOffsets(offsets: RectF) { + offsets.left = 0f + offsets.right = 0f + offsets.top = 0f + offsets.bottom = 0f + + if (!legend.isEnabled || legend.isDrawInsideEnabled) return + + when (legend.orientation) { + LegendOrientation.VERTICAL -> when (legend.horizontalAlignment) { + LegendHorizontalAlignment.LEFT -> offsets.left += min( + legend.mNeededWidth, + viewPortHandler.chartWidth * legend.maxSizePercent + ) + legend.xOffset + + LegendHorizontalAlignment.RIGHT -> offsets.right += min( + legend.mNeededWidth, + viewPortHandler.chartWidth * legend.maxSizePercent + ) + legend.xOffset + + LegendHorizontalAlignment.CENTER -> when (legend.verticalAlignment) { + LegendVerticalAlignment.TOP -> offsets.top += min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + legend.yOffset + + LegendVerticalAlignment.BOTTOM -> offsets.bottom += min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + legend.yOffset + + else -> {} + } + } + + LegendOrientation.HORIZONTAL -> when (legend.verticalAlignment) { + LegendVerticalAlignment.TOP -> offsets.top += min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + legend.yOffset + + + LegendVerticalAlignment.BOTTOM -> offsets.bottom += min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + legend.yOffset + + + else -> {} + } + } + } + + private val mOffsetsBuffer = RectF() + + public override fun calculateOffsets() { + if (!mCustomViewPortEnabled) { + var offsetLeft = 0f + var offsetRight = 0f + var offsetTop = 0f + var offsetBottom = 0f + + calculateLegendOffsets(mOffsetsBuffer) + + offsetLeft += mOffsetsBuffer.left + offsetTop += mOffsetsBuffer.top + offsetRight += mOffsetsBuffer.right + offsetBottom += mOffsetsBuffer.bottom + + // offsets for y-labels + if (mAxisLeft.needsOffset()) { + offsetLeft += mAxisLeft.getRequiredWidthSpace( + mAxisRendererLeft + .paintAxisLabels + ) + } + + if (mAxisRight.needsOffset()) { + offsetRight += mAxisRight.getRequiredWidthSpace( + mAxisRendererRight + .paintAxisLabels + ) + } + + if (mXAxis.isEnabled && mXAxis.isDrawLabelsEnabled) { + val xLabelHeight = mXAxis.mLabelHeight + mXAxis.yOffset + + // offsets for x-labels + when (mXAxis.position) { + XAxisPosition.BOTTOM -> { + offsetBottom += xLabelHeight + } + XAxisPosition.TOP -> { + offsetTop += xLabelHeight + } + XAxisPosition.BOTH_SIDED -> { + offsetBottom += xLabelHeight + offsetTop += xLabelHeight + } + else -> {} + } + } + + offsetTop += extraTopOffset + offsetRight += extraRightOffset + offsetBottom += extraBottomOffset + offsetLeft += extraLeftOffset + + val minOffset = Utils.convertDpToPixel(this.minOffset) + + viewPortHandler.restrainViewPort( + max(minOffset, offsetLeft), + max(minOffset, offsetTop), + max(minOffset, offsetRight), + max(minOffset, offsetBottom) + ) + + if (isLogEnabled) { + Log.i( + LOG_TAG, ("offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop + + ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom) + ) + Log.i(LOG_TAG, "Content: " + viewPortHandler.contentRect.toString()) + } + } + + prepareOffsetMatrix() + prepareValuePxMatrix() + } + + /** + * draws the grid background + */ + protected fun drawGridBackground(c: Canvas) { + if (drawGridBackground) { + // draw the grid background + + c.drawRect(viewPortHandler.contentRect, mGridBackgroundPaint) + } + + if (this.isDrawBordersEnabled) { + c.drawRect(viewPortHandler.contentRect, mBorderPaint) + } + } + + /** + * Returns the Transformer class that contains all matrices and is + * responsible for transforming values into pixels on the screen and + * backwards. + * + * @return + */ + override fun getTransformer(axis: AxisDependency?): Transformer { + return if (axis == AxisDependency.LEFT) mLeftAxisTransformer + else mRightAxisTransformer + } + + override fun onTouchEvent(event: MotionEvent?): Boolean { + super.onTouchEvent(event) + + if (mChartTouchListener == null || mData == null) return false + + // check if touch gestures are enabled + return if (!mTouchEnabled) false + else mChartTouchListener?.onTouch(this, event) == true + } + + override fun computeScroll() { + (mChartTouchListener as? BarLineChartTouchListener)?.computeScroll() + } + + /** + * ################ ################ ################ ################ + */ + /** + * CODE BELOW THIS RELATED TO SCALING AND GESTURES AND MODIFICATION OF THE + * VIEWPORT + */ + protected var mZoomMatrixBuffer: Matrix = Matrix() + + /** + * Zooms in by 1.4f, into the charts center. + */ + fun zoomIn() { + val center = viewPortHandler.contentCenter + + viewPortHandler.zoomIn(center.x, -center.y, mZoomMatrixBuffer) + viewPortHandler.refresh(mZoomMatrixBuffer, this, false) + + MPPointF.Companion.recycleInstance(center) + + // Range might have changed, which means that Y-axis labels + // could have changed in size, affecting Y-axis size. + // So we need to recalculate offsets. + calculateOffsets() + postInvalidate() + } + + /** + * Zooms out by 0.7f, from the charts center. + */ + fun zoomOut() { + val center = viewPortHandler.contentCenter + + viewPortHandler.zoomOut(center.x, -center.y, mZoomMatrixBuffer) + viewPortHandler.refresh(mZoomMatrixBuffer, this, false) + + MPPointF.Companion.recycleInstance(center) + + // Range might have changed, which means that Y-axis labels + // could have changed in size, affecting Y-axis size. + // So we need to recalculate offsets. + calculateOffsets() + postInvalidate() + } + + /** + * Zooms out to original size. + */ + fun resetZoom() { + viewPortHandler.resetZoom(mZoomMatrixBuffer) + viewPortHandler.refresh(mZoomMatrixBuffer, this, false) + + // Range might have changed, which means that Y-axis labels + // could have changed in size, affecting Y-axis size. + // So we need to recalculate offsets. + calculateOffsets() + postInvalidate() + } + + /** + * Zooms in or out by the given scale factor. x and y are the coordinates + * (in pixels) of the zoom center. + * + * @param scaleX if < 1f --> zoom out, if > 1f --> zoom in + * @param scaleY if < 1f --> zoom out, if > 1f --> zoom in + * @param x + * @param y + */ + fun zoom(scaleX: Float, scaleY: Float, x: Float, y: Float) { + viewPortHandler.zoom(scaleX, scaleY, x, -y, mZoomMatrixBuffer) + viewPortHandler.refresh(mZoomMatrixBuffer, this, false) + + // Range might have changed, which means that Y-axis labels + // could have changed in size, affecting Y-axis size. + // So we need to recalculate offsets. + calculateOffsets() + postInvalidate() + } + + /** + * Zooms in or out by the given scale factor. + * x and y are the values (NOT PIXELS) of the zoom center.. + * + * @param scaleX + * @param scaleY + * @param xValue + * @param yValue + * @param axis the axis relative to which the zoom should take place + */ + fun zoom(scaleX: Float, scaleY: Float, xValue: Float, yValue: Float, axis: AxisDependency?) { + val job: Runnable = ZoomJob.Companion.getInstance(viewPortHandler, scaleX, scaleY, xValue, yValue, getTransformer(axis), axis, this) + addViewportJob(job) + } + + /** + * Zooms to the center of the chart with the given scale factor. + * + * @param scaleX + * @param scaleY + */ + fun zoomToCenter(scaleX: Float, scaleY: Float) { + val center = centerOffsets + + val save = mZoomMatrixBuffer + viewPortHandler.zoom(scaleX, scaleY, center.x, -center.y, save) + viewPortHandler.refresh(save, this, false) + } + + /** + * Zooms by the specified scale factor to the specified values on the specified axis. + * + * @param scaleX + * @param scaleY + * @param xValue + * @param yValue + * @param axis + * @param duration + */ + fun zoomAndCenterAnimated( + scaleX: Float, scaleY: Float, xValue: Float, yValue: Float, axis: AxisDependency?, + duration: Long + ) { + val origin = getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentTop(), axis) + + val job: Runnable = AnimatedZoomJob.Companion.getInstance( + viewPortHandler, this, getTransformer(axis), getAxis(axis), mXAxis + .mAxisRange, scaleX, scaleY, viewPortHandler.scaleX, viewPortHandler.scaleY, + xValue, yValue, origin.x.toFloat(), origin.y.toFloat(), duration + ) + addViewportJob(job) + + MPPointD.Companion.recycleInstance(origin) + } + + protected var mFitScreenMatrixBuffer: Matrix = Matrix() + + /** + * Resets all zooming and dragging and makes the chart fit exactly it's + * bounds. + */ + fun fitScreen() { + val save = mFitScreenMatrixBuffer + viewPortHandler.fitScreen(save) + viewPortHandler.refresh(save, this, false) + + calculateOffsets() + postInvalidate() + } + + /** + * Sets the minimum scale factor value to which can be zoomed out. 1f = + * fitScreen + * + * @param scaleX + * @param scaleY + */ + fun setScaleMinima(scaleX: Float, scaleY: Float) { + viewPortHandler.setMinimumScaleX(scaleX) + viewPortHandler.setMinimumScaleY(scaleY) + } + + /** + * Sets the size of the area (range on the x-axis) that should be maximum + * visible at once (no further zooming out allowed). If this is e.g. set to + * 10, no more than a range of 10 on the x-axis can be viewed at once without + * scrolling. + * + * @param maxXRange The maximum visible range of x-values. + */ + open fun setVisibleXRangeMaximum(maxXRange: Float) { + val xScale = mXAxis.mAxisRange / (maxXRange) + viewPortHandler.setMinimumScaleX(xScale) + } + + /** + * Sets the size of the area (range on the x-axis) that should be minimum + * visible at once (no further zooming in allowed). If this is e.g. set to + * 10, no less than a range of 10 on the x-axis can be viewed at once without + * scrolling. + * + * @param minXRange The minimum visible range of x-values. + */ + open fun setVisibleXRangeMinimum(minXRange: Float) { + val xScale = mXAxis.mAxisRange / (minXRange) + viewPortHandler.setMaximumScaleX(xScale) + } + + /** + * Limits the maximum and minimum x range that can be visible by pinching and zooming. e.g. minRange=10, maxRange=100 the + * smallest range to be displayed at once is 10, and no more than a range of 100 values can be viewed at once without + * scrolling + * + * @param minXRange + * @param maxXRange + */ + open fun setVisibleXRange(minXRange: Float, maxXRange: Float) { + val minScale = mXAxis.mAxisRange / minXRange + val maxScale = mXAxis.mAxisRange / maxXRange + viewPortHandler.setMinMaxScaleX(minScale, maxScale) + } + + /** + * Sets the size of the area (range on the y-axis) that should be maximum + * visible at once. + * + * @param maxYRange the maximum visible range on the y-axis + * @param axis the axis for which this limit should apply + */ + open fun setVisibleYRangeMaximum(maxYRange: Float, axis: AxisDependency?) { + val yScale = getAxisRange(axis) / maxYRange + viewPortHandler.setMinimumScaleY(yScale) + } + + /** + * Sets the size of the area (range on the y-axis) that should be minimum visible at once, no further zooming in possible. + * + * @param minYRange + * @param axis the axis for which this limit should apply + */ + open fun setVisibleYRangeMinimum(minYRange: Float, axis: AxisDependency?) { + val yScale = getAxisRange(axis) / minYRange + viewPortHandler.setMaximumScaleY(yScale) + } + + /** + * Limits the maximum and minimum y range that can be visible by pinching and zooming. + * + * @param minYRange + * @param maxYRange + * @param axis + */ + open fun setVisibleYRange(minYRange: Float, maxYRange: Float, axis: AxisDependency?) { + val minScale = getAxisRange(axis) / minYRange + val maxScale = getAxisRange(axis) / maxYRange + viewPortHandler.setMinMaxScaleY(minScale, maxScale) + } + + + /** + * Moves the left side of the current viewport to the specified x-position. + * This also refreshes the chart by calling invalidate(). + * + * @param xValue + */ + fun moveViewToX(xValue: Float) { + val job: Runnable = MoveViewJob.Companion.getInstance( + viewPortHandler, xValue, 0f, + getTransformer(AxisDependency.LEFT), this + ) + + addViewportJob(job) + } + + /** + * This will move the left side of the current viewport to the specified + * x-value on the x-axis, and center the viewport to the specified y value on the y-axis. + * This also refreshes the chart by calling invalidate(). + * + * @param xValue + * @param yValue + * @param axis - which axis should be used as a reference for the y-axis + */ + fun moveViewTo(xValue: Float, yValue: Float, axis: AxisDependency?) { + val yInView = getAxisRange(axis) / viewPortHandler.scaleY + + val job: Runnable = MoveViewJob.Companion.getInstance( + viewPortHandler, xValue, yValue + yInView / 2f, + getTransformer(axis), this + ) + + addViewportJob(job) + } + + /** + * This will move the left side of the current viewport to the specified x-value + * and center the viewport to the y value animated. + * This also refreshes the chart by calling invalidate(). + * + * @param xValue + * @param yValue + * @param axis + * @param duration the duration of the animation in milliseconds + */ + fun moveViewToAnimated(xValue: Float, yValue: Float, axis: AxisDependency?, duration: Long) { + val bounds = getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentTop(), axis) + + val yInView = getAxisRange(axis) / viewPortHandler.scaleY + + val job: Runnable = AnimatedMoveViewJob.Companion.getInstance( + viewPortHandler, xValue, yValue + yInView / 2f, + getTransformer(axis), this, bounds.x.toFloat(), bounds.y.toFloat(), duration + ) + + addViewportJob(job) + + MPPointD.Companion.recycleInstance(bounds) + } + + /** + * Centers the viewport to the specified y value on the y-axis. + * This also refreshes the chart by calling invalidate(). + * + * @param yValue + * @param axis - which axis should be used as a reference for the y-axis + */ + fun centerViewToY(yValue: Float, axis: AxisDependency?) { + val valsInView = getAxisRange(axis) / viewPortHandler.scaleY + + val job: Runnable = MoveViewJob.Companion.getInstance( + viewPortHandler, 0f, yValue + valsInView / 2f, + getTransformer(axis), this + ) + + addViewportJob(job) + } + + /** + * This will move the center of the current viewport to the specified + * x and y value. + * This also refreshes the chart by calling invalidate(). + * + * @param xValue + * @param yValue + * @param axis - which axis should be used as a reference for the y axis + */ + fun centerViewTo(xValue: Float, yValue: Float, axis: AxisDependency?) { + val yInView = getAxisRange(axis) / viewPortHandler.scaleY + val xInView = xAxis.mAxisRange / viewPortHandler.scaleX + + val job: Runnable = MoveViewJob.Companion.getInstance( + viewPortHandler, + xValue - xInView / 2f, yValue + yInView / 2f, + getTransformer(axis), this + ) + + addViewportJob(job) + } + + /** + * This will move the center of the current viewport to the specified + * x and y value animated. + * + * @param xValue + * @param yValue + * @param axis + * @param duration the duration of the animation in milliseconds + */ + fun centerViewToAnimated(xValue: Float, yValue: Float, axis: AxisDependency?, duration: Long) { + val bounds = getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentTop(), axis) + + val yInView = getAxisRange(axis) / viewPortHandler.scaleY + val xInView = xAxis.mAxisRange / viewPortHandler.scaleX + + val job: Runnable = AnimatedMoveViewJob.Companion.getInstance( + viewPortHandler, + xValue - xInView / 2f, yValue + yInView / 2f, + getTransformer(axis), this, bounds.x.toFloat(), bounds.y.toFloat(), duration + ) + + addViewportJob(job) + + MPPointD.Companion.recycleInstance(bounds) + } + + /** + * flag that indicates if a custom viewport offset has been set + */ + private var mCustomViewPortEnabled = false + + /** + * Sets custom offsets for the current ViewPort (the offsets on the sides of + * the actual chart window). Setting this will prevent the chart from + * automatically calculating it's offsets. Use resetViewPortOffsets() to + * undo this. ONLY USE THIS WHEN YOU KNOW WHAT YOU ARE DOING, else use + * setExtraOffsets(...). + * + * @param left + * @param top + * @param right + * @param bottom + */ + fun setViewPortOffsets( + left: Float, top: Float, + right: Float, bottom: Float + ) { + mCustomViewPortEnabled = true + post { + viewPortHandler.restrainViewPort(left, top, right, bottom) + prepareOffsetMatrix() + prepareValuePxMatrix() + } + } + + /** + * Resets all custom offsets set via setViewPortOffsets(...) method. Allows + * the chart to again calculate all offsets automatically. + */ + fun resetViewPortOffsets() { + mCustomViewPortEnabled = false + calculateOffsets() + } + + /** + * ################ ################ ################ ################ + */ + /** CODE BELOW IS GETTERS AND SETTERS */ + /** + * Returns the range of the specified axis. + * + * @param axis + * @return + */ + protected fun getAxisRange(axis: AxisDependency?): Float { + return if (axis == AxisDependency.LEFT) mAxisLeft.mAxisRange + else mAxisRight.mAxisRange + } + + /** + * Sets the OnDrawListener + * + * @param drawListener + */ + fun setOnDrawListener(drawListener: OnDrawListener?) { + this.drawListener = drawListener + } + + protected var mGetPositionBuffer: FloatArray = FloatArray(2) + + /** + * Returns a recyclable MPPointF instance. + * Returns the position (in pixels) the provided Entry has inside the chart + * view or null, if the provided Entry is null. + * + * @param e + * @return + */ + open fun getPosition(e: Entry, axis: AxisDependency?): MPPointF { + + mGetPositionBuffer[0] = e.x + mGetPositionBuffer[1] = e.y + + getTransformer(axis).pointValuesToPixel(mGetPositionBuffer) + + return MPPointF.getInstance(mGetPositionBuffer[0], mGetPositionBuffer[1]) + } + + /** + * sets the number of maximum visible drawn values on the chart only active + * when setDrawValues() is enabled + * + * @param count + */ + fun setMaxVisibleValueCount(count: Int) { + this.mMaxVisibleCount = count + } + + override val maxVisibleCount: Int + get() = mMaxVisibleCount + + /** + * Sets the color for the background of the chart-drawing area (everything + * behind the grid lines). + * + * @param color + */ + fun setGridBackgroundColor(color: Int) { + mGridBackgroundPaint.setColor(color) + } + + var isDragEnabled: Boolean + /** + * Returns true if dragging is enabled for the chart, false if not. + * + * @return + */ + get() = this.isDragXEnabled || this.isDragYEnabled + /** + * Set this to true to enable dragging (moving the chart with the finger) + * for the chart (this does not effect scaling). + * + * @param enabled + */ + set(enabled) { + this.isDragXEnabled = enabled + this.isDragYEnabled = enabled + } + + /** + * Set this to true to enable scaling (zooming in and out by gesture) for + * the chart (this does not effect dragging) on both X- and Y-Axis. + * + * @param enabled + */ + fun setScaleEnabled(enabled: Boolean) { + this.isScaleXEnabled = enabled + this.isScaleYEnabled = enabled + } + + /** + * Sets the width of the border lines in dp. + * + * @param width + */ + fun setBorderWidth(width: Float) { + mBorderPaint.strokeWidth = Utils.convertDpToPixel(width) + } + + /** + * Sets the color of the chart border lines. + * + * @param color + */ + fun setBorderColor(color: Int) { + mBorderPaint.setColor(color) + } + + /** + * Returns a recyclable MPPointD instance + * Returns the x and y values in the chart at the given touch point + * (encapsulated in a MPPointD). This method transforms pixel coordinates to + * coordinates / values in the chart. This is the opposite method to + * getPixelForValues(...). + * + * @param x + * @param y + * @return + */ + fun getValuesByTouchPoint(x: Float, y: Float, axis: AxisDependency?): MPPointD { + val result: MPPointD = MPPointD.Companion.getInstance(0.0, 0.0) + getValuesByTouchPoint(x, y, axis, result) + return result + } + + fun getValuesByTouchPoint(x: Float, y: Float, axis: AxisDependency?, outputPoint: MPPointD) { + getTransformer(axis).getValuesByTouchPoint(x, y, outputPoint) + } + + /** + * Returns a recyclable MPPointD instance + * Transforms the given chart values into pixels. This is the opposite + * method to getValuesByTouchPoint(...). + * + * @param x + * @param y + * @return + */ + fun getPixelForValues(x: Float, y: Float, axis: AxisDependency?): MPPointD? { + return getTransformer(axis).getPixelForValues(x, y) + } + + /** + * returns the Entry object displayed at the touched position of the chart + * + * @param x + * @param y + * @return + */ + fun getEntryByTouchPoint(x: Float, y: Float): Entry? { + val h = getHighlightByTouchPoint(x, y) + if (h != null) { + return mData?.getEntryForHighlight(h) + } + return null + } + + /** + * returns the DataSet object displayed at the touched position of the chart + * + * @param x + * @param y + * @return + */ + fun getDataSetByTouchPoint(x: Float, y: Float): IBarLineScatterCandleBubbleDataSet<*>? { + val h = getHighlightByTouchPoint(x, y) + if (h != null) { + return mData?.getDataSetByIndex(h.dataSetIndex) + } + return null + } + + /** + * buffer for storing lowest visible x point + */ + protected var posForGetLowestVisibleX: MPPointD = MPPointD.Companion.getInstance(0.0, 0.0) + + override val lowestVisibleX: Float + /** + * Returns the lowest x-index (value on the x-axis) that is still visible on + * the chart. + * + * @return + */ + get() { + getTransformer(AxisDependency.LEFT).getValuesByTouchPoint( + viewPortHandler.contentLeft(), + viewPortHandler.contentBottom(), posForGetLowestVisibleX + ) + val result = max(mXAxis.mAxisMinimum.toDouble(), posForGetLowestVisibleX.x).toFloat() + return result + } + + /** + * buffer for storing highest visible x point + */ + protected var posForGetHighestVisibleX: MPPointD = MPPointD.Companion.getInstance(0.0, 0.0) + + override val highestVisibleX: Float + /** + * Returns the highest x-index (value on the x-axis) that is still visible + * on the chart. + * + * @return + */ + get() { + getTransformer(AxisDependency.LEFT).getValuesByTouchPoint( + viewPortHandler.contentRight(), + viewPortHandler.contentBottom(), posForGetHighestVisibleX + ) + val result = min(mXAxis.mAxisMaximum.toDouble(), posForGetHighestVisibleX.x).toFloat() + return result + } + + val visibleXRange: Float + /** + * Returns the range visible on the x-axis. + * + * @return + */ + get() = abs(highestVisibleX - lowestVisibleX) + + /** + * returns the current x-scale factor + */ + override fun getScaleX(): Float { + return viewPortHandler.scaleX + } + + /** + * returns the current y-scale factor + */ + override fun getScaleY(): Float { + return viewPortHandler.scaleY + } + + val isFullyZoomedOut: Boolean + /** + * if the chart is fully zoomed out, return true + * + * @return + */ + get() = viewPortHandler.isFullyZoomedOut + + val axisLeft: YAxis + /** + * Returns the left y-axis object. In the horizontal bar-chart, this is the + * top axis. + * + * @return + */ + get() = mAxisLeft + + val axisRight: YAxis + /** + * Returns the right y-axis object. In the horizontal bar-chart, this is the + * bottom axis. + * + * @return + */ + get() = mAxisRight + + /** + * Returns the y-axis object to the corresponding AxisDependency. In the + * horizontal bar-chart, LEFT == top, RIGHT == BOTTOM + * + * @param axis + * @return + */ + fun getAxis(axis: AxisDependency?): YAxis { + return if (axis == AxisDependency.LEFT) mAxisLeft + else mAxisRight + } + + override fun isInverted(axis: AxisDependency?): Boolean { + return getAxis(axis).isInverted + } + + /** + * If set to true, both x and y axis can be scaled simultaneously with 2 fingers, if false, + * x and y axis can be scaled separately. default: false + * + * @param enabled + */ + fun setPinchZoom(enabled: Boolean) { + this.isPinchZoomEnabled = enabled + } + + /** + * Set an offset in dp that allows the user to drag the chart over it's + * bounds on the x-axis. + * + * @param offset + */ + fun setDragOffsetX(offset: Float) { + viewPortHandler.setDragOffsetX(offset) + } + + /** + * Set an offset in dp that allows the user to drag the chart over it's + * bounds on the y-axis. + * + * @param offset + */ + fun setDragOffsetY(offset: Float) { + viewPortHandler.setDragOffsetY(offset) + } + + /** + * Returns true if both drag offsets (x and y) are zero or smaller. + * + * @return + */ + fun hasNoDragOffset(): Boolean { + return viewPortHandler.hasNoDragOffset() + } + + val rendererXAxis: XAxisRenderer + get() = mXAxisRenderer + + /** + * Sets a custom XAxisRenderer and overrides the existing (default) one. + * + * @param xAxisRenderer + */ + fun setXAxisRenderer(xAxisRenderer: XAxisRenderer) { + mXAxisRenderer = xAxisRenderer + } + + var rendererLeftYAxis: YAxisRenderer + get() = mAxisRendererLeft + /** + * Sets a custom axis renderer for the left axis and overwrites the existing one. + * + * @param rendererLeftYAxis + */ + set(rendererLeftYAxis) { + mAxisRendererLeft = rendererLeftYAxis + } + + var rendererRightYAxis: YAxisRenderer + get() = mAxisRendererRight + /** + * Sets a custom axis renderer for the right acis and overwrites the existing one. + * + * @param rendererRightYAxis + */ + set(rendererRightYAxis) { + mAxisRendererRight = rendererRightYAxis + } + + override val yChartMax: Float + get() = max(mAxisLeft.mAxisMaximum, mAxisRight.mAxisMaximum) + + override val yChartMin: Float + get() = min(mAxisLeft.mAxisMinimum, mAxisRight.mAxisMinimum) + + val isAnyAxisInverted: Boolean + /** + * Returns true if either the left or the right or both axes are inverted. + * + * @return + */ + get() { + if (mAxisLeft.isInverted) return true + if (mAxisRight.isInverted) return true + return false + } + + override fun setPaint(p: Paint, which: Int) { + super.setPaint(p, which) + + when (which) { + PAINT_GRID_BACKGROUND -> mGridBackgroundPaint = p + } + } + + override fun getPaint(which: Int): Paint? { + val p = super.getPaint(which) + if (p != null) return p + + when (which) { + PAINT_GRID_BACKGROUND -> return mGridBackgroundPaint + } + + return null + } + + protected var mOnSizeChangedBuffer: FloatArray = FloatArray(2) + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + // Saving current position of chart. + + mOnSizeChangedBuffer[1] = 0f + mOnSizeChangedBuffer[0] = mOnSizeChangedBuffer[1] + + if (this.isKeepPositionOnRotation) { + mOnSizeChangedBuffer[0] = viewPortHandler.contentLeft() + mOnSizeChangedBuffer[1] = viewPortHandler.contentTop() + getTransformer(AxisDependency.LEFT).pixelsToValue(mOnSizeChangedBuffer) + } + + //Superclass transforms chart. + super.onSizeChanged(w, h, oldw, oldh) + + if (this.isKeepPositionOnRotation) { + //Restoring old position of chart. + + getTransformer(AxisDependency.LEFT).pointValuesToPixel(mOnSizeChangedBuffer) + viewPortHandler.centerViewPort(mOnSizeChangedBuffer, this) + } else { + viewPortHandler.refresh(viewPortHandler.matrixTouch, this, true) + } + } + + /** + * Sets the text color to use for the labels. Make sure to use + * getResources().getColor(...) when using a color from the resources. + * + * @param color + */ + fun setTextColor(color: Int) { + mAxisRendererLeft.setTextColor(color) + mAxisRendererRight.setTextColor(color) + mXAxisRenderer.setTextColor(color) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BubbleChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BubbleChart.java deleted file mode 100644 index 1510d85f01..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BubbleChart.java +++ /dev/null @@ -1,48 +0,0 @@ - -package com.github.mikephil.charting.charts; - -import android.content.Context; -import android.util.AttributeSet; - -import com.github.mikephil.charting.data.BubbleData; -import com.github.mikephil.charting.interfaces.dataprovider.BubbleDataProvider; -import com.github.mikephil.charting.renderer.BubbleChartRenderer; - -/** - * The BubbleChart. Draws bubbles. Bubble chart implementation: Copyright 2015 - * Pierre-Marc Airoldi Licensed under Apache License 2.0. In the BubbleChart, it - * is the area of the bubble, not the radius or diameter of the bubble that - * conveys the data. - * - * @author Philipp Jahoda - */ -public class BubbleChart extends BarLineChartBase implements BubbleDataProvider { - - public BubbleChart(Context context) { - super(context); - } - - public BubbleChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public BubbleChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void init() { - super.init(); - - mRenderer = new BubbleChartRenderer(this, mAnimator, mViewPortHandler); - } - - public BubbleData getBubbleData() { - return mData; - } - - @Override - public String getAccessibilityDescription() { - return "This is bubble chart"; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BubbleChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BubbleChart.kt new file mode 100644 index 0000000000..836795f5f6 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/BubbleChart.kt @@ -0,0 +1,38 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.util.AttributeSet +import com.github.mikephil.charting.data.BubbleData +import com.github.mikephil.charting.data.BubbleEntry +import com.github.mikephil.charting.interfaces.dataprovider.BubbleDataProvider +import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet +import com.github.mikephil.charting.renderer.BubbleChartRenderer + +/** + * The BubbleChart. Draws bubbles. Bubble chart implementation: Copyright 2015 + * Pierre-Marc Airoldi Licensed under Apache License 2.0. In the BubbleChart, it + * is the area of the bubble, not the radius or diameter of the bubble that + * conveys the data. + * + * @author Philipp Jahoda + */ +class BubbleChart : BarLineChartBase, BubbleDataProvider { + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + init { + mRenderer = BubbleChartRenderer(this, mAnimator, viewPortHandler) + } + + override var bubbleData: BubbleData? + get() = mData + set(value) { + mData = value + } + + override val accessibilityDescription: String? + get() = "This is bubble chart" +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CandleStickChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CandleStickChart.java deleted file mode 100644 index 29ba92d56b..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CandleStickChart.java +++ /dev/null @@ -1,49 +0,0 @@ - -package com.github.mikephil.charting.charts; - -import android.content.Context; -import android.util.AttributeSet; - -import com.github.mikephil.charting.data.CandleData; -import com.github.mikephil.charting.interfaces.dataprovider.CandleDataProvider; -import com.github.mikephil.charting.renderer.CandleStickChartRenderer; - -/** - * Financial chart type that draws candle-sticks (OHCL chart). - * - * @author Philipp Jahoda - */ -public class CandleStickChart extends BarLineChartBase implements CandleDataProvider { - - public CandleStickChart(Context context) { - super(context); - } - - public CandleStickChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CandleStickChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void init() { - super.init(); - - mRenderer = new CandleStickChartRenderer(this, mAnimator, mViewPortHandler); - - getXAxis().setSpaceMin(0.5f); - getXAxis().setSpaceMax(0.5f); - } - - @Override - public CandleData getCandleData() { - return mData; - } - - @Override - public String getAccessibilityDescription() { - return "This is a candlestick chart"; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CandleStickChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CandleStickChart.kt new file mode 100644 index 0000000000..d9ffdb81e1 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CandleStickChart.kt @@ -0,0 +1,38 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.util.AttributeSet +import com.github.mikephil.charting.data.CandleData +import com.github.mikephil.charting.data.CandleEntry +import com.github.mikephil.charting.interfaces.dataprovider.CandleDataProvider +import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet +import com.github.mikephil.charting.renderer.CandleStickChartRenderer + +/** + * Financial chart type that draws candle-sticks (OHCL chart). + * + * @author Philipp Jahoda + */ +class CandleStickChart : BarLineChartBase, CandleDataProvider { + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + init { + mRenderer = CandleStickChartRenderer(this, mAnimator, viewPortHandler) + + xAxis.spaceMin = 0.5f + xAxis.spaceMax = 0.5f + } + + override var candleData: CandleData? + get() = mData + set(value) { + mData = value + } + + override val accessibilityDescription: String? + get() = "This is a candlestick chart" +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/Chart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/Chart.java deleted file mode 100644 index 5ca21a9885..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/Chart.java +++ /dev/null @@ -1,1716 +0,0 @@ -package com.github.mikephil.charting.charts; - -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; -import android.content.ContentValues; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.Paint; -import android.graphics.Paint.Align; -import android.graphics.RectF; -import android.graphics.Typeface; -import android.graphics.drawable.Drawable; -import android.os.Environment; -import android.provider.MediaStore.Images; -import android.text.TextUtils; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; - -import com.github.mikephil.charting.animation.ChartAnimator; -import com.github.mikephil.charting.animation.Easing.EasingFunction; -import com.github.mikephil.charting.components.Description; -import com.github.mikephil.charting.components.IMarker; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.data.ChartData; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.formatter.DefaultValueFormatter; -import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.highlight.ChartHighlighter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.highlight.IHighlighter; -import com.github.mikephil.charting.interfaces.dataprovider.ChartInterface; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.listener.ChartTouchListener; -import com.github.mikephil.charting.listener.OnChartGestureListener; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.renderer.DataRenderer; -import com.github.mikephil.charting.renderer.LegendRenderer; -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.Utils; -import com.github.mikephil.charting.utils.ViewPortHandler; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Baseclass of all Chart-Views. - * - * @author Philipp Jahoda - */ -@SuppressWarnings("unused") -public abstract class Chart>> extends ViewGroup implements ChartInterface { - - public static final String LOG_TAG = "MPAndroidChart"; - - /** - * flag that indicates if logging is enabled or not - */ - protected boolean mLogEnabled = false; - - /** - * object that holds all data that was originally set for the chart, before - * it was modified or any filtering algorithms had been applied - */ - protected T mData = null; - - /** - * Flag that indicates if highlighting per tap (touch) is enabled - */ - protected boolean mHighLightPerTapEnabled = true; - - /** - * If set to true, chart continues to scroll after touch up - */ - private boolean mDragDecelerationEnabled = true; - - /** - * Deceleration friction coefficient in [0 ; 1] interval, higher values - * indicate that speed will decrease slowly, for example if it set to 0, it - * will stop immediately. 1 is an invalid value, and will be converted to - * 0.999f automatically. - */ - private float mDragDecelerationFrictionCoef = 0.9f; - - /** - * default value-formatter, number of digits depends on provided chart-data - */ - protected DefaultValueFormatter mDefaultValueFormatter = new DefaultValueFormatter(0); - - /** - * paint object used for drawing the description text in the bottom right - * corner of the chart - */ - protected Paint mDescPaint; - - /** - * paint object for drawing the information text when there are no values in - * the chart - */ - protected Paint mInfoPaint; - - /** - * the object representing the labels on the x-axis - */ - protected XAxis mXAxis; - - /** - * if true, touch gestures are enabled on the chart - */ - protected boolean mTouchEnabled = true; - - /** - * the object responsible for representing the description text - */ - protected Description mDescription; - - /** - * the legend object containing all data associated with the legend - */ - protected Legend mLegend; - - /** - * listener that is called when a value on the chart is selected - */ - protected OnChartValueSelectedListener mSelectionListener; - - protected ChartTouchListener mChartTouchListener; - - /** - * text that is displayed when the chart is empty - */ - private String mNoDataText = "No chart data available."; - - /** - * Gesture listener for custom callbacks when making gestures on the chart. - */ - private OnChartGestureListener mGestureListener; - - protected LegendRenderer mLegendRenderer; - - /** - * object responsible for rendering the data - */ - protected DataRenderer mRenderer; - - protected IHighlighter mHighlighter; - - /** - * object that manages the bounds and drawing constraints of the chart - */ - protected ViewPortHandler mViewPortHandler = new ViewPortHandler(); - - /** - * object responsible for animations - */ - protected ChartAnimator mAnimator; - - /** - * Extra offsets to be appended to the viewport - */ - private float mExtraTopOffset = 0.f, mExtraRightOffset = 0.f, mExtraBottomOffset = 0.f, mExtraLeftOffset = 0.f; - - /** - * Additional data on top of dynamically generated description. This can be set by the user. - */ - private String accessibilitySummaryDescription = ""; - - /** - * default constructor for initialization in code - */ - public Chart(Context context) { - super(context); - init(); - } - - /** - * constructor for initialization in xml - */ - public Chart(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - /** - * even more awesome constructor - */ - public Chart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(); - } - - /** - * initialize all paints and stuff - */ - protected void init() { - - setWillNotDraw(false); - // setLayerType(View.LAYER_TYPE_HARDWARE, null); - - mAnimator = new ChartAnimator(new AnimatorUpdateListener() { - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - // ViewCompat.postInvalidateOnAnimation(Chart.this); - postInvalidate(); - } - }); - - // initialize the utils - Utils.init(getContext()); - mMaxHighlightDistance = Utils.convertDpToPixel(500f); - - mDescription = new Description(); - mLegend = new Legend(); - - mLegendRenderer = new LegendRenderer(mViewPortHandler, mLegend); - - mXAxis = new XAxis(); - - mDescPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - - mInfoPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mInfoPaint.setColor(Color.rgb(247, 189, 51)); // orange - mInfoPaint.setTextAlign(Align.CENTER); - mInfoPaint.setTextSize(Utils.convertDpToPixel(12f)); - - if (mLogEnabled) { - Log.i("", "Chart.init()"); - - // enable being detected by ScreenReader - setFocusable(true); - }} - - // public void initWithDummyData() { - // ColorTemplate template = new ColorTemplate(); - // template.addColorsForDataSets(ColorTemplate.COLORFUL_COLORS, - // getContext()); - // - // setColorTemplate(template); - // setDrawYValues(false); - // - // ArrayList xVals = new ArrayList(); - // Calendar calendar = Calendar.getInstance(); - // for (int i = 0; i < 12; i++) { - // xVals.add(calendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, - // Locale.getDefault())); - // } - // - // ArrayList dataSets = new ArrayList(); - // for (int i = 0; i < 3; i++) { - // - // ArrayList yVals = new ArrayList(); - // - // for (int j = 0; j < 12; j++) { - // float val = (float) (Math.random() * 100); - // yVals.add(new Entry(val, j)); - // } - // - // DataSet set = new DataSet(yVals, "DataSet " + i); - // dataSets.add(set); // add the datasets - // } - // // create a data object with the datasets - // ChartData data = new ChartData(xVals, dataSets); - // setData(data); - // invalidate(); - // } - - /** - * Sets a new data object for the chart. The data object contains all values - * and information needed for displaying. - */ - public void setData(T data) { - - mData = data; - mOffsetsCalculated = false; - - if (data == null) { - return; - } - - // calculate how many digits are needed - setupDefaultFormatter(data.getYMin(), data.getYMax()); - - for (IDataSet set : mData.getDataSets()) { - if (set.needsFormatter() || set.getValueFormatter() == mDefaultValueFormatter) { - set.setValueFormatter(mDefaultValueFormatter); - } - } - - // let the chart know there is new data - notifyDataSetChanged(); - - if (mLogEnabled) { - Log.i(LOG_TAG, "Data is set."); - } - } - - /** - * Clears the chart from all data (sets it to null) and refreshes it (by - * calling invalidate()). - */ - public void clear() { - mData = null; - mOffsetsCalculated = false; - mIndicesToHighlight = null; - mChartTouchListener.setLastHighlighted(null); - invalidate(); - } - - /** - * Removes all DataSets (and thereby Entries) from the chart. Does not set the data object to null. Also refreshes the - * chart by calling invalidate(). - */ - public void clearValues() { - mData.clearValues(); - invalidate(); - } - - /** - * Returns true if the chart is empty (meaning it's data object is either - * null or contains no entries). - */ - public boolean isEmpty() { - if (mData == null) { - return true; - } else { - return mData.getEntryCount() <= 0; - } - } - - /** - * Lets the chart know its underlying data has changed and performs all - * necessary recalculations. It is crucial that this method is called - * everytime data is changed dynamically. Not calling this method can lead - * to crashes or unexpected behaviour. - */ - public abstract void notifyDataSetChanged(); - - /** - * Calculates the offsets of the chart to the border depending on the - * position of an eventual legend or depending on the length of the y-axis - * and x-axis labels and their position - */ - protected abstract void calculateOffsets(); - - /** - * Calculates the y-min and y-max value and the y-delta and x-delta value - */ - protected abstract void calcMinMax(); - - /** - * Calculates the required number of digits for the values that might be - * drawn in the chart (if enabled), and creates the default-value-formatter - */ - protected void setupDefaultFormatter(float min, float max) { - - float reference; - - if (mData == null || mData.getEntryCount() < 2) { - reference = Math.max(Math.abs(min), Math.abs(max)); - } else { - reference = Math.abs(max - min); - } - - int digits = Utils.getDecimals(reference); - - // setup the formatter with a new number of digits - mDefaultValueFormatter.setup(digits); - } - - /** - * flag that indicates if offsets calculation has already been done or not - */ - private boolean mOffsetsCalculated = false; - - @Override - protected void onDraw(Canvas canvas) { - // super.onDraw(canvas); - - if (mData == null) { - - boolean hasText = !TextUtils.isEmpty(mNoDataText); - - if (hasText) { - MPPointF pt = getCenter(); - - switch (mInfoPaint.getTextAlign()) { - case LEFT: - pt.x = 0; - canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint); - break; - - case RIGHT: - pt.x *= 2.0; - canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint); - break; - - default: - canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint); - break; - } - } - - return; - } - - if (!mOffsetsCalculated) { - - calculateOffsets(); - mOffsetsCalculated = true; - } - } - - /** - * Draws the description text in the bottom right corner of the chart (per default) - */ - protected void drawDescription(Canvas c) { - - // check if description should be drawn - if (mDescription != null && mDescription.isEnabled()) { - - MPPointF position = mDescription.getPosition(); - - mDescPaint.setTypeface(mDescription.getTypeface()); - mDescPaint.setTextSize(mDescription.getTextSize()); - mDescPaint.setColor(mDescription.getTextColor()); - mDescPaint.setTextAlign(mDescription.getTextAlign()); - - float x, y; - - // if no position specified, draw on default position - if (position == null) { - x = getWidth() - mViewPortHandler.offsetRight() - mDescription.getXOffset(); - y = getHeight() - mViewPortHandler.offsetBottom() - mDescription.getYOffset(); - } else { - x = position.x; - y = position.y; - } - - c.drawText(mDescription.getText(), x, y, mDescPaint); - } - } - - /** - * array of Highlight objects that reference the highlighted slices in the - * chart - */ - protected Highlight[] mIndicesToHighlight; - - /** - * The maximum distance in dp away from an entry causing it to highlight. - */ - protected float mMaxHighlightDistance = 0f; - - @Override - public float getMaxHighlightDistance() { - return mMaxHighlightDistance; - } - - /** - * Sets the maximum distance in screen dp a touch can be away from an entry to cause it to get highlighted. - * Default: 500dp - */ - public void setMaxHighlightDistance(float distDp) { - mMaxHighlightDistance = Utils.convertDpToPixel(distDp); - } - - /** - * Returns the array of currently highlighted values. This might a null or - * empty array if nothing is highlighted. - */ - public Highlight[] getHighlighted() { - return mIndicesToHighlight; - } - - /** - * Returns true if values can be highlighted via tap gesture, false if not. - */ - public boolean isHighlightPerTapEnabled() { - return mHighLightPerTapEnabled; - } - - /** - * Set this to false to prevent values from being highlighted by tap gesture. - * Values can still be highlighted via drag or programmatically. Default: true - */ - public void setHighlightPerTapEnabled(boolean enabled) { - mHighLightPerTapEnabled = enabled; - } - - /** - * Returns true if there are values to highlight, false if there are no - * values to highlight. Checks if the highlight array is null, has a length - * of zero or if the first object is null. - */ - public boolean valuesToHighlight() { - return mIndicesToHighlight != null && mIndicesToHighlight.length > 0 && mIndicesToHighlight[0] != null; - } - - /** - * Sets the last highlighted value for the touchlistener. - */ - protected void setLastHighlighted(Highlight[] highs) { - - if (highs == null || highs.length <= 0 || highs[0] == null) { - mChartTouchListener.setLastHighlighted(null); - } else { - mChartTouchListener.setLastHighlighted(highs[0]); - } - } - - /** - * Highlights the values at the given indices in the given DataSets. Provide - * null or an empty array to undo all highlighting. This should be used to - * programmatically highlight values. - * This method *will not* call the listener. - */ - public void highlightValues(Highlight[] highs) { - - // set the indices to highlight - mIndicesToHighlight = highs; - - setLastHighlighted(highs); - - // redraw the chart - invalidate(); - } - - public void highlightValues(List highs, List markers) { - if (highs.size() != markers.size()) throw new IllegalArgumentException("Markers and highs must be mutually corresponding. High size = " + highs.size() + " Markers size = " + markers.size()); - setMarkers(markers); - highlightValues(highs.toArray(new Highlight[0])); - } - - /** - * Highlights any y-value at the given x-value in the given DataSet. - * Provide -1 as the dataSetIndex to undo all highlighting. - * This method will call the listener. - ** @param x The x-value to highlight - * @param dataSetIndex The dataset index to search in - * @param dataIndex The data index to search in (only used in CombinedChartView currently) - */ - public void highlightValue(float x, int dataSetIndex, int dataIndex) { - highlightValue(x, dataSetIndex, dataIndex, true); - } - - /** - * Highlights any y-value at the given x-value in the given DataSet. - * Provide -1 as the dataSetIndex to undo all highlighting. - * This method will call the listener. - * - * @param x The x-value to highlight - * @param dataSetIndex The dataset index to search in - */ - public void highlightValue(float x, int dataSetIndex) { - highlightValue(x, dataSetIndex, -1, true); - } - - /** - * Highlights the value at the given x-value and y-value in the given DataSet. - * Provide -1 as the dataSetIndex to undo all highlighting. - * This method will call the listener. - * - * @param x The x-value to highlight - * @param y The y-value to highlight. Supply `NaN` for "any" - * @param dataSetIndex The dataset index to search in - * @param dataIndex The data index to search in (only used in CombinedChartView currently) - */ - public void highlightValue(float x, float y, int dataSetIndex, int dataIndex) { - highlightValue(x, y, dataSetIndex, dataIndex, true); - } - - /** - * Highlights the value at the given x-value and y-value in the given DataSet. - * Provide -1 as the dataSetIndex to undo all highlighting. - * This method will call the listener. - * - * @param x The x-value to highlight - * @param y The y-value to highlight. Supply `NaN` for "any" - * @param dataSetIndex The dataset index to search in - */ - public void highlightValue(float x, float y, int dataSetIndex) { - highlightValue(x, y, dataSetIndex, -1, true); - } - - /** - * Highlights any y-value at the given x-value in the given DataSet. - * Provide -1 as the dataSetIndex to undo all highlighting. - * - * @param x The x-value to highlight - * @param dataSetIndex The dataset index to search in - * @param dataIndex The data index to search in (only used in CombinedChartView currently) - * @param callListener Should the listener be called for this change - */ - public void highlightValue(float x, int dataSetIndex, int dataIndex, boolean callListener) { - highlightValue(x, Float.NaN, dataSetIndex, dataIndex, callListener); - } - - /** - * Highlights any y-value at the given x-value in the given DataSet. - * Provide -1 as the dataSetIndex to undo all highlighting. - * - * @param x The x-value to highlight - * @param dataSetIndex The dataset index to search in - * @param callListener Should the listener be called for this change - */ - public void highlightValue(float x, int dataSetIndex, boolean callListener) { - highlightValue(x, Float.NaN, dataSetIndex, -1, callListener); - } - - /** - * Highlights any y-value at the given x-value in the given DataSet. - * Provide -1 as the dataSetIndex to undo all highlighting. - * - * @param x The x-value to highlight - * @param y The y-value to highlight. Supply `NaN` for "any" - * @param dataSetIndex The dataset index to search in - * @param dataIndex The data index to search in (only used in CombinedChartView currently) - * @param callListener Should the listener be called for this change - */ - public void highlightValue(float x, float y, int dataSetIndex, int dataIndex, boolean callListener) { - - if (dataSetIndex < 0 || dataSetIndex >= mData.getDataSetCount()) { - highlightValue(null, callListener); - } else { - highlightValue(new Highlight(x, y, dataSetIndex, dataIndex), callListener); - } - } - - /** - * Highlights any y-value at the given x-value in the given DataSet. - * Provide -1 as the dataSetIndex to undo all highlighting. - * - * @param x The x-value to highlight - * @param y The y-value to highlight. Supply `NaN` for "any" - * @param dataSetIndex The dataset index to search in - * @param callListener Should the listener be called for this change - */ - public void highlightValue(float x, float y, int dataSetIndex, boolean callListener) { - highlightValue(x, y, dataSetIndex, -1, callListener); - } - - /** - * Highlights the values represented by the provided Highlight object - * This method *will not* call the listener. - * - * @param highlight contains information about which entry should be highlighted - */ - public void highlightValue(Highlight highlight) { - highlightValue(highlight, false); - } - - /** - * Highlights the value selected by touch gesture. Unlike - * highlightValues(...), this generates a callback to the - * OnChartValueSelectedListener. - * - * @param high - the highlight object - * @param callListener - call the listener - */ - public void highlightValue(Highlight high, boolean callListener) { - - Entry e = null; - - if (high == null) { - mIndicesToHighlight = null; - } else { - - if (mLogEnabled) { - Log.i(LOG_TAG, "Highlighted: " + high); - } - - e = mData.getEntryForHighlight(high); - if (e == null) { - mIndicesToHighlight = null; - high = null; - } else { - - // set the indices to highlight - mIndicesToHighlight = new Highlight[]{high}; - } - } - - setLastHighlighted(mIndicesToHighlight); - - if (callListener && mSelectionListener != null) { - - if (!valuesToHighlight()) { - mSelectionListener.onNothingSelected(); - } else { - // notify the listener - mSelectionListener.onValueSelected(e, high); - } - } - - // redraw the chart - invalidate(); - } - - /** - * Returns the Highlight object (contains x-index and DataSet index) of the - * selected value at the given touch point inside the Line-, Scatter-, or - * CandleStick-Chart. - */ - public Highlight getHighlightByTouchPoint(float x, float y) { - - if (mData == null) { - Log.e(LOG_TAG, "Can't select by touch. No data set."); - return null; - } else { - return getHighlighter().getHighlight(x, y); - } - } - - /** - * Set a new (e.g. custom) ChartTouchListener NOTE: make sure to - * setTouchEnabled(true); if you need touch gestures on the chart - */ - public void setOnTouchListener(ChartTouchListener l) { - this.mChartTouchListener = l; - } - - /** - * Returns an instance of the currently active touch listener. - */ - public ChartTouchListener getOnTouchListener() { - return mChartTouchListener; - } - - /** - * if set to true, the marker view is drawn when a value is clicked - */ - protected boolean mDrawMarkers = true; - - /** - * the view that represents the marker - */ - protected List mMarkers = new ArrayList<>(); - - /** - * draws all MarkerViews on the highlighted positions - */ - protected void drawMarkers(Canvas canvas) { - - // if there is no marker view or drawing marker is disabled - if (mMarkers == null || !isDrawMarkersEnabled() || !valuesToHighlight()) { - return; - } - - for (int i = 0; i < mIndicesToHighlight.length; i++) { - - Highlight highlight = mIndicesToHighlight[i]; - - // When changing data sets and calling animation functions, sometimes an erroneous highlight is generated - // on the dataset that is removed. Null check to prevent crash - IDataSet set = mData.getDataSetByIndex(highlight.getDataSetIndex()); - if (set == null || !set.isVisible()) { - continue; - } - - Entry e = mData.getEntryForHighlight(highlight); - - // make sure entry not null before using it - if (e == null) { - continue; - } - - int entryIndex = set.getEntryIndex(e); - - if (entryIndex > set.getEntryCount() * mAnimator.getPhaseX()) { - continue; - } - - float[] pos = getMarkerPosition(highlight); - - // check bounds - if (!mViewPortHandler.isInBounds(pos[0], pos[1])) { - continue; - } - - // callbacks to update the content - if (!mMarkers.isEmpty()) { - int markerIndex = i % mMarkers.size(); - IMarker markerItem = mMarkers.get(markerIndex); - markerItem.refreshContent(e, highlight); - - // draw the marker - markerItem.draw(canvas, pos[0], pos[1]); - } - } - } - - /** - * Returns the actual position in pixels of the MarkerView for the given - * Highlight object. - */ - protected float[] getMarkerPosition(Highlight high) { - return new float[]{high.getDrawX(), high.getDrawY()}; - } - - /** - * Returns the animator responsible for animating chart values. - */ - public ChartAnimator getAnimator() { - return mAnimator; - } - - /** - * If set to true, chart continues to scroll after touch up default: true - */ - public boolean isDragDecelerationEnabled() { - return mDragDecelerationEnabled; - } - - /** - * If set to true, chart continues to scroll after touch up. Default: true. - */ - public void setDragDecelerationEnabled(boolean enabled) { - mDragDecelerationEnabled = enabled; - } - - /** - * Returns drag deceleration friction coefficient - */ - public float getDragDecelerationFrictionCoef() { - return mDragDecelerationFrictionCoef; - } - - /** - * Deceleration friction coefficient in [0 ; 1] interval, higher values - * indicate that speed will decrease slowly, for example if it set to 0, it - * will stop immediately. 1 is an invalid value, and will be converted to - * 0.999f automatically. - */ - public void setDragDecelerationFrictionCoef(float newValue) { - - if (newValue < 0.f) { - newValue = 0.f; - } - - if (newValue >= 1f) { - newValue = 0.999f; - } - - mDragDecelerationFrictionCoef = newValue; - } - - /** - * Animates the drawing / rendering of the chart on both x- and y-axis with - * the specified animation time. If animate(...) is called, no further - * calling of invalidate() is necessary to refresh the chart. ANIMATIONS - * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. - * - * @param easingX a custom easing function to be used on the animation phase - * @param easingY a custom easing function to be used on the animation phase - */ - public void animateXY(int durationMillisX, int durationMillisY, EasingFunction easingX, EasingFunction easingY) { - mAnimator.animateXY(durationMillisX, durationMillisY, easingX, easingY); - } - - /** - * Animates the drawing / rendering of the chart on both x- and y-axis with - * the specified animation time. If animate(...) is called, no further - * calling of invalidate() is necessary to refresh the chart. ANIMATIONS - * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. - * - * @param easing a custom easing function to be used on the animation phase - */ - public void animateXY(int durationMillisX, int durationMillisY, EasingFunction easing) { - mAnimator.animateXY(durationMillisX, durationMillisY, easing); - } - - /** - * Animates the rendering of the chart on the x-axis with the specified - * animation time. If animate(...) is called, no further calling of - * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR - * API LEVEL 11 (Android 3.0.x) AND HIGHER. - * - * @param easing a custom easing function to be used on the animation phase - */ - public void animateX(int durationMillis, EasingFunction easing) { - mAnimator.animateX(durationMillis, easing); - } - - /** - * Animates the rendering of the chart on the y-axis with the specified - * animation time. If animate(...) is called, no further calling of - * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR - * API LEVEL 11 (Android 3.0.x) AND HIGHER. - * - * @param easing a custom easing function to be used on the animation phase - */ - public void animateY(int durationMillis, EasingFunction easing) { - mAnimator.animateY(durationMillis, easing); - } - - /** - * Animates the rendering of the chart on the x-axis with the specified - * animation time. If animate(...) is called, no further calling of - * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR - * API LEVEL 11 (Android 3.0.x) AND HIGHER. - */ - public void animateX(int durationMillis) { - mAnimator.animateX(durationMillis); - } - - /** - * Animates the rendering of the chart on the y-axis with the specified - * animation time. If animate(...) is called, no further calling of - * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR - * API LEVEL 11 (Android 3.0.x) AND HIGHER. - */ - public void animateY(int durationMillis) { - mAnimator.animateY(durationMillis); - } - - /** - * Animates the drawing / rendering of the chart on both x- and y-axis with - * the specified animation time. If animate(...) is called, no further - * calling of invalidate() is necessary to refresh the chart. ANIMATIONS - * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. - */ - public void animateXY(int durationMillisX, int durationMillisY) { - mAnimator.animateXY(durationMillisX, durationMillisY); - } - - - /** - * Returns the object representing all x-labels, this method can be used to - * acquire the XAxis object and modify it (e.g. change the position of the - * labels, styling, etc.) - */ - public XAxis getXAxis() { - return mXAxis; - } - - /** - * Returns the default IValueFormatter that has been determined by the chart - * considering the provided minimum and maximum values. - */ - public IValueFormatter getDefaultValueFormatter() { - return mDefaultValueFormatter; - } - - /** - * set a selection listener for the chart - */ - public void setOnChartValueSelectedListener(OnChartValueSelectedListener l) { - this.mSelectionListener = l; - } - - /** - * Sets a gesture-listener for the chart for custom callbacks when executing - * gestures on the chart surface. - */ - public void setOnChartGestureListener(OnChartGestureListener l) { - this.mGestureListener = l; - } - - /** - * Returns the custom gesture listener. - */ - public OnChartGestureListener getOnChartGestureListener() { - return mGestureListener; - } - - /** - * returns the current y-max value across all DataSets - */ - public float getYMax() { - return mData.getYMax(); - } - - /** - * returns the current y-min value across all DataSets - */ - public float getYMin() { - return mData.getYMin(); - } - - @Override - public float getXChartMax() { - return mXAxis.mAxisMaximum; - } - - @Override - public float getXChartMin() { - return mXAxis.mAxisMinimum; - } - - @Override - public float getXRange() { - return mXAxis.mAxisRange; - } - - /** - * Returns a recyclable MPPointF instance. - * Returns the center point of the chart (the whole View) in pixels. - */ - public MPPointF getCenter() { - return MPPointF.getInstance(getWidth() / 2f, getHeight() / 2f); - } - - /** - * Returns a recyclable MPPointF instance. - * Returns the center of the chart taking offsets under consideration. - * (returns the center of the content rectangle) - */ - @Override - public MPPointF getCenterOffsets() { - return mViewPortHandler.getContentCenter(); - } - - /** - * Sets extra offsets (around the chart view) to be appended to the - * auto-calculated offsets. - */ - public void setExtraOffsets(float left, float top, float right, float bottom) { - setExtraLeftOffset(left); - setExtraTopOffset(top); - setExtraRightOffset(right); - setExtraBottomOffset(bottom); - } - - /** - * Set an extra offset to be appended to the viewport's top - */ - public void setExtraTopOffset(float offset) { - mExtraTopOffset = Utils.convertDpToPixel(offset); - } - - /** - * @return the extra offset to be appended to the viewport's top - */ - public float getExtraTopOffset() { - return mExtraTopOffset; - } - - /** - * Set an extra offset to be appended to the viewport's right - */ - public void setExtraRightOffset(float offset) { - mExtraRightOffset = Utils.convertDpToPixel(offset); - } - - /** - * @return the extra offset to be appended to the viewport's right - */ - public float getExtraRightOffset() { - return mExtraRightOffset; - } - - /** - * Set an extra offset to be appended to the viewport's bottom - */ - public void setExtraBottomOffset(float offset) { - mExtraBottomOffset = Utils.convertDpToPixel(offset); - } - - /** - * @return the extra offset to be appended to the viewport's bottom - */ - public float getExtraBottomOffset() { - return mExtraBottomOffset; - } - - /** - * Set an extra offset to be appended to the viewport's left - */ - public void setExtraLeftOffset(float offset) { - mExtraLeftOffset = Utils.convertDpToPixel(offset); - } - - /** - * @return the extra offset to be appended to the viewport's left - */ - public float getExtraLeftOffset() { - return mExtraLeftOffset; - } - - /** - * Set this to true to enable logcat outputs for the chart. Beware that - * logcat output decreases rendering performance. Default: disabled. - */ - public void setLogEnabled(boolean enabled) { - mLogEnabled = enabled; - } - - /** - * Returns true if log-output is enabled for the chart, fals if not. - */ - public boolean isLogEnabled() { - return mLogEnabled; - } - - /** - * Sets the text that informs the user that there is no data available with - * which to draw the chart. - */ - public void setNoDataText(String text) { - mNoDataText = text; - } - - /** - * Sets the color of the no data text. - */ - public void setNoDataTextColor(int color) { - mInfoPaint.setColor(color); - } - - /** - * Sets the typeface to be used for the no data text. - */ - public void setNoDataTextTypeface(Typeface tf) { - mInfoPaint.setTypeface(tf); - } - - /** - * alignment of the no data text - */ - public void setNoDataTextAlignment(Align align) { - mInfoPaint.setTextAlign(align); - } - - /** - * Set this to false to disable all gestures and touches on the chart, - * default: true - */ - public void setTouchEnabled(boolean enabled) { - this.mTouchEnabled = enabled; - } - - public void setMarkers(List marker) { - mMarkers = marker; - } - - /** - * sets the marker that is displayed when a value is clicked on the chart - */ - public void setMarker(IMarker marker) { - setMarkers(Collections.singletonList(marker)); - } - /** - * returns the marker that is set as a marker view for the chart - - */ - public List getMarker() { - return mMarkers; - } - - @Deprecated - public void setMarkerView(IMarker v) { - setMarker(v); - } - - @Deprecated - public List getMarkerView() { - return getMarker(); - } - - /** - * Sets a new Description object for the chart. - */ - public void setDescription(Description desc) { - this.mDescription = desc; - } - - /** - * Returns the Description object of the chart that is responsible for holding all information related - * to the description text that is displayed in the bottom right corner of the chart (by default). - */ - public Description getDescription() { - return mDescription; - } - - /** - * Returns the Legend object of the chart. This method can be used to get an - * instance of the legend in order to customize the automatically generated - * Legend. - */ - public Legend getLegend() { - return mLegend; - } - - /** - * Returns the renderer object responsible for rendering / drawing the - * Legend. - */ - public LegendRenderer getLegendRenderer() { - return mLegendRenderer; - } - - /** - * Returns the rectangle that defines the borders of the chart-value surface - * (into which the actual values are drawn). - */ - @Override - public RectF getContentRect() { - return mViewPortHandler.getContentRect(); - } - - /** - * disables intercept touchevents - */ - public void disableScroll() { - ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - } - - /** - * enables intercept touchevents - */ - public void enableScroll() { - ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(false); - } - } - - /** - * paint for the grid background (only line and barchart) - */ - public static final int PAINT_GRID_BACKGROUND = 4; - - /** - * paint for the info text that is displayed when there are no values in the - * chart - */ - public static final int PAINT_INFO = 7; - - /** - * paint for the description text in the bottom right corner - */ - public static final int PAINT_DESCRIPTION = 11; - - /** - * paint for the hole in the middle of the pie chart - */ - public static final int PAINT_HOLE = 13; - - /** - * paint for the text in the middle of the pie chart - */ - public static final int PAINT_CENTER_TEXT = 14; - - /** - * paint used for the legend - */ - public static final int PAINT_LEGEND_LABEL = 18; - - /** - * set a new paint object for the specified parameter in the chart e.g. - * Chart.PAINT_VALUES - * - * @param p the new paint object - * @param which Chart.PAINT_VALUES, Chart.PAINT_GRID, Chart.PAINT_VALUES, - * ... - */ - public void setPaint(Paint p, int which) { - - switch (which) { - case PAINT_INFO: - mInfoPaint = p; - break; - case PAINT_DESCRIPTION: - mDescPaint = p; - break; - } - } - - /** - * Returns the paint object associated with the provided constant. - * - * @param which e.g. Chart.PAINT_LEGEND_LABEL - */ - public Paint getPaint(int which) { - switch (which) { - case PAINT_INFO: - return mInfoPaint; - case PAINT_DESCRIPTION: - return mDescPaint; - } - - return null; - } - - @Deprecated - public boolean isDrawMarkerViewsEnabled() { - return isDrawMarkersEnabled(); - } - - @Deprecated - public void setDrawMarkerViews(boolean enabled) { - setDrawMarkers(enabled); - } - - /** - * returns true if drawing the marker is enabled when tapping on values - * (use the setMarker(IMarker marker) method to specify a marker) - */ - public boolean isDrawMarkersEnabled() { - return mDrawMarkers; - } - - /** - * Set this to true to draw a user specified marker when tapping on - * chart values (use the setMarker(IMarker marker) method to specify a - * marker). Default: true - */ - public void setDrawMarkers(boolean enabled) { - mDrawMarkers = enabled; - } - - /** - * Returns the ChartData object that has been set for the chart. - */ - public T getData() { - return mData; - } - - /** - * Returns the ViewPortHandler of the chart that is responsible for the - * content area of the chart and its offsets and dimensions. - */ - public ViewPortHandler getViewPortHandler() { - return mViewPortHandler; - } - - /** - * Returns the Renderer object the chart uses for drawing data. - */ - public DataRenderer getRenderer() { - return mRenderer; - } - - /** - * Sets a new DataRenderer object for the chart. - */ - public void setRenderer(DataRenderer renderer) { - - if (renderer != null) { - mRenderer = renderer; - } - } - - public IHighlighter getHighlighter() { - return mHighlighter; - } - - /** - * Sets a custom highligher object for the chart that handles / processes - * all highlight touch events performed on the chart-view. - */ - public void setHighlighter(ChartHighlighter highlighter) { - mHighlighter = highlighter; - } - - /** - * Returns a recyclable MPPointF instance. - */ - @Override - public MPPointF getCenterOfView() { - return getCenter(); - } - - /** - * Returns the bitmap that represents the chart. - */ - public Bitmap getChartBitmap() { - // Define a bitmap with the same size as the view - Bitmap returnedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565); - // Bind a canvas to it - Canvas canvas = new Canvas(returnedBitmap); - // Get the view's background - Drawable bgDrawable = getBackground(); - if (bgDrawable != null) - // has background drawable, then draw it on the canvas - { - bgDrawable.draw(canvas); - } else - // does not have background drawable, then draw white background on - // the canvas - { - canvas.drawColor(Color.WHITE); - } - // draw the view on the canvas - draw(canvas); - // return the bitmap - return returnedBitmap; - } - - /** - * Saves the current chart state with the given name to the given path on - * the sdcard leaving the path empty "" will put the saved file directly on - * the SD card chart is saved as a PNG image, example: - * saveToPath("myfilename", "foldername1/foldername2"); - * - * @param pathOnSD e.g. "folder1/folder2/folder3" - * @return returns true on success, false on error - */ - public boolean saveToPath(String title, String pathOnSD) { - - - Bitmap b = getChartBitmap(); - - OutputStream stream; - try { - stream = new FileOutputStream(Environment.getExternalStorageDirectory().getPath() + pathOnSD + "/" + title + ".png"); - - /* - * Write bitmap to file using JPEG or PNG and 40% quality hint for - * JPEG. - */ - b.compress(CompressFormat.PNG, 40, stream); - - stream.close(); - } catch (Exception e) { - e.printStackTrace(); - return false; - } - - return true; - } - - /** - * Saves the current state of the chart to the gallery as an image type. The - * compression must be set for JPEG only. 0 == maximum compression, 100 = low - * compression (high quality). NOTE: Needs permission WRITE_EXTERNAL_STORAGE - * - * @param fileName e.g. "my_image" - * @param subFolderPath e.g. "ChartPics" - * @param fileDescription e.g. "Chart details" - * @param format e.g. Bitmap.CompressFormat.PNG - * @param quality e.g. 50, min = 0, max = 100 - * @return returns true if saving was successful, false if not - */ - public boolean saveToGallery(String fileName, String subFolderPath, String fileDescription, Bitmap.CompressFormat format, int quality) { - // restrain quality - if (quality < 0 || quality > 100) { - quality = 50; - } - - long currentTime = System.currentTimeMillis(); - - File extBaseDir = Environment.getExternalStorageDirectory(); - File file = new File(extBaseDir.getAbsolutePath() + "/DCIM/" + subFolderPath); - if (!file.exists()) { - if (!file.mkdirs()) { - return false; - } - } - - String mimeType; - switch (format) { - case PNG: - mimeType = "image/png"; - if (!fileName.endsWith(".png")) { - fileName += ".png"; - } - break; - case WEBP: - mimeType = "image/webp"; - if (!fileName.endsWith(".webp")) { - fileName += ".webp"; - } - break; - case JPEG: - default: - mimeType = "image/jpeg"; - if (!(fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))) { - fileName += ".jpg"; - } - break; - } - - String filePath = file.getAbsolutePath() + "/" + fileName; - FileOutputStream out; - try { - out = new FileOutputStream(filePath); - - Bitmap b = getChartBitmap(); - b.compress(format, quality, out); - - out.flush(); - out.close(); - - } catch (IOException e) { - e.printStackTrace(); - - return false; - } - - long size = new File(filePath).length(); - - ContentValues values = new ContentValues(8); - - // store the details - values.put(Images.Media.TITLE, fileName); - values.put(Images.Media.DISPLAY_NAME, fileName); - values.put(Images.Media.DATE_ADDED, currentTime); - values.put(Images.Media.MIME_TYPE, mimeType); - values.put(Images.Media.DESCRIPTION, fileDescription); - values.put(Images.Media.ORIENTATION, 0); - values.put(Images.Media.DATA, filePath); - values.put(Images.Media.SIZE, size); - - return getContext().getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values) != null; - } - - /** - * Saves the current state of the chart to the gallery as a JPEG image. The - * filename and compression can be set. 0 == maximum compression, 100 = low - * compression (high quality). NOTE: Needs permission WRITE_EXTERNAL_STORAGE - * - * @param fileName e.g. "my_image" - * @param quality e.g. 50, min = 0, max = 100 - * @return returns true if saving was successful, false if not - */ - public boolean saveToGallery(String fileName, int quality) { - return saveToGallery(fileName, "", "MPAndroidChart-Library Save", Bitmap.CompressFormat.PNG, quality); - } - - /** - * Saves the current state of the chart to the gallery as a PNG image. - * NOTE: Needs permission WRITE_EXTERNAL_STORAGE - * - * @param fileName e.g. "my_image" - * @return returns true if saving was successful, false if not - */ - public boolean saveToGallery(String fileName) { - return saveToGallery(fileName, "", "MPAndroidChart-Library Save", Bitmap.CompressFormat.PNG, 40); - } - - /** - * tasks to be done after the view is setup - */ - protected ArrayList mJobs = new ArrayList<>(); - - public void removeViewportJob(Runnable job) { - mJobs.remove(job); - } - - public void clearAllViewportJobs() { - mJobs.clear(); - } - - /** - * Either posts a job immediately if the chart has already setup it's - * dimensions or adds the job to the execution queue. - */ - public void addViewportJob(Runnable job) { - - if (mViewPortHandler.hasChartDimens()) { - post(job); - } else { - mJobs.add(job); - } - } - - /** - * Returns all jobs that are scheduled to be executed after - * onSizeChanged(...). - */ - public ArrayList getJobs() { - return mJobs; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - - for (int i = 0; i < getChildCount(); i++) { - getChildAt(i).layout(left, top, right, bottom); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int size = (int) Utils.convertDpToPixel(50f); - setMeasuredDimension(Math.max(getSuggestedMinimumWidth(), resolveSize(size, widthMeasureSpec)), Math.max(getSuggestedMinimumHeight(), resolveSize(size, heightMeasureSpec))); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - if (mLogEnabled) { - Log.i(LOG_TAG, "OnSizeChanged()"); - } - - if (w > 0 && h > 0 && w < 10000 && h < 10000) { - if (mLogEnabled) { - Log.i(LOG_TAG, "Setting chart dimens, width: " + w + ", height: " + h); - } - mViewPortHandler.setChartDimens(w, h); - } else { - if (mLogEnabled) { - Log.w(LOG_TAG, "*Avoiding* setting chart dimens! width: " + w + ", height: " + h); - } - } - - // This may cause the chart view to mutate properties affecting the view port -- - // lets do this before we try to run any pending jobs on the view port itself - notifyDataSetChanged(); - - for (Runnable r : mJobs) { - post(r); - } - - mJobs.clear(); - - super.onSizeChanged(w, h, oldw, oldh); - } - - /** - * Setting this to true will set the layer-type HARDWARE for the view, false - * will set layer-type SOFTWARE. - */ - public void setHardwareAccelerationEnabled(boolean enabled) { - - if (enabled) { - setLayerType(View.LAYER_TYPE_HARDWARE, null); - } else { - setLayerType(View.LAYER_TYPE_SOFTWARE, null); - } - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - //Log.i(LOG_TAG, "Detaching..."); - - if (mUnbind) { - unbindDrawables(this); - } - } - - /** - * unbind flag - */ - private boolean mUnbind = false; - - /** - * Unbind all drawables to avoid memory leaks. - * Link: http://stackoverflow.com/a/6779164/1590502 - */ - private void unbindDrawables(View view) { - - if (view.getBackground() != null) { - view.getBackground().setCallback(null); - } - if (view instanceof ViewGroup) { - for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) { - unbindDrawables(((ViewGroup) view).getChildAt(i)); - } - ((ViewGroup) view).removeAllViews(); - } - } - - /** - * Set this to true to enable "unbinding" of drawables. When a View is detached - * from a window. This helps avoid memory leaks. - * Default: false - * Link: http://stackoverflow.com/a/6779164/1590502 - */ - public void setUnbindEnabled(boolean enabled) { - this.mUnbind = enabled; - } - - // region accessibility - - /** - * - * @return accessibility description must be created for each chart - */ - public abstract String getAccessibilityDescription(); - - public String getAccessibilitySummaryDescription() { - return accessibilitySummaryDescription; - } - - public void setAccessibilitySummaryDescription(String accessibilitySummaryDescription) { - this.accessibilitySummaryDescription = accessibilitySummaryDescription; - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) { - - boolean completed = super.dispatchPopulateAccessibilityEvent(event); - Log.d(LOG_TAG, "Dispatch called for Chart and completed as " + completed); - - event.getText().add(getAccessibilityDescription()); - - // Add the user generated summary after the dynamic summary is complete. - if (!TextUtils.isEmpty(this.getAccessibilitySummaryDescription())) { - event.getText().add(this.getAccessibilitySummaryDescription()); - } - - return true; - } - - // endregion -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/Chart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/Chart.kt new file mode 100644 index 0000000000..ead0dab1e6 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/Chart.kt @@ -0,0 +1,1488 @@ +package com.github.mikephil.charting.charts + +import android.content.ContentValues +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Bitmap.CompressFormat +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.Paint.Align +import android.graphics.RectF +import android.graphics.Typeface +import android.os.Environment +import android.provider.MediaStore +import android.text.TextUtils +import android.util.AttributeSet +import android.util.Log +import android.view.View +import android.view.ViewGroup +import android.view.accessibility.AccessibilityEvent +import androidx.core.graphics.createBitmap +import androidx.core.view.size +import com.github.mikephil.charting.animation.ChartAnimator +import com.github.mikephil.charting.animation.Easing.EasingFunction +import com.github.mikephil.charting.components.Description +import com.github.mikephil.charting.components.IMarker +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.data.ChartData +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.formatter.DefaultValueFormatter +import com.github.mikephil.charting.formatter.IValueFormatter +import com.github.mikephil.charting.highlight.ChartHighlighter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.highlight.IHighlighter +import com.github.mikephil.charting.interfaces.dataprovider.ChartInterface +import com.github.mikephil.charting.interfaces.datasets.IDataSet +import com.github.mikephil.charting.listener.ChartTouchListener +import com.github.mikephil.charting.listener.OnChartGestureListener +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.renderer.DataRenderer +import com.github.mikephil.charting.renderer.LegendRenderer +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Utils +import com.github.mikephil.charting.utils.ViewPortHandler +import java.io.File +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStream +import kotlin.math.abs +import kotlin.math.max + +/** + * Baseclass of all Chart-Views. + * + * @author Philipp Jahoda + */ +@Suppress("unused") +abstract class Chart, T : ChartData> : ViewGroup, ChartInterface { + /** + * Returns true if log-output is enabled for the chart, fals if not. + */ + /** + * Set this to true to enable logcat outputs for the chart. Beware that + * logcat output decreases rendering performance. Default: disabled. + */ + /** + * flag that indicates if logging is enabled or not + */ + var isLogEnabled: Boolean = false + + /** + * object that holds all data that was originally set for the chart, before + * it was modified or any filtering algorithms had been applied + */ + protected var mData: T? = null + + /** + * Returns true if values can be highlighted via tap gesture, false if not. + */ + /** + * Set this to false to prevent values from being highlighted by tap gesture. + * Values can still be highlighted via drag or programmatically. Default: true + */ + /** + * Flag that indicates if highlighting per tap (touch) is enabled + */ + var isHighlightPerTapEnabled: Boolean = true + + /** + * If set to true, chart continues to scroll after touch up default: true + */ + /** + * If set to true, chart continues to scroll after touch up. Default: true. + */ + /** + * If set to true, chart continues to scroll after touch up + */ + var isDragDecelerationEnabled: Boolean = true + + /** + * Deceleration friction coefficient in [0 ; 1] interval, higher values + * indicate that speed will decrease slowly, for example if it set to 0, it + * will stop immediately. 1 is an invalid value, and will be converted to + * 0.999f automatically. + */ + private var mDragDecelerationFrictionCoef = 0.9f + + /** + * default value-formatter, number of digits depends on provided chart-data + */ + protected var mDefaultValueFormatter: DefaultValueFormatter = DefaultValueFormatter(0) + + /** + * paint object used for drawing the description text in the bottom right + * corner of the chart + */ + protected var mDescPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + + /** + * paint object for drawing the information text when there are no values in + * the chart + */ + protected var mInfoPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + + /** + * the object representing the labels on the x-axis + */ + protected val mXAxis: XAxis = XAxis() + + /** + * if true, touch gestures are enabled on the chart + */ + protected var mTouchEnabled: Boolean = true + + /** + * Returns the Description object of the chart that is responsible for holding all information related + * to the description text that is displayed in the bottom right corner of the chart (by default). + */ + /** + * Sets a new Description object for the chart. + */ + /** + * the object responsible for representing the description text + */ + var description: Description = Description() + + /** + * Returns the Legend object of the chart. This method can be used to get an + * instance of the legend in order to customize the automatically generated + * Legend. + */ + /** + * the legend object containing all data associated with the legend + */ + val legend: Legend = Legend() + + /** + * listener that is called when a value on the chart is selected + */ + protected var mSelectionListener: OnChartValueSelectedListener? = null + + protected var mChartTouchListener: ChartTouchListener<*>? = null + + /** + * text that is displayed when the chart is empty + */ + private var mNoDataText = "No chart data available." + + /** + * Returns the custom gesture listener. + */ + /** + * Sets a gesture-listener for the chart for custom callbacks when executing + * gestures on the chart surface. + */ + /** + * Gesture listener for custom callbacks when making gestures on the chart. + */ + var onChartGestureListener: OnChartGestureListener? = null + + /** + * Returns the ViewPortHandler of the chart that is responsible for the + * content area of the chart and its offsets and dimensions. + */ + /** + * object that manages the bounds and drawing constraints of the chart + */ + var viewPortHandler: ViewPortHandler = ViewPortHandler() + protected set + + /** + * Returns the renderer object responsible for rendering / drawing the + * Legend. + */ + val legendRenderer: LegendRenderer = LegendRenderer(this.viewPortHandler, this.legend) + + /** + * object responsible for rendering the data + */ + protected var mRenderer: DataRenderer? = null + + var highlighter: IHighlighter? = null + protected set + + /** + * object responsible for animations + */ + protected val mAnimator = ChartAnimator { // ViewCompat.postInvalidateOnAnimation(Chart.this); + postInvalidate() + } + + /** + * Extra offsets to be appended to the viewport + */ + private var mExtraTopOffset = 0f + private var mExtraRightOffset = 0f + private var mExtraBottomOffset = 0f + private var mExtraLeftOffset = 0f + + /** + * Additional data on top of dynamically generated description. This can be set by the user. + */ + var accessibilitySummaryDescription: String? = "" + + /** + * default constructor for initialization in code + */ + constructor(context: Context) : super(context) { + } + + /** + * constructor for initialization in xml + */ + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + } + + /** + * even more awesome constructor + */ + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) { + } + + /** + * The maximum distance in dp away from an entry causing it to highlight. + */ + protected var mMaxHighlightDistance: Float = 0f + + /** + * initialize all paints and stuff + */ + init { + setWillNotDraw(false) + + // initialize the utils + Utils.init(context) + mMaxHighlightDistance = Utils.convertDpToPixel(500f) + + mInfoPaint.setColor(Color.rgb(247, 189, 51)) // orange + mInfoPaint.textAlign = Align.CENTER + mInfoPaint.textSize = Utils.convertDpToPixel(12f) + + if (this.isLogEnabled) { + Log.i("", "Chart.init()") + + // enable being detected by ScreenReader + setFocusable(true) + } + } + + // public void initWithDummyData() { + // ColorTemplate template = new ColorTemplate(); + // template.addColorsForDataSets(ColorTemplate.COLORFUL_COLORS, + // getContext()); + // + // setColorTemplate(template); + // setDrawYValues(false); + // + // ArrayList xVals = new ArrayList(); + // Calendar calendar = Calendar.getInstance(); + // for (int i = 0; i < 12; i++) { + // xVals.add(calendar.getDisplayName(Calendar.MONTH, Calendar.SHORT, + // Locale.getDefault())); + // } + // + // ArrayList dataSets = new ArrayList(); + // for (int i = 0; i < 3; i++) { + // + // ArrayList yVals = new ArrayList(); + // + // for (int j = 0; j < 12; j++) { + // float val = (float) (Math.random() * 100); + // yVals.add(new Entry(val, j)); + // } + // + // DataSet set = new DataSet(yVals, "DataSet " + i); + // dataSets.add(set); // add the datasets + // } + // // create a data object with the datasets + // ChartData data = new ChartData(xVals, dataSets); + // setData(data); + // invalidate(); + // } + /** + * Sets a new data object for the chart. The data object contains all values + * and information needed for displaying. + */ + open fun setData(data: T?) { + mData = data + mOffsetsCalculated = false + + if (data == null) { + return + } + + // calculate how many digits are needed + setupDefaultFormatter(data.yMin, data.yMax) + + // let the chart know there is new data + notifyDataSetChanged() + + if (this.isLogEnabled) { + Log.i(LOG_TAG, "Data is set.") + } + } + + /** + * Clears the chart from all data (sets it to null) and refreshes it (by + * calling invalidate()). + */ + fun clear() { + mData = null + mOffsetsCalculated = false + this.highlighted = null + mChartTouchListener?.setLastHighlighted(null) + invalidate() + } + + /** + * Removes all DataSets (and thereby Entries) from the chart. Does not set the data object to null. Also refreshes the + * chart by calling invalidate(). + */ + fun clearValues() { + mData?.clearValues() + invalidate() + } + + val isEmpty: Boolean + /** + * Returns true if the chart is empty (meaning it's data object is either + * null or contains no entries). + */ + get() { + return mData?.let { it.entryCount <= 0 } ?: true + } + + /** + * Lets the chart know its underlying data has changed and performs all + * necessary recalculations. It is crucial that this method is called + * everytime data is changed dynamically. Not calling this method can lead + * to crashes or unexpected behaviour. + */ + abstract fun notifyDataSetChanged() + + /** + * Calculates the offsets of the chart to the border depending on the + * position of an eventual legend or depending on the length of the y-axis + * and x-axis labels and their position + */ + protected abstract fun calculateOffsets() + + /** + * Calculates the y-min and y-max value and the y-delta and x-delta value + */ + protected abstract fun calcMinMax() + + /** + * Calculates the required number of digits for the values that might be + * drawn in the chart (if enabled), and creates the default-value-formatter + */ + protected fun setupDefaultFormatter(min: Float, max: Float) { + val reference = if (mData.let { it != null && it.entryCount < 2 }) { + max(abs(min), abs(max)) + } else { + abs(max - min) + } + + val digits = Utils.getDecimals(reference) + + // setup the formatter with a new number of digits + mDefaultValueFormatter.setup(digits) + } + + /** + * flag that indicates if offsets calculation has already been done or not + */ + private var mOffsetsCalculated = false + + override fun onDraw(canvas: Canvas) { + // super.onDraw(canvas); + + if (mData == null) { + val hasText = !TextUtils.isEmpty(mNoDataText) + + if (hasText) { + val pt = this.center + + when (mInfoPaint.textAlign) { + Align.LEFT -> { + pt.x = 0f + canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint) + } + + Align.RIGHT -> { + pt.x *= 2.0.toFloat() + canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint) + } + + else -> canvas.drawText(mNoDataText, pt.x, pt.y, mInfoPaint) + } + } + + return + } + + if (!mOffsetsCalculated) { + calculateOffsets() + mOffsetsCalculated = true + } + } + + /** + * Draws the description text in the bottom right corner of the chart (per default) + */ + protected fun drawDescription(c: Canvas) { + // check if description should be drawn + + if (description.isEnabled) { + val position = description.position + + mDescPaint.setTypeface(description.typeface) + mDescPaint.textSize = description.textSize + mDescPaint.setColor(description.textColor) + mDescPaint.textAlign = description.textAlign + + val x: Float + val y: Float + + // if no position specified, draw on default position + if (position == null) { + x = width - viewPortHandler.offsetRight() - description.xOffset + y = height - viewPortHandler.offsetBottom() - description.yOffset + } else { + x = position.x + y = position.y + } + + c.drawText(description.text, x, y, mDescPaint) + } + } + + /** + * Returns the array of currently highlighted values. This might a null or + * empty array if nothing is highlighted. + */ + /** + * array of Highlight objects that reference the highlighted slices in the + * chart + */ + var highlighted: Array? = null + protected set + + override val maxHighlightDistance: Float + get() = mMaxHighlightDistance + + /** + * Sets the maximum distance in screen dp a touch can be away from an entry to cause it to get highlighted. + * Default: 500dp + */ + fun setMaxHighlightDistance(distDp: Float) { + mMaxHighlightDistance = Utils.convertDpToPixel(distDp) + } + + /** + * Returns true if there are values to highlight, false if there are no + * values to highlight. Checks if the highlight array is null, has a length + * of zero or if the first object is null. + */ + fun valuesToHighlight(): Boolean { + return !highlighted.isNullOrEmpty() + } + + /** + * Sets the last highlighted value for the touchlistener. + */ + protected fun setLastHighlighted(highs: Array?) { + if (highs == null || highs.isEmpty()) { + mChartTouchListener?.setLastHighlighted(null) + } else { + mChartTouchListener?.setLastHighlighted(highs[0]) + } + } + + /** + * Highlights the values at the given indices in the given DataSets. Provide + * null or an empty array to undo all highlighting. This should be used to + * programmatically highlight values. + * This method *will not* call the listener. + */ + fun highlightValues(highs: Array?) { + // set the indices to highlight + + this.highlighted = highs + + setLastHighlighted(highs) + + // redraw the chart + invalidate() + } + + fun highlightValues(highs: MutableList, markers: MutableList) { + require(highs.size == markers.size) { "Markers and highs must be mutually corresponding. High size = " + highs.size + " Markers size = " + markers.size } + setMarkers(markers) + highlightValues(highs.toTypedArray()) + } + + /** + * Highlights any y-value at the given x-value in the given DataSet. + * Provide -1 as the dataSetIndex to undo all highlighting. + * + * @param x The x-value to highlight + * @param y The y-value to highlight. Supply `NaN` for "any" + * @param dataSetIndex The dataset index to search in + * @param dataIndex The data index to search in (only used in CombinedChartView currently) + * @param callListener Should the listener be called for this change + */ + /** + * Highlights the value at the given x-value and y-value in the given DataSet. + * Provide -1 as the dataSetIndex to undo all highlighting. + * This method will call the listener. + * + * @param x The x-value to highlight + * @param y The y-value to highlight. Supply `NaN` for "any" + * @param dataSetIndex The dataset index to search in + */ + /** + * Highlights the value at the given x-value and y-value in the given DataSet. + * Provide -1 as the dataSetIndex to undo all highlighting. + * This method will call the listener. + * + * @param x The x-value to highlight + * @param y The y-value to highlight. Supply `NaN` for "any" + * @param dataSetIndex The dataset index to search in + * @param dataIndex The data index to search in (only used in CombinedChartView currently) + */ + @JvmOverloads + open fun highlightValue(x: Float, y: Float = Float.NaN, dataSetIndex: Int = -1, dataIndex: Int = -1, callListener: Boolean = true) { + if (dataSetIndex < 0 || mData.let { it != null && dataSetIndex >= it.dataSetCount }) { + highlightValue(null, callListener) + } else { + highlightValue(Highlight(x, y, dataSetIndex, dataIndex), callListener) + } + } + + /** + * Highlights any y-value at the given x-value in the given DataSet. + * Provide -1 as the dataSetIndex to undo all highlighting. + * + * @param x The x-value to highlight + * @param y The y-value to highlight. Supply `NaN` for "any" + * @param dataSetIndex The dataset index to search in + * @param callListener Should the listener be called for this change + */ + fun highlightValue(x: Float, y: Float, dataSetIndex: Int, callListener: Boolean) { + highlightValue(x, y, dataSetIndex, -1, callListener) + } + + /** + * Highlights the values represented by the provided Highlight object + * This method *will not* call the listener. + * + * @param highlight contains information about which entry should be highlighted + */ + fun highlightValue(highlight: Highlight?) { + highlightValue(highlight, false) + } + + /** + * Highlights the value selected by touch gesture. Unlike + * highlightValues(...), this generates a callback to the + * OnChartValueSelectedListener. + * + * @param high - the highlight object + * @param callListener - call the listener + */ + fun highlightValue(high: Highlight?, callListener: Boolean) { + var high = high + var e: E? = null + + if (high == null) { + this.highlighted = null + } else { + if (this.isLogEnabled) { + Log.i(LOG_TAG, "Highlighted: $high") + } + + e = mData?.getEntryForHighlight(high) + if (e == null) { + this.highlighted = null + high = null + } else { + // set the indices to highlight + + this.highlighted = arrayOf(high) + } + } + + setLastHighlighted(this.highlighted) + + if (callListener && mSelectionListener != null) { + if (!valuesToHighlight()) { + mSelectionListener?.onNothingSelected() + } else { + // notify the listener + mSelectionListener?.onValueSelected(e, high) + } + } + + // redraw the chart + invalidate() + } + + /** + * Returns the Highlight object (contains x-index and DataSet index) of the + * selected value at the given touch point inside the Line-, Scatter-, or + * CandleStick-Chart. + */ + open fun getHighlightByTouchPoint(x: Float, y: Float): Highlight? { + if (mData == null) { + Log.e(LOG_TAG, "Can't select by touch. No data set.") + return null + } else { + return this.highlighter?.getHighlight(x, y) + } + } + + var onTouchListener: ChartTouchListener<*>? + /** + * Returns an instance of the currently active touch listener. + */ + get() = mChartTouchListener + /** + * Set a new (e.g. custom) ChartTouchListener NOTE: make sure to + * setTouchEnabled(true); if you need touch gestures on the chart + */ + set(l) { + this.mChartTouchListener = l + } + + /** + * returns true if drawing the marker is enabled when tapping on values + * (use the setMarker(IMarker marker) method to specify a marker) + */ + /** + * if set to true, the marker view is drawn when a value is clicked + */ + var isDrawMarkersEnabled: Boolean = true + protected set + + /** + * returns the marker that is set as a marker view for the chart + * + */ + /** + * the view that represents the marker + */ + var marker: MutableList = ArrayList() + protected set + + /** + * draws all MarkerViews on the highlighted positions + */ + protected open fun drawMarkers(canvas: Canvas?) { + // if there is no marker view or drawing marker is disabled + + if (!this.isDrawMarkersEnabled || !valuesToHighlight()) { + return + } + + val highlighted = highlighted ?: return + val data = mData ?: return + + for (i in highlighted.indices) { + val highlight = highlighted[i] + + // When changing data sets and calling animation functions, sometimes an erroneous highlight is generated + // on the dataset that is removed. Null check to prevent crash + val set = data.getDataSetByIndex(highlight.dataSetIndex) + if (!set.isVisible) { + continue + } + + val e = data.getEntryForHighlight(highlight) + + // make sure entry not null before using it + if (e == null) { + continue + } + val entryIndex = set.getEntryIndex(e) + + if (entryIndex > set.entryCount * mAnimator.phaseX) { + continue + } + + val pos = getMarkerPosition(highlight) + + // check bounds + if (!viewPortHandler.isInBounds(pos[0], pos[1])) { + continue + } + + // callbacks to update the content + if (!marker.isEmpty()) { + val markerIndex = i % marker.size + val markerItem = marker[markerIndex] + markerItem.refreshContent(e, highlight) + + // draw the marker + markerItem.draw(canvas, pos[0], pos[1]) + } + } + } + + /** + * Returns the actual position in pixels of the MarkerView for the given + * Highlight object. + */ + protected open fun getMarkerPosition(high: Highlight): FloatArray { + return floatArrayOf(high.drawX, high.drawY) + } + + val animator: ChartAnimator + /** + * Returns the animator responsible for animating chart values. + */ + get() = mAnimator + + var dragDecelerationFrictionCoef: Float + /** + * Returns drag deceleration friction coefficient + */ + get() = mDragDecelerationFrictionCoef + /** + * Deceleration friction coefficient in [0 ; 1] interval, higher values + * indicate that speed will decrease slowly, for example if it set to 0, it + * will stop immediately. 1 is an invalid value, and will be converted to + * 0.999f automatically. + */ + set(newValue) { + var newValue = newValue + if (newValue < 0f) { + newValue = 0f + } + + if (newValue >= 1f) { + newValue = 0.999f + } + + mDragDecelerationFrictionCoef = newValue + } + + /** + * Animates the drawing / rendering of the chart on both x- and y-axis with + * the specified animation time. If animate(...) is called, no further + * calling of invalidate() is necessary to refresh the chart. ANIMATIONS + * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. + * + * @param easingX a custom easing function to be used on the animation phase + * @param easingY a custom easing function to be used on the animation phase + */ + fun animateXY(durationMillisX: Int, durationMillisY: Int, easingX: EasingFunction?, easingY: EasingFunction?) { + mAnimator.animateXY(durationMillisX, durationMillisY, easingX, easingY) + } + + /** + * Animates the drawing / rendering of the chart on both x- and y-axis with + * the specified animation time. If animate(...) is called, no further + * calling of invalidate() is necessary to refresh the chart. ANIMATIONS + * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. + * + * @param easing a custom easing function to be used on the animation phase + */ + fun animateXY(durationMillisX: Int, durationMillisY: Int, easing: EasingFunction?) { + mAnimator.animateXY(durationMillisX, durationMillisY, easing) + } + + /** + * Animates the rendering of the chart on the x-axis with the specified + * animation time. If animate(...) is called, no further calling of + * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR + * API LEVEL 11 (Android 3.0.x) AND HIGHER. + * + * @param easing a custom easing function to be used on the animation phase + */ + fun animateX(durationMillis: Int, easing: EasingFunction?) { + mAnimator.animateX(durationMillis, easing) + } + + /** + * Animates the rendering of the chart on the y-axis with the specified + * animation time. If animate(...) is called, no further calling of + * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR + * API LEVEL 11 (Android 3.0.x) AND HIGHER. + * + * @param easing a custom easing function to be used on the animation phase + */ + fun animateY(durationMillis: Int, easing: EasingFunction?) { + mAnimator.animateY(durationMillis, easing) + } + + /** + * Animates the rendering of the chart on the x-axis with the specified + * animation time. If animate(...) is called, no further calling of + * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR + * API LEVEL 11 (Android 3.0.x) AND HIGHER. + */ + fun animateX(durationMillis: Int) { + mAnimator.animateX(durationMillis) + } + + /** + * Animates the rendering of the chart on the y-axis with the specified + * animation time. If animate(...) is called, no further calling of + * invalidate() is necessary to refresh the chart. ANIMATIONS ONLY WORK FOR + * API LEVEL 11 (Android 3.0.x) AND HIGHER. + */ + fun animateY(durationMillis: Int) { + mAnimator.animateY(durationMillis) + } + + /** + * Animates the drawing / rendering of the chart on both x- and y-axis with + * the specified animation time. If animate(...) is called, no further + * calling of invalidate() is necessary to refresh the chart. ANIMATIONS + * ONLY WORK FOR API LEVEL 11 (Android 3.0.x) AND HIGHER. + */ + fun animateXY(durationMillisX: Int, durationMillisY: Int) { + mAnimator.animateXY(durationMillisX, durationMillisY) + } + + + open val xAxis: XAxis + /** + * Returns the object representing all x-labels, this method can be used to + * acquire the XAxis object and modify it (e.g. change the position of the + * labels, styling, etc.) + */ + get() = mXAxis + + /** + * Returns the default IValueFormatter that has been determined by the chart + * considering the provided minimum and maximum values. + */ + override val defaultValueFormatter: IValueFormatter? + get() = mDefaultValueFormatter + + /** + * set a selection listener for the chart + */ + fun setOnChartValueSelectedListener(l: OnChartValueSelectedListener?) { + this.mSelectionListener = l + } + + val yMax: Float? + /** + * returns the current y-max value across all DataSets + */ + get() = mData?.yMax + + val yMin: Float? + /** + * returns the current y-min value across all DataSets + */ + get() = mData?.yMin + + override val xChartMax: Float + get() = mXAxis.mAxisMaximum + + override val xChartMin: Float + get() = mXAxis.axisMinimum + + override val xRange: Float + get() = mXAxis.mAxisRange + + val center: MPPointF + /** + * Returns a recyclable MPPointF instance. + * Returns the center point of the chart (the whole View) in pixels. + */ + get() = MPPointF.Companion.getInstance(width / 2f, height / 2f) + + /** + * Returns a recyclable MPPointF instance. + * Returns the center of the chart taking offsets under consideration. + * (returns the center of the content rectangle) + */ + override val centerOffsets: MPPointF + get() = viewPortHandler.contentCenter + + /** + * Sets extra offsets (around the chart view) to be appended to the + * auto-calculated offsets. + */ + fun setExtraOffsets(left: Float, top: Float, right: Float, bottom: Float) { + this.extraLeftOffset = left + this.extraTopOffset = top + this.extraRightOffset = right + this.extraBottomOffset = bottom + } + + var extraTopOffset: Float + /** + * @return the extra offset to be appended to the viewport's top + */ + get() = mExtraTopOffset + /** + * Set an extra offset to be appended to the viewport's top + */ + set(offset) { + mExtraTopOffset = Utils.convertDpToPixel(offset) + } + + var extraRightOffset: Float + /** + * @return the extra offset to be appended to the viewport's right + */ + get() = mExtraRightOffset + /** + * Set an extra offset to be appended to the viewport's right + */ + set(offset) { + mExtraRightOffset = Utils.convertDpToPixel(offset) + } + + var extraBottomOffset: Float + /** + * @return the extra offset to be appended to the viewport's bottom + */ + get() = mExtraBottomOffset + /** + * Set an extra offset to be appended to the viewport's bottom + */ + set(offset) { + mExtraBottomOffset = Utils.convertDpToPixel(offset) + } + + var extraLeftOffset: Float + /** + * @return the extra offset to be appended to the viewport's left + */ + get() = mExtraLeftOffset + /** + * Set an extra offset to be appended to the viewport's left + */ + set(offset) { + mExtraLeftOffset = Utils.convertDpToPixel(offset) + } + + /** + * Sets the text that informs the user that there is no data available with + * which to draw the chart. + */ + fun setNoDataText(text: String) { + mNoDataText = text + } + + /** + * Sets the color of the no data text. + */ + fun setNoDataTextColor(color: Int) { + mInfoPaint.setColor(color) + } + + /** + * Sets the typeface to be used for the no data text. + */ + fun setNoDataTextTypeface(tf: Typeface?) { + mInfoPaint.setTypeface(tf) + } + + /** + * alignment of the no data text + */ + fun setNoDataTextAlignment(align: Align?) { + mInfoPaint.textAlign = align + } + + /** + * Set this to false to disable all gestures and touches on the chart, + * default: true + */ + fun setTouchEnabled(enabled: Boolean) { + this.mTouchEnabled = enabled + } + + fun setMarkers(marker: MutableList) { + this.marker = marker + } + + /** + * sets the marker that is displayed when a value is clicked on the chart + */ + fun setMarker(marker: IMarker?) { + setMarkers(marker?.let { mutableListOf(it) } ?: mutableListOf()) + } + + @Deprecated("") + fun setMarkerView(v: IMarker?) { + v?.let { setMarker(it) } ?: setMarkers(mutableListOf()) + } + + @get:Deprecated("") + val markerView: MutableList? + get() = this.marker + + /** + * Returns the rectangle that defines the borders of the chart-value surface + * (into which the actual values are drawn). + */ + override val contentRect: RectF? + get() = viewPortHandler.contentRect + + /** + * disables intercept touchevents + */ + fun disableScroll() { + val parent = getParent() + parent?.requestDisallowInterceptTouchEvent(true) + } + + /** + * enables intercept touchevents + */ + fun enableScroll() { + val parent = getParent() + parent?.requestDisallowInterceptTouchEvent(false) + } + + /** + * set a new paint object for the specified parameter in the chart e.g. + * Chart.PAINT_VALUES + * + * @param p the new paint object + * @param which Chart.PAINT_VALUES, Chart.PAINT_GRID, Chart.PAINT_VALUES, + * ... + */ + open fun setPaint(p: Paint, which: Int) { + when (which) { + PAINT_INFO -> mInfoPaint = p + PAINT_DESCRIPTION -> mDescPaint = p + } + } + + /** + * Returns the paint object associated with the provided constant. + * + * @param which e.g. Chart.PAINT_LEGEND_LABEL + */ + open fun getPaint(which: Int): Paint? { + when (which) { + PAINT_INFO -> return mInfoPaint + PAINT_DESCRIPTION -> return mDescPaint + } + + return null + } + + @get:Deprecated("") + val isDrawMarkerViewsEnabled: Boolean + get() = this.isDrawMarkersEnabled + + @Deprecated("") + fun setDrawMarkerViews(enabled: Boolean) { + setDrawMarkers(enabled) + } + + /** + * Set this to true to draw a user specified marker when tapping on + * chart values (use the setMarker(IMarker marker) method to specify a + * marker). Default: true + */ + fun setDrawMarkers(enabled: Boolean) { + this.isDrawMarkersEnabled = enabled + } + + /** + * Returns the ChartData object that has been set for the chart. + */ + override val data: T? + get() = mData + + var renderer: DataRenderer? + /** + * Returns the Renderer object the chart uses for drawing data. + */ + get() = mRenderer + /** + * Sets a new DataRenderer object for the chart. + */ + set(renderer) { + if (renderer != null) { + mRenderer = renderer + } + } + + /** + * Sets a custom highligher object for the chart that handles / processes + * all highlight touch events performed on the chart-view. + */ + fun setHighlighter(highlighter: ChartHighlighter<*>?) { + this.highlighter = highlighter + } + + /** + * Returns a recyclable MPPointF instance. + */ + override val centerOfView: MPPointF? + get() = this.center + + val chartBitmap: Bitmap + /** + * Returns the bitmap that represents the chart. + */ + get() { + // Define a bitmap with the same size as the view + val returnedBitmap = createBitmap(width, height, Bitmap.Config.RGB_565) + // Bind a canvas to it + val canvas = Canvas(returnedBitmap) + // Get the view's background + val bgDrawable = background + if (bgDrawable != null) // has background drawable, then draw it on the canvas + { + bgDrawable.draw(canvas) + } else // does not have background drawable, then draw white background on + // the canvas + { + canvas.drawColor(Color.WHITE) + } + // draw the view on the canvas + draw(canvas) + // return the bitmap + return returnedBitmap + } + + /** + * Saves the current chart state with the given name to the given path on + * the sdcard leaving the path empty "" will put the saved file directly on + * the SD card chart is saved as a PNG image, example: + * saveToPath("myfilename", "foldername1/foldername2"); + * + * @param pathOnSD e.g. "folder1/folder2/folder3" + * @return returns true on success, false on error + */ + fun saveToPath(title: String?, pathOnSD: String?): Boolean { + val b = this.chartBitmap + + val stream: OutputStream? + try { + stream = FileOutputStream(Environment.getExternalStorageDirectory().path + pathOnSD + "/" + title + ".png") + + /* + * Write bitmap to file using JPEG or PNG and 40% quality hint for + * JPEG. + */ + b.compress(CompressFormat.PNG, 40, stream) + + stream.close() + } catch (e: Exception) { + e.printStackTrace() + return false + } + + return true + } + + /** + * Saves the current state of the chart to the gallery as an image type. The + * compression must be set for JPEG only. 0 == maximum compression, 100 = low + * compression (high quality). NOTE: Needs permission WRITE_EXTERNAL_STORAGE + * + * @param fileName e.g. "my_image" + * @param subFolderPath e.g. "ChartPics" + * @param fileDescription e.g. "Chart details" + * @param format e.g. Bitmap.CompressFormat.PNG + * @param quality e.g. 50, min = 0, max = 100 + * @return returns true if saving was successful, false if not + */ + /** + * Saves the current state of the chart to the gallery as a PNG image. + * NOTE: Needs permission WRITE_EXTERNAL_STORAGE + * + * @param fileName e.g. "my_image" + * @return returns true if saving was successful, false if not + */ + @JvmOverloads + fun saveToGallery( + fileName: String, + subFolderPath: String? = "", + fileDescription: String? = "MPAndroidChart-Library Save", + format: CompressFormat = CompressFormat.PNG, + quality: Int = 40 + ): Boolean { + // restrain quality + var fileName = fileName + var quality = quality + if (quality < 0 || quality > 100) { + quality = 50 + } + + val currentTime = System.currentTimeMillis() + + val extBaseDir = Environment.getExternalStorageDirectory() + val file = File(extBaseDir.absolutePath + "/DCIM/" + subFolderPath) + if (!file.exists()) { + if (!file.mkdirs()) { + return false + } + } + + val mimeType: String? + when (format) { + CompressFormat.PNG -> { + mimeType = "image/png" + if (!fileName.endsWith(".png")) { + fileName += ".png" + } + } + + CompressFormat.WEBP -> { + mimeType = "image/webp" + if (!fileName.endsWith(".webp")) { + fileName += ".webp" + } + } + + CompressFormat.JPEG -> { + mimeType = "image/jpeg" + if (!(fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))) { + fileName += ".jpg" + } + } + + else -> { + mimeType = "image/jpeg" + if (!(fileName.endsWith(".jpg") || fileName.endsWith(".jpeg"))) { + fileName += ".jpg" + } + } + } + + val filePath = file.absolutePath + "/" + fileName + val out: FileOutputStream? + try { + out = FileOutputStream(filePath) + + val b = this.chartBitmap + b.compress(format, quality, out) + + out.flush() + out.close() + } catch (e: IOException) { + e.printStackTrace() + + return false + } + + val size = File(filePath).length() + + val values = ContentValues(8) + + // store the details + values.put(MediaStore.Images.Media.TITLE, fileName) + values.put(MediaStore.Images.Media.DISPLAY_NAME, fileName) + values.put(MediaStore.Images.Media.DATE_ADDED, currentTime) + values.put(MediaStore.Images.Media.MIME_TYPE, mimeType) + values.put(MediaStore.Images.Media.DESCRIPTION, fileDescription) + values.put(MediaStore.Images.Media.ORIENTATION, 0) + values.put(MediaStore.Images.Media.DATA, filePath) + values.put(MediaStore.Images.Media.SIZE, size) + + return context.contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values) != null + } + + /** + * Saves the current state of the chart to the gallery as a JPEG image. The + * filename and compression can be set. 0 == maximum compression, 100 = low + * compression (high quality). NOTE: Needs permission WRITE_EXTERNAL_STORAGE + * + * @param fileName e.g. "my_image" + * @param quality e.g. 50, min = 0, max = 100 + * @return returns true if saving was successful, false if not + */ + fun saveToGallery(fileName: String, quality: Int): Boolean { + return saveToGallery(fileName, "", "MPAndroidChart-Library Save", CompressFormat.PNG, quality) + } + + /** + * Returns all jobs that are scheduled to be executed after + * onSizeChanged(...). + */ + /** + * tasks to be done after the view is setup + */ + var jobs: ArrayList = ArrayList() + protected set + + fun removeViewportJob(job: Runnable?) { + jobs.remove(job) + } + + fun clearAllViewportJobs() { + jobs.clear() + } + + /** + * Either posts a job immediately if the chart has already setup it's + * dimensions or adds the job to the execution queue. + */ + fun addViewportJob(job: Runnable?) { + if (viewPortHandler.hasChartDimens()) { + post(job) + } else { + jobs.add(job) + } + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + for (i in 0.. 0 && h > 0 && w < 10000 && h < 10000) { + if (this.isLogEnabled) { + Log.i(LOG_TAG, "Setting chart dimens, width: $w, height: $h") + } + viewPortHandler.setChartDimens(w.toFloat(), h.toFloat()) + } else { + if (this.isLogEnabled) { + Log.w(LOG_TAG, "*Avoiding* setting chart dimens! width: $w, height: $h") + } + } + + // This may cause the chart view to mutate properties affecting the view port -- + // lets do this before we try to run any pending jobs on the view port itself + notifyDataSetChanged() + + for (r in this.jobs) { + post(r) + } + + jobs.clear() + + super.onSizeChanged(w, h, oldw, oldh) + } + + /** + * Setting this to true will set the layer-type HARDWARE for the view, false + * will set layer-type SOFTWARE. + */ + fun setHardwareAccelerationEnabled(enabled: Boolean) { + if (enabled) { + setLayerType(LAYER_TYPE_HARDWARE, null) + } else { + setLayerType(LAYER_TYPE_SOFTWARE, null) + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + + //Log.i(LOG_TAG, "Detaching..."); + if (mUnbind) { + unbindDrawables(this) + } + } + + /** + * unbind flag + */ + private var mUnbind = false + + /** + * Unbind all drawables to avoid memory leaks. + * Link: http://stackoverflow.com/a/6779164/1590502 + */ + private fun unbindDrawables(view: View) { + if (view.background != null) { + view.background.callback = null + } + if (view is ViewGroup) { + for (i in 0.. and completed as $completed") + + event.text.add(this.accessibilityDescription) + + // Add the user generated summary after the dynamic summary is complete. + if (!TextUtils.isEmpty(this.accessibilitySummaryDescription)) { + event.text.add(this.accessibilitySummaryDescription) + } + + return true + } // endregion + + companion object { + const val LOG_TAG: String = "MPAndroidChart" + + /** + * paint for the grid background (only line and barchart) + */ + const val PAINT_GRID_BACKGROUND: Int = 4 + + /** + * paint for the info text that is displayed when there are no values in the + * chart + */ + const val PAINT_INFO: Int = 7 + + /** + * paint for the description text in the bottom right corner + */ + const val PAINT_DESCRIPTION: Int = 11 + + /** + * paint for the hole in the middle of the pie chart + */ + const val PAINT_HOLE: Int = 13 + + /** + * paint for the text in the middle of the pie chart + */ + const val PAINT_CENTER_TEXT: Int = 14 + + /** + * paint used for the legend + */ + const val PAINT_LEGEND_LABEL: Int = 18 + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.java deleted file mode 100644 index 40b7d5b70a..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.java +++ /dev/null @@ -1,278 +0,0 @@ - -package com.github.mikephil.charting.charts; - -import android.content.Context; -import android.graphics.Canvas; -import android.util.AttributeSet; -import android.util.Log; - -import com.github.mikephil.charting.components.IMarker; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BubbleData; -import com.github.mikephil.charting.data.CandleData; -import com.github.mikephil.charting.data.CombinedData; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.ScatterData; -import com.github.mikephil.charting.highlight.CombinedHighlighter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.dataprovider.CombinedDataProvider; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.renderer.CombinedChartRenderer; - -/** - * This chart class allows the combination of lines, bars, scatter and candle - * data all displayed in one chart area. - * - * @author Philipp Jahoda - */ -@SuppressWarnings("unused") -public class CombinedChart extends BarLineChartBase implements CombinedDataProvider { - - /** - * if set to true, all values are drawn above their bars, instead of below - * their top - */ - private boolean mDrawValueAboveBar = true; - - - /** - * flag that indicates whether the highlight should be full-bar oriented, or single-value? - */ - protected boolean mHighlightFullBarEnabled = false; - - /** - * if set to true, a grey area is drawn behind each bar that indicates the - * maximum value - */ - private boolean mDrawBarShadow = false; - - protected DrawOrder[] mDrawOrder; - - /** - * enum that allows to specify the order in which the different data objects - * for the combined-chart are drawn - */ - public enum DrawOrder { - BAR, BUBBLE, LINE, CANDLE, SCATTER - } - - public CombinedChart(Context context) { - super(context); - } - - public CombinedChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public CombinedChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void init() { - super.init(); - - // Default values are not ready here yet - mDrawOrder = new DrawOrder[]{ - DrawOrder.BAR, DrawOrder.BUBBLE, DrawOrder.LINE, DrawOrder.CANDLE, DrawOrder.SCATTER - }; - - setHighlighter(new CombinedHighlighter(this, this)); - - // Old default behaviour - setHighlightFullBarEnabled(true); - - mRenderer = new CombinedChartRenderer(this, mAnimator, mViewPortHandler); - } - - @Override - public CombinedData getCombinedData() { - return mData; - } - - @Override - public void setData(CombinedData data) { - super.setData(data); - setHighlighter(new CombinedHighlighter(this, this)); - ((CombinedChartRenderer) mRenderer).createRenderers(); - mRenderer.initBuffers(); - } - - /** - * Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch - * point - * inside the CombinedChart. - */ - @Override - public Highlight getHighlightByTouchPoint(float x, float y) { - - if (mData == null) { - Log.e(LOG_TAG, "Can't select by touch. No data set."); - return null; - } else { - Highlight h = getHighlighter().getHighlight(x, y); - if (h == null || !isHighlightFullBarEnabled()) { - return h; - } - - // For isHighlightFullBarEnabled, remove stackIndex - return new Highlight(h.getX(), h.getY(), - h.getXPx(), h.getYPx(), - h.getDataSetIndex(), -1, h.getAxis()); - } - } - - @Override - public LineData getLineData() { - if (mData == null) { - return null; - } - return mData.getLineData(); - } - - @Override - public BarData getBarData() { - if (mData == null) { - return null; - } - return mData.getBarData(); - } - - @Override - public ScatterData getScatterData() { - if (mData == null) { - return null; - } - return mData.getScatterData(); - } - - @Override - public CandleData getCandleData() { - if (mData == null) { - return null; - } - return mData.getCandleData(); - } - - @Override - public BubbleData getBubbleData() { - if (mData == null) { - return null; - } - return mData.getBubbleData(); - } - - @Override - public boolean isDrawBarShadowEnabled() { - return mDrawBarShadow; - } - - @Override - public boolean isDrawValueAboveBarEnabled() { - return mDrawValueAboveBar; - } - - /** - * If set to true, all values are drawn above their bars, instead of below - * their top. - */ - public void setDrawValueAboveBar(boolean enabled) { - mDrawValueAboveBar = enabled; - } - - - /** - * If set to true, a grey area is drawn behind each bar that indicates the - * maximum value. Enabling his will reduce performance by about 50%. - */ - public void setDrawBarShadow(boolean enabled) { - mDrawBarShadow = enabled; - } - - /** - * Set this to true to make the highlight operation full-bar oriented, - * false to make it highlight single values (relevant only for stacked). - */ - public void setHighlightFullBarEnabled(boolean enabled) { - mHighlightFullBarEnabled = enabled; - } - - /** - * @return true the highlight operation is be full-bar oriented, false if single-value - */ - @Override - public boolean isHighlightFullBarEnabled() { - return mHighlightFullBarEnabled; - } - - /** - * Returns the currently set draw order. - */ - public DrawOrder[] getDrawOrder() { - return mDrawOrder; - } - - /** - * Sets the order in which the provided data objects should be drawn. The - * earlier you place them in the provided array, the further they will be in - * the background. e.g. if you provide new DrawOrer[] { DrawOrder.BAR, - * DrawOrder.LINE }, the bars will be drawn behind the lines. - */ - public void setDrawOrder(DrawOrder[] order) { - if (order == null || order.length <= 0) { - return; - } - mDrawOrder = order; - } - - /** - * draws all MarkerViews on the highlighted positions - */ - protected void drawMarkers(Canvas canvas) { - - // if there is no marker view or drawing marker is disabled - if (mMarkers == null || !isDrawMarkersEnabled() || !valuesToHighlight()) { - return; - } - - for (int i = 0; i < mIndicesToHighlight.length; i++) { - - Highlight highlight = mIndicesToHighlight[i]; - IDataSet set = mData.getDataSetByHighlight(highlight); - - Entry e = mData.getEntryForHighlight(highlight); - if (e == null || set == null) { - continue; - } - - int entryIndex = set.getEntryIndex(e); - - // make sure entry not null - if (entryIndex > set.getEntryCount() * mAnimator.getPhaseX()) { - continue; - } - - float[] pos = getMarkerPosition(highlight); - - // check bounds - if (!mViewPortHandler.isInBounds(pos[0], pos[1])) { - continue; - } - - // callbacks to update the content - if (!mMarkers.isEmpty()) { - IMarker markerItem = mMarkers.get(i % mMarkers.size()); - markerItem.refreshContent(e, highlight); - - // draw the marker - markerItem.draw(canvas, pos[0], pos[1]); - } - } - } - - @Override - public String getAccessibilityDescription() { - return "This is a combined chart"; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.kt new file mode 100644 index 0000000000..4684ea7d5b --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/CombinedChart.kt @@ -0,0 +1,226 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.util.Log +import com.github.mikephil.charting.components.IMarker +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BubbleData +import com.github.mikephil.charting.data.CandleData +import com.github.mikephil.charting.data.CombinedData +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.ScatterData +import com.github.mikephil.charting.highlight.CombinedHighlighter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.dataprovider.CombinedDataProvider +import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet +import com.github.mikephil.charting.renderer.CombinedChartRenderer + + +/** + * This chart class allows the combination of lines, bars, scatter and candle + * data all displayed in one chart area. + * + * @author Philipp Jahoda + */ +@Suppress("unused") +open class CombinedChart : BarLineChartBase, CombinedData>, CombinedDataProvider { + /** + * if set to true, all values are drawn above their bars, instead of below + * their top + */ + private var mDrawValueAboveBar = true + + + /** + * flag that indicates whether the highlight should be full-bar oriented, or single-value? + */ + protected var mHighlightFullBarEnabled: Boolean = false + + /** + * if set to true, a grey area is drawn behind each bar that indicates the + * maximum value + */ + private var mDrawBarShadow = false + + protected var mDrawOrder: Array = arrayOf( + DrawOrder.BAR, DrawOrder.BUBBLE, DrawOrder.LINE, DrawOrder.CANDLE, DrawOrder.SCATTER + ) + + /** + * enum that allows to specify the order in which the different data objects + * for the combined-chart are drawn + */ + enum class DrawOrder { + BAR, BUBBLE, LINE, CANDLE, SCATTER + } + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + init { + setHighlighter(CombinedHighlighter(this, this)) + + // Old default behaviour + mHighlightFullBarEnabled = true + + mRenderer = CombinedChartRenderer(this, mAnimator, viewPortHandler) + } + + override val combinedData: CombinedData? + get() = mData + + override fun setData(data: CombinedData?) { + super.setData(data) + setHighlighter(CombinedHighlighter(this, this)) + (mRenderer as? CombinedChartRenderer)?.createRenderers() + mRenderer?.initBuffers() + } + + /** + * Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch + * point + * inside the CombinedChart. + */ + override fun getHighlightByTouchPoint(x: Float, y: Float): Highlight? { + if (mData == null) { + Log.e(LOG_TAG, "Can't select by touch. No data set.") + return null + } else { + val h = highlighter?.getHighlight(x, y) + if (h == null || !isHighlightFullBarEnabled) { + return h + } + + // For isHighlightFullBarEnabled, remove stackIndex + return Highlight( + h.x, h.y, + h.xPx, h.yPx, + h.dataSetIndex, -1, h.axis + ) + } + } + + override var lineData: LineData? + get() = mData?.lineData + set(value) { + mData?.setData(value) + } + + override var barData: BarData? + get() = mData?.barData + set(value) { + mData?.setData(value) + } + + override var scatterData: ScatterData? + get() = mData?.scatterData + set(value) { + mData?.setData(value) + } + + override var candleData: CandleData? + get() = mData?.candleData + set(value) { + mData?.setData(value) + } + + override var bubbleData: BubbleData? + get() = mData?.bubbleData + set(value) { + mData?.setData(value) + } + + override var isDrawBarShadowEnabled: Boolean + get() = mDrawBarShadow + set(value) { + mDrawBarShadow = value + } + + override var isDrawValueAboveBarEnabled: Boolean + get() = mDrawValueAboveBar + set(value) { + mDrawValueAboveBar = value + } + + /** + * @return true the highlight operation is be full-bar oriented, false if single-value + */ + override var isHighlightFullBarEnabled: Boolean + get() = mHighlightFullBarEnabled + set(value) { + mHighlightFullBarEnabled = value + } + + var drawOrder: Array + /** + * Returns the currently set draw order. + */ + get() = mDrawOrder + /** + * Sets the order in which the provided data objects should be drawn. The + * earlier you place them in the provided array, the further they will be in + * the background. e.g. if you provide new DrawOrer[] { DrawOrder.BAR, + * DrawOrder.LINE }, the bars will be drawn behind the lines. + */ + set(order) { + if (order.isEmpty()) { + return + } + mDrawOrder = order + } + + /** + * draws all MarkerViews on the highlighted positions + */ + override fun drawMarkers(canvas: Canvas?) { + // if there is no marker view or drawing marker is disabled + + if (!isDrawMarkersEnabled || !valuesToHighlight()) { + return + } + + val highlighted = highlighted ?: return + + for (i in highlighted.indices) { + val highlight = highlighted[i] + val set = mData?.getDataSetByHighlight(highlight) + + val e = mData?.getEntryForHighlight(highlight) + if (e == null || set == null) { + continue + } + + val entryIndex = set.getEntryIndex(e) + + // make sure entry not null + if (entryIndex > set.entryCount * mAnimator.phaseX) { + continue + } + + val pos = getMarkerPosition(highlight) + + // check bounds + if (!viewPortHandler.isInBounds(pos[0], pos[1])) { + continue + } + + // callbacks to update the content + if (!marker.isEmpty()) { + val markerItem: IMarker = marker[i % marker.size] + markerItem.refreshContent(e, highlight) + + // draw the marker + markerItem.draw(canvas, pos[0], pos[1]) + } + } + } + + override val accessibilityDescription: String? + get() = "This is a combined chart" +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.java deleted file mode 100644 index 18d796b4c4..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.java +++ /dev/null @@ -1,346 +0,0 @@ -package com.github.mikephil.charting.charts; - -import android.content.Context; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.util.Log; - -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.highlight.HorizontalBarHighlighter; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer; -import com.github.mikephil.charting.renderer.XAxisRendererHorizontalBarChart; -import com.github.mikephil.charting.renderer.YAxisRendererHorizontalBarChart; -import com.github.mikephil.charting.utils.HorizontalViewPortHandler; -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.TransformerHorizontalBarChart; -import com.github.mikephil.charting.utils.Utils; - -/** - * BarChart with horizontal bar orientation. In this implementation, x- and y-axis are switched, meaning the YAxis class - * represents the horizontal values and the XAxis class represents the vertical values. - * - * @author Philipp Jahoda - */ -public class HorizontalBarChart extends BarChart { - - public HorizontalBarChart(Context context) { - super(context); - } - - public HorizontalBarChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public HorizontalBarChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void init() { - - mViewPortHandler = new HorizontalViewPortHandler(); - - super.init(); - - mLeftAxisTransformer = new TransformerHorizontalBarChart(mViewPortHandler); - mRightAxisTransformer = new TransformerHorizontalBarChart(mViewPortHandler); - - mRenderer = new HorizontalBarChartRenderer(this, mAnimator, mViewPortHandler); - setHighlighter(new HorizontalBarHighlighter(this)); - - mAxisRendererLeft = new YAxisRendererHorizontalBarChart(mViewPortHandler, mAxisLeft, mLeftAxisTransformer); - mAxisRendererRight = new YAxisRendererHorizontalBarChart(mViewPortHandler, mAxisRight, mRightAxisTransformer); - mXAxisRenderer = new XAxisRendererHorizontalBarChart(mViewPortHandler, mXAxis, mLeftAxisTransformer); - } - - private RectF mOffsetsBuffer = new RectF(); - - protected void calculateLegendOffsets(RectF offsets) { - - offsets.left = 0.f; - offsets.right = 0.f; - offsets.top = 0.f; - offsets.bottom = 0.f; - - if (mLegend == null || !mLegend.isEnabled() || mLegend.isDrawInsideEnabled()) - return; - - switch (mLegend.getOrientation()) { - case VERTICAL: - - switch (mLegend.getHorizontalAlignment()) { - case LEFT: - offsets.left += Math.min(mLegend.mNeededWidth, - mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) - + mLegend.getXOffset(); - break; - - case RIGHT: - offsets.right += Math.min(mLegend.mNeededWidth, - mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()) - + mLegend.getXOffset(); - break; - - case CENTER: - - switch (mLegend.getVerticalAlignment()) { - case TOP: - offsets.top += Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) - + mLegend.getYOffset(); - break; - - case BOTTOM: - offsets.bottom += Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) - + mLegend.getYOffset(); - break; - - default: - break; - } - } - - break; - - case HORIZONTAL: - - switch (mLegend.getVerticalAlignment()) { - case TOP: - offsets.top += Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) - + mLegend.getYOffset(); - - if (mAxisLeft.isEnabled() && mAxisLeft.isDrawLabelsEnabled()) - offsets.top += mAxisLeft.getRequiredHeightSpace( - mAxisRendererLeft.getPaintAxisLabels()); - break; - - case BOTTOM: - offsets.bottom += Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()) - + mLegend.getYOffset(); - - if (mAxisRight.isEnabled() && mAxisRight.isDrawLabelsEnabled()) - offsets.bottom += mAxisRight.getRequiredHeightSpace( - mAxisRendererRight.getPaintAxisLabels()); - break; - - default: - break; - } - break; - } - } - - @Override - public void calculateOffsets() { - - float offsetLeft = 0f, offsetRight = 0f, offsetTop = 0f, offsetBottom = 0f; - - calculateLegendOffsets(mOffsetsBuffer); - - offsetLeft += mOffsetsBuffer.left; - offsetTop += mOffsetsBuffer.top; - offsetRight += mOffsetsBuffer.right; - offsetBottom += mOffsetsBuffer.bottom; - - // offsets for y-labels - if (mAxisLeft.needsOffset()) { - offsetTop += mAxisLeft.getRequiredHeightSpace(mAxisRendererLeft.getPaintAxisLabels()); - } - - if (mAxisRight.needsOffset()) { - offsetBottom += mAxisRight.getRequiredHeightSpace(mAxisRendererRight.getPaintAxisLabels()); - } - - float xLabelWidth = mXAxis.mLabelWidth; - - if (mXAxis.isEnabled()) { - - // offsets for x-labels - if (mXAxis.getPosition() == XAxisPosition.BOTTOM) { - - offsetLeft += xLabelWidth; - - } else if (mXAxis.getPosition() == XAxisPosition.TOP) { - - offsetRight += xLabelWidth; - - } else if (mXAxis.getPosition() == XAxisPosition.BOTH_SIDED) { - - offsetLeft += xLabelWidth; - offsetRight += xLabelWidth; - } - } - - offsetTop += getExtraTopOffset(); - offsetRight += getExtraRightOffset(); - offsetBottom += getExtraBottomOffset(); - offsetLeft += getExtraLeftOffset(); - - float minOffset = Utils.convertDpToPixel(mMinOffset); - - mViewPortHandler.restrainViewPort( - Math.max(minOffset, offsetLeft), - Math.max(minOffset, offsetTop), - Math.max(minOffset, offsetRight), - Math.max(minOffset, offsetBottom)); - - if (mLogEnabled) { - Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop + ", offsetRight: " + - offsetRight + ", offsetBottom: " - + offsetBottom); - Log.i(LOG_TAG, "Content: " + mViewPortHandler.getContentRect().toString()); - } - - prepareOffsetMatrix(); - prepareValuePxMatrix(); - } - - @Override - protected void prepareValuePxMatrix() { - mRightAxisTransformer.prepareMatrixValuePx(mAxisRight.mAxisMinimum, mAxisRight.mAxisRange, mXAxis.mAxisRange, - mXAxis.mAxisMinimum); - mLeftAxisTransformer.prepareMatrixValuePx(mAxisLeft.mAxisMinimum, mAxisLeft.mAxisRange, mXAxis.mAxisRange, - mXAxis.mAxisMinimum); - } - - @Override - protected float[] getMarkerPosition(Highlight high) { - return new float[]{high.getDrawY(), high.getDrawX()}; - } - - @Override - public void getBarBounds(BarEntry barEntry, RectF outputRect) { - - RectF bounds = outputRect; - IBarDataSet set = mData.getDataSetForEntry(barEntry); - - if (set == null) { - outputRect.set(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE); - return; - } - - float y = barEntry.getY(); - float x = barEntry.getX(); - - float barWidth = mData.getBarWidth(); - - float top = x - barWidth / 2f; - float bottom = x + barWidth / 2f; - float left = y >= 0 ? y : 0; - float right = y <= 0 ? y : 0; - - bounds.set(left, top, right, bottom); - - getTransformer(set.getAxisDependency()).rectValueToPixel(bounds); - - } - - protected float[] mGetPositionBuffer = new float[2]; - - /** - * Returns a recyclable MPPointF instance. - * - * @param e - * @param axis - * @return - */ - @Override - public MPPointF getPosition(Entry e, AxisDependency axis) { - - if (e == null) - return null; - - float[] vals = mGetPositionBuffer; - vals[0] = e.getY(); - vals[1] = e.getX(); - - getTransformer(axis).pointValuesToPixel(vals); - - return MPPointF.getInstance(vals[0], vals[1]); - } - - /** - * Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch point - * inside the BarChart. - * - * @param x - * @param y - * @return - */ - @Override - public Highlight getHighlightByTouchPoint(float x, float y) { - - if (mData == null) { - if (mLogEnabled) - Log.e(LOG_TAG, "Can't select by touch. No data set."); - return null; - } else - return getHighlighter().getHighlight(y, x); // switch x and y - } - - @Override - public float getLowestVisibleX() { - getTransformer(AxisDependency.LEFT).getValuesByTouchPoint(mViewPortHandler.contentLeft(), - mViewPortHandler.contentBottom(), posForGetLowestVisibleX); - float result = (float) Math.max(mXAxis.mAxisMinimum, posForGetLowestVisibleX.y); - return result; - } - - @Override - public float getHighestVisibleX() { - getTransformer(AxisDependency.LEFT).getValuesByTouchPoint(mViewPortHandler.contentLeft(), - mViewPortHandler.contentTop(), posForGetHighestVisibleX); - float result = (float) Math.min(mXAxis.mAxisMaximum, posForGetHighestVisibleX.y); - return result; - } - - /** - * ###### VIEWPORT METHODS BELOW THIS ###### - */ - - @Override - public void setVisibleXRangeMaximum(float maxXRange) { - float xScale = mXAxis.mAxisRange / (maxXRange); - mViewPortHandler.setMinimumScaleY(xScale); - } - - @Override - public void setVisibleXRangeMinimum(float minXRange) { - float xScale = mXAxis.mAxisRange / (minXRange); - mViewPortHandler.setMaximumScaleY(xScale); - } - - @Override - public void setVisibleXRange(float minXRange, float maxXRange) { - float minScale = mXAxis.mAxisRange / minXRange; - float maxScale = mXAxis.mAxisRange / maxXRange; - mViewPortHandler.setMinMaxScaleY(minScale, maxScale); - } - - @Override - public void setVisibleYRangeMaximum(float maxYRange, AxisDependency axis) { - float yScale = getAxisRange(axis) / maxYRange; - mViewPortHandler.setMinimumScaleX(yScale); - } - - @Override - public void setVisibleYRangeMinimum(float minYRange, AxisDependency axis) { - float yScale = getAxisRange(axis) / minYRange; - mViewPortHandler.setMaximumScaleX(yScale); - } - - @Override - public void setVisibleYRange(float minYRange, float maxYRange, AxisDependency axis) { - float minScale = getAxisRange(axis) / minYRange; - float maxScale = getAxisRange(axis) / maxYRange; - mViewPortHandler.setMinMaxScaleX(minScale, maxScale); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.kt new file mode 100644 index 0000000000..02c3569e22 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/HorizontalBarChart.kt @@ -0,0 +1,312 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.graphics.RectF +import android.util.AttributeSet +import android.util.Log +import com.github.mikephil.charting.components.Legend.LegendHorizontalAlignment +import com.github.mikephil.charting.components.Legend.LegendOrientation +import com.github.mikephil.charting.components.Legend.LegendVerticalAlignment +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.highlight.HorizontalBarHighlighter +import com.github.mikephil.charting.renderer.HorizontalBarChartRenderer +import com.github.mikephil.charting.renderer.XAxisRendererHorizontalBarChart +import com.github.mikephil.charting.renderer.YAxisRendererHorizontalBarChart +import com.github.mikephil.charting.utils.HorizontalViewPortHandler +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.TransformerHorizontalBarChart +import com.github.mikephil.charting.utils.Utils +import kotlin.math.max +import kotlin.math.min + +/** + * BarChart with horizontal bar orientation. In this implementation, x- and y-axis are switched, meaning the YAxis class + * represents the horizontal values and the XAxis class represents the vertical values. + * + * @author Philipp Jahoda + */ +class HorizontalBarChart : BarChart { + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + init { + viewPortHandler = HorizontalViewPortHandler() + + mLeftAxisTransformer = TransformerHorizontalBarChart(viewPortHandler) + mRightAxisTransformer = TransformerHorizontalBarChart(viewPortHandler) + + mRenderer = HorizontalBarChartRenderer(this, mAnimator, viewPortHandler) + setHighlighter(HorizontalBarHighlighter(this)) + + mAxisRendererLeft = YAxisRendererHorizontalBarChart(viewPortHandler, mAxisLeft, mLeftAxisTransformer) + mAxisRendererRight = YAxisRendererHorizontalBarChart(viewPortHandler, mAxisRight, mRightAxisTransformer) + mXAxisRenderer = XAxisRendererHorizontalBarChart(viewPortHandler, mXAxis, mLeftAxisTransformer) + } + + private val mOffsetsBuffer = RectF() + + override fun calculateLegendOffsets(offsets: RectF) { + offsets.left = 0f + offsets.right = 0f + offsets.top = 0f + offsets.bottom = 0f + + if (!legend.isEnabled || legend.isDrawInsideEnabled) return + + when (legend.orientation) { + LegendOrientation.VERTICAL -> when (legend.horizontalAlignment) { + LegendHorizontalAlignment.LEFT -> offsets.left += min( + legend.mNeededWidth, + viewPortHandler.chartWidth * legend.maxSizePercent + ) + legend.xOffset + + LegendHorizontalAlignment.RIGHT -> offsets.right += min( + legend.mNeededWidth, + viewPortHandler.chartWidth * legend.maxSizePercent + ) + legend.xOffset + + LegendHorizontalAlignment.CENTER -> when (legend.verticalAlignment) { + LegendVerticalAlignment.TOP -> offsets.top += min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + legend.yOffset + + LegendVerticalAlignment.BOTTOM -> offsets.bottom += min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + legend.yOffset + + else -> {} + } + } + + LegendOrientation.HORIZONTAL -> when (legend.verticalAlignment) { + LegendVerticalAlignment.TOP -> { + offsets.top += min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + legend.yOffset + + if (mAxisLeft.isEnabled && mAxisLeft.isDrawLabelsEnabled) offsets.top += mAxisLeft.getRequiredHeightSpace( + mAxisRendererLeft.paintAxisLabels + ) + } + + LegendVerticalAlignment.BOTTOM -> { + offsets.bottom += min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + legend.yOffset + + if (mAxisRight.isEnabled && mAxisRight.isDrawLabelsEnabled) offsets.bottom += mAxisRight.getRequiredHeightSpace( + mAxisRendererRight.paintAxisLabels + ) + } + + else -> {} + } + } + } + + override fun calculateOffsets() { + var offsetLeft = 0f + var offsetRight = 0f + var offsetTop = 0f + var offsetBottom = 0f + + calculateLegendOffsets(mOffsetsBuffer) + + offsetLeft += mOffsetsBuffer.left + offsetTop += mOffsetsBuffer.top + offsetRight += mOffsetsBuffer.right + offsetBottom += mOffsetsBuffer.bottom + + // offsets for y-labels + if (mAxisLeft.needsOffset()) { + offsetTop += mAxisLeft.getRequiredHeightSpace(mAxisRendererLeft.paintAxisLabels) + } + + if (mAxisRight.needsOffset()) { + offsetBottom += mAxisRight.getRequiredHeightSpace(mAxisRendererRight.paintAxisLabels) + } + + val xLabelWidth = mXAxis.mLabelWidth.toFloat() + + if (mXAxis.isEnabled) { + // offsets for x-labels + + when (mXAxis.position) { + XAxisPosition.BOTTOM -> { + offsetLeft += xLabelWidth + } + XAxisPosition.TOP -> { + offsetRight += xLabelWidth + } + XAxisPosition.BOTH_SIDED -> { + offsetLeft += xLabelWidth + offsetRight += xLabelWidth + } + else -> {} + } + } + + offsetTop += extraTopOffset + offsetRight += extraRightOffset + offsetBottom += extraBottomOffset + offsetLeft += extraLeftOffset + + val minOffset = Utils.convertDpToPixel(minOffset) + + viewPortHandler.restrainViewPort( + max(minOffset, offsetLeft), + max(minOffset, offsetTop), + max(minOffset, offsetRight), + max(minOffset, offsetBottom) + ) + + if (isLogEnabled) { + Log.i( + LOG_TAG, ("offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop + ", offsetRight: " + + offsetRight + ", offsetBottom: " + + offsetBottom) + ) + Log.i(LOG_TAG, "Content: " + viewPortHandler.contentRect.toString()) + } + + prepareOffsetMatrix() + prepareValuePxMatrix() + } + + override fun prepareValuePxMatrix() { + mRightAxisTransformer.prepareMatrixValuePx( + mAxisRight.mAxisMinimum, mAxisRight.mAxisRange, mXAxis.mAxisRange, + mXAxis.mAxisMinimum + ) + mLeftAxisTransformer.prepareMatrixValuePx( + mAxisLeft.mAxisMinimum, mAxisLeft.mAxisRange, mXAxis.mAxisRange, + mXAxis.mAxisMinimum + ) + } + + override fun getMarkerPosition(high: Highlight): FloatArray { + return floatArrayOf(high.drawY, high.drawX) + } + + override fun getBarBounds(barEntry: BarEntry, outputRect: RectF) { + val bounds = outputRect + val set = mData?.getDataSetForEntry(barEntry) + + if (set == null) { + outputRect.set(Float.Companion.MIN_VALUE, Float.Companion.MIN_VALUE, Float.Companion.MIN_VALUE, Float.Companion.MIN_VALUE) + return + } + + val y = barEntry.y + val x = barEntry.x + + val barWidth = mData?.barWidth ?: return + + val top = x - barWidth / 2f + val bottom = x + barWidth / 2f + val left = if (y >= 0) y else 0f + val right = if (y <= 0) y else 0f + + bounds.set(left, top, right, bottom) + + getTransformer(set.axisDependency).rectValueToPixel(bounds) + } + + /** + * Returns a recyclable MPPointF instance. + * + * @param e + * @param axis + * @return + */ + override fun getPosition(e: Entry, axis: AxisDependency?): MPPointF { + val vals = mGetPositionBuffer + vals[0] = e.y + vals[1] = e.x + + getTransformer(axis).pointValuesToPixel(vals) + + return MPPointF.getInstance(vals[0], vals[1]) + } + + /** + * Returns the Highlight object (contains x-index and DataSet index) of the selected value at the given touch point + * inside the BarChart. + * + * @param x + * @param y + * @return + */ + override fun getHighlightByTouchPoint(x: Float, y: Float): Highlight? { + if (mData == null) { + if (isLogEnabled) Log.e(LOG_TAG, "Can't select by touch. No data set.") + return null + } else return highlighter?.getHighlight(y, x) // switch x and y + } + + override val lowestVisibleX: Float + get() { + getTransformer(AxisDependency.LEFT).getValuesByTouchPoint( + viewPortHandler.contentLeft(), + viewPortHandler.contentBottom(), posForGetLowestVisibleX + ) + val result = max(mXAxis.mAxisMinimum.toDouble(), posForGetLowestVisibleX.y).toFloat() + return result + } + + override val highestVisibleX: Float + get() { + getTransformer(AxisDependency.LEFT).getValuesByTouchPoint( + viewPortHandler.contentLeft(), + viewPortHandler.contentTop(), posForGetHighestVisibleX + ) + val result = min(mXAxis.mAxisMaximum.toDouble(), posForGetHighestVisibleX.y).toFloat() + return result + } + + /** + * ###### VIEWPORT METHODS BELOW THIS ###### + */ + override fun setVisibleXRangeMaximum(maxXRange: Float) { + val xScale = mXAxis.mAxisRange / (maxXRange) + viewPortHandler.setMinimumScaleY(xScale) + } + + override fun setVisibleXRangeMinimum(minXRange: Float) { + val xScale = mXAxis.mAxisRange / (minXRange) + viewPortHandler.setMaximumScaleY(xScale) + } + + override fun setVisibleXRange(minXRange: Float, maxXRange: Float) { + val minScale = mXAxis.mAxisRange / minXRange + val maxScale = mXAxis.mAxisRange / maxXRange + viewPortHandler.setMinMaxScaleY(minScale, maxScale) + } + + override fun setVisibleYRangeMaximum(maxYRange: Float, axis: AxisDependency?) { + val yScale = getAxisRange(axis) / maxYRange + viewPortHandler.setMinimumScaleX(yScale) + } + + override fun setVisibleYRangeMinimum(minYRange: Float, axis: AxisDependency?) { + val yScale = getAxisRange(axis) / minYRange + viewPortHandler.setMaximumScaleX(yScale) + } + + override fun setVisibleYRange(minYRange: Float, maxYRange: Float, axis: AxisDependency?) { + val minScale = getAxisRange(axis) / minYRange + val maxScale = getAxisRange(axis) / maxYRange + viewPortHandler.setMinMaxScaleX(minScale, maxScale) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/LineChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/LineChart.kt index b2871320cd..7a6bd8d6f6 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/LineChart.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/LineChart.kt @@ -2,29 +2,26 @@ package com.github.mikephil.charting.charts import android.content.Context import android.util.AttributeSet +import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.LineData import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet import com.github.mikephil.charting.renderer.LineChartRenderer import java.util.Locale -open class LineChart : BarLineChartBase, LineDataProvider { +open class LineChart : BarLineChartBase, LineDataProvider { + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - constructor(context: Context?) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - mRenderer = LineChartRenderer(this, mAnimator, mViewPortHandler) + init { + mRenderer = LineChartRenderer(this, mAnimator, viewPortHandler) } - override val lineData: LineData - get() { - mData?.let { - return it - } ?: run { - return LineData() - } + override var lineData: LineData? + get() = mData + set(value) { + mData = value } public override fun onDetachedFromWindow() { @@ -35,25 +32,26 @@ open class LineChart : BarLineChartBase, LineDataProvider { super.onDetachedFromWindow() } - override fun getAccessibilityDescription(): String { - val lineData = lineData - val numberOfPoints = lineData.entryCount - - // Min and max values... - val yAxisValueFormatter = axisLeft.valueFormatter - val minVal = yAxisValueFormatter.getFormattedValue(lineData.yMin, null) - val maxVal = yAxisValueFormatter.getFormattedValue(lineData.yMax, null) - - // Data range... - val xAxisValueFormatter = xAxis.valueFormatter - val minRange = xAxisValueFormatter.getFormattedValue(lineData.xMin, null) - val maxRange = xAxisValueFormatter.getFormattedValue(lineData.xMax, null) - val entries = if (numberOfPoints == 1) "entry" else "entries" - return String.format( - Locale.getDefault(), "The line chart has %d %s. " + - "The minimum value is %s and maximum value is %s." + - "Data ranges from %s to %s.", - numberOfPoints, entries, minVal, maxVal, minRange, maxRange - ) - } + override val accessibilityDescription: String? + get() { + val lineData = lineData + val numberOfPoints = lineData?.entryCount ?: return null + + // Min and max values... + val yAxisValueFormatter = axisLeft.valueFormatter + val minVal = yAxisValueFormatter.getFormattedValue(lineData.yMin, null) + val maxVal = yAxisValueFormatter.getFormattedValue(lineData.yMax, null) + + // Data range... + val xAxisValueFormatter = xAxis.valueFormatter + val minRange = xAxisValueFormatter.getFormattedValue(lineData.xMin, null) + val maxRange = xAxisValueFormatter.getFormattedValue(lineData.xMax, null) + val entries = if (numberOfPoints == 1) "entry" else "entries" + return String.format( + Locale.getDefault(), "The line chart has %d %s. " + + "The minimum value is %s and maximum value is %s." + + "Data ranges from %s to %s.", + numberOfPoints, entries, minVal, maxVal, minRange, maxRange + ) + } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.java deleted file mode 100644 index d029c2cb88..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.java +++ /dev/null @@ -1,830 +0,0 @@ - -package com.github.mikephil.charting.charts; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.graphics.Typeface; -import android.text.TextUtils; -import android.util.AttributeSet; - -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieEntry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.highlight.PieHighlighter; -import com.github.mikephil.charting.interfaces.datasets.IPieDataSet; -import com.github.mikephil.charting.renderer.PieChartRenderer; -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.Utils; - -import java.util.List; -import java.util.Locale; - -/** - * View that represents a pie chart. Draws cake like slices. - * - * @author Philipp Jahoda - */ -public class PieChart extends PieRadarChartBase { - - /** - * rect object that represents the bounds of the piechart, needed for - * drawing the circle - */ - private final RectF mCircleBox = new RectF(); - - /** - * flag indicating if entry labels should be drawn or not - */ - private boolean mDrawEntryLabels = true; - - /** - * array that holds the width of each pie-slice in degrees - */ - private float[] mDrawAngles = new float[1]; - - /** - * array that holds the absolute angle in degrees of each slice - */ - private float[] mAbsoluteAngles = new float[1]; - - /** - * if true, the white hole inside the chart will be drawn - */ - private boolean mDrawHole = true; - - /** - * if true, the hole will see-through to the inner tips of the slices - */ - private boolean mDrawSlicesUnderHole = false; - - /** - * if true, the values inside the piechart are drawn as percent values - */ - private boolean mUsePercentValues = false; - - /** - * if true, the slices of the piechart are rounded - */ - private boolean mDrawRoundedSlices = false; - - /** - * variable for the text that is drawn in the center of the pie-chart - */ - private CharSequence mCenterText = ""; - - private final MPPointF mCenterTextOffset = MPPointF.getInstance(0, 0); - - /** - * indicates the size of the hole in the center of the piechart, default: - * radius / 2 - */ - private float mHoleRadiusPercent = 50f; - - /** - * the radius of the transparent circle next to the chart-hole in the center - */ - protected float mTransparentCircleRadiusPercent = 55f; - - /** - * if enabled, centertext is drawn - */ - private boolean mDrawCenterText = true; - - private float mCenterTextRadiusPercent = 100.f; - - protected float mMaxAngle = 360f; - - /** - * Minimum angle to draw slices, this only works if there is enough room for all slices to have - * the minimum angle, default 0f. - */ - private float mMinAngleForSlices = 0f; - - public PieChart(Context context) { - super(context); - } - - public PieChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public PieChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void init() { - super.init(); - - mRenderer = new PieChartRenderer(this, mAnimator, mViewPortHandler); - mXAxis = null; - - mHighlighter = new PieHighlighter(this); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mData == null) - return; - - mRenderer.drawData(canvas); - - if (valuesToHighlight()) - mRenderer.drawHighlighted(canvas, mIndicesToHighlight); - - mRenderer.drawExtras(canvas); - - mRenderer.drawValues(canvas); - - mLegendRenderer.renderLegend(canvas); - - drawDescription(canvas); - - drawMarkers(canvas); - } - - @Override - public void calculateOffsets() { - super.calculateOffsets(); - - // prevent nullpointer when no data set - if (mData == null) - return; - - float diameter = getDiameter(); - float radius = diameter / 2f; - - MPPointF c = getCenterOffsets(); - - float shift = mData.getDataSet().getSelectionShift(); - - // create the circle box that will contain the pie-chart (the bounds of - // the pie-chart) - mCircleBox.set(c.x - radius + shift, - c.y - radius + shift, - c.x + radius - shift, - c.y + radius - shift); - - MPPointF.recycleInstance(c); - } - - @Override - protected void calcMinMax() { - calcAngles(); - } - - @Override - protected float[] getMarkerPosition(Highlight highlight) { - - MPPointF center = getCenterCircleBox(); - float r = getRadius(); - - float off = r / 10f * 3.6f; - - if (isDrawHoleEnabled()) { - off = (r - (r / 100f * getHoleRadius())) / 2f; - } - - r -= off; // offset to keep things inside the chart - - float rotationAngle = getRotationAngle(); - - int entryIndex = (int) highlight.getX(); - - // offset needed to center the drawn text in the slice - float offset = mDrawAngles[entryIndex] / 2; - - // calculate the text position - float x = (float) (r - * Math.cos(Math.toRadians((rotationAngle + mAbsoluteAngles[entryIndex] - offset) - * mAnimator.getPhaseY())) + center.x); - float y = (float) (r - * Math.sin(Math.toRadians((rotationAngle + mAbsoluteAngles[entryIndex] - offset) - * mAnimator.getPhaseY())) + center.y); - - MPPointF.recycleInstance(center); - return new float[]{x, y}; - } - - /** - * calculates the needed angles for the chart slices - */ - private void calcAngles() { - - int entryCount = mData.getEntryCount(); - - if (mDrawAngles.length != entryCount) { - mDrawAngles = new float[entryCount]; - } else { - for (int i = 0; i < entryCount; i++) { - mDrawAngles[i] = 0; - } - } - if (mAbsoluteAngles.length != entryCount) { - mAbsoluteAngles = new float[entryCount]; - } else { - for (int i = 0; i < entryCount; i++) { - mAbsoluteAngles[i] = 0; - } - } - - float yValueSum = mData.getYValueSum(); - - List dataSets = mData.getDataSets(); - - boolean hasMinAngle = mMinAngleForSlices != 0f && entryCount * mMinAngleForSlices <= mMaxAngle; - float[] minAngles = new float[entryCount]; - - int cnt = 0; - float offset = 0f; - float diff = 0f; - - for (int i = 0; i < mData.getDataSetCount(); i++) { - - IPieDataSet set = dataSets.get(i); - - for (int j = 0; j < set.getEntryCount(); j++) { - - float drawAngle = calcAngle(Math.abs(set.getEntryForIndex(j).getY()), yValueSum); - - if (hasMinAngle) { - float temp = drawAngle - mMinAngleForSlices; - if (temp <= 0) { - minAngles[cnt] = mMinAngleForSlices; - offset += -temp; - } else { - minAngles[cnt] = drawAngle; - diff += temp; - } - } - - mDrawAngles[cnt] = drawAngle; - - if (cnt == 0) { - mAbsoluteAngles[cnt] = mDrawAngles[cnt]; - } else { - mAbsoluteAngles[cnt] = mAbsoluteAngles[cnt - 1] + mDrawAngles[cnt]; - } - - cnt++; - } - } - - if (hasMinAngle) { - // Correct bigger slices by relatively reducing their angles based on the total angle needed to subtract - // This requires that `entryCount * mMinAngleForSlices <= mMaxAngle` be true to properly work! - for (int i = 0; i < entryCount; i++) { - minAngles[i] -= (minAngles[i] - mMinAngleForSlices) / diff * offset; - if (i == 0) { - mAbsoluteAngles[0] = minAngles[0]; - } else { - mAbsoluteAngles[i] = mAbsoluteAngles[i - 1] + minAngles[i]; - } - } - - mDrawAngles = minAngles; - } - } - - /** - * Checks if the given index is set to be highlighted. - * - * @param index - * @return - */ - public boolean needsHighlight(int index) { - - // no highlight - if (!valuesToHighlight()) - return false; - - // check if the xvalue for the given dataset needs highlight - for (Highlight highlight : mIndicesToHighlight) - if ((int) highlight.getX() == index) - return true; - - return false; - } - - /** - * calculates the needed angle for a given value - * - * @param value - * @return - */ - private float calcAngle(float value) { - return calcAngle(value, mData.getYValueSum()); - } - - /** - * calculates the needed angle for a given value - * - * @param value - * @param yValueSum - * @return - */ - private float calcAngle(float value, float yValueSum) { - return value / yValueSum * mMaxAngle; - } - - /** - * This will throw an exception, PieChart has no XAxis object. - * - * @return - */ - @Deprecated - @Override - public XAxis getXAxis() { - throw new RuntimeException("PieChart has no XAxis"); - } - - @Override - public int getIndexForAngle(float angle) { - - // take the current angle of the chart into consideration - float a = Utils.getNormalizedAngle(angle - getRotationAngle()); - - for (int i = 0; i < mAbsoluteAngles.length; i++) { - if (mAbsoluteAngles[i] > a) - return i; - } - - return -1; // return -1 if no index found - } - - /** - * Returns the index of the DataSet this x-index belongs to. - * - * @param xIndex - * @return - */ - public int getDataSetIndexForIndex(int xIndex) { - - List dataSets = mData.getDataSets(); - - for (int i = 0; i < dataSets.size(); i++) { - if (dataSets.get(i).getEntryForXValue(xIndex, Float.NaN) != null) - return i; - } - - return -1; - } - - /** - * returns an integer array of all the different angles the chart slices - * have the angles in the returned array determine how much space (of 360°) - * each slice takes - * - * @return - */ - public float[] getDrawAngles() { - return mDrawAngles; - } - - /** - * returns the absolute angles of the different chart slices (where the - * slices end) - * - * @return - */ - public float[] getAbsoluteAngles() { - return mAbsoluteAngles; - } - - /** - * Sets the color for the hole that is drawn in the center of the PieChart - * (if enabled). - * - * @param color - */ - public void setHoleColor(int color) { - ((PieChartRenderer) mRenderer).getPaintHole().setColor(color); - } - - /** - * Enable or disable the visibility of the inner tips of the slices behind the hole - */ - public void setDrawSlicesUnderHole(boolean enable) { - mDrawSlicesUnderHole = enable; - } - - /** - * Returns true if the inner tips of the slices are visible behind the hole, - * false if not. - * - * @return true if slices are visible behind the hole. - */ - public boolean isDrawSlicesUnderHoleEnabled() { - return mDrawSlicesUnderHole; - } - - /** - * set this to true to draw the pie center empty - * - * @param enabled - */ - public void setDrawHoleEnabled(boolean enabled) { - this.mDrawHole = enabled; - } - - /** - * returns true if the hole in the center of the pie-chart is set to be - * visible, false if not - * - * @return - */ - public boolean isDrawHoleEnabled() { - return mDrawHole; - } - - /** - * Sets the text String that is displayed in the center of the PieChart. - * - * @param text - */ - public void setCenterText(CharSequence text) { - if (text == null) - mCenterText = ""; - else - mCenterText = text; - } - - /** - * returns the text that is drawn in the center of the pie-chart - * - * @return - */ - public CharSequence getCenterText() { - return mCenterText; - } - - /** - * set this to true to draw the text that is displayed in the center of the - * pie chart - * - * @param enabled - */ - public void setDrawCenterText(boolean enabled) { - this.mDrawCenterText = enabled; - } - - /** - * returns true if drawing the center text is enabled - * - * @return - */ - public boolean isDrawCenterTextEnabled() { - return mDrawCenterText; - } - - @Override - protected float getRequiredLegendOffset() { - return mLegendRenderer.getLabelPaint().getTextSize() * 2.f; - } - - @Override - protected float getRequiredBaseOffset() { - return 0; - } - - @Override - public float getRadius() { - if (mCircleBox == null) - return 0; - else - return Math.min(mCircleBox.width() / 2f, mCircleBox.height() / 2f); - } - - /** - * returns the circlebox, the boundingbox of the pie-chart slices - * - * @return - */ - public RectF getCircleBox() { - return mCircleBox; - } - - /** - * returns the center of the circlebox - * - * @return - */ - public MPPointF getCenterCircleBox() { - return MPPointF.getInstance(mCircleBox.centerX(), mCircleBox.centerY()); - } - - /** - * sets the typeface for the center-text paint - * - * @param t - */ - public void setCenterTextTypeface(Typeface t) { - ((PieChartRenderer) mRenderer).getPaintCenterText().setTypeface(t); - } - - /** - * Sets the size of the center text of the PieChart in dp. - * - * @param sizeDp - */ - public void setCenterTextSize(float sizeDp) { - ((PieChartRenderer) mRenderer).getPaintCenterText().setTextSize( - Utils.convertDpToPixel(sizeDp)); - } - - /** - * Sets the size of the center text of the PieChart in pixels. - * - * @param sizePixels - */ - public void setCenterTextSizePixels(float sizePixels) { - ((PieChartRenderer) mRenderer).getPaintCenterText().setTextSize(sizePixels); - } - - /** - * Sets the offset the center text should have from it's original position in dp. Default x = 0, y = 0 - * - * @param x - * @param y - */ - public void setCenterTextOffset(float x, float y) { - mCenterTextOffset.x = Utils.convertDpToPixel(x); - mCenterTextOffset.y = Utils.convertDpToPixel(y); - } - - /** - * Returns the offset on the x- and y-axis the center text has in dp. - * - * @return - */ - public MPPointF getCenterTextOffset() { - return MPPointF.getInstance(mCenterTextOffset.x, mCenterTextOffset.y); - } - - /** - * Sets the color of the center text of the PieChart. - * - * @param color - */ - public void setCenterTextColor(int color) { - ((PieChartRenderer) mRenderer).getPaintCenterText().setColor(color); - } - - /** - * sets the radius of the hole in the center of the piechart in percent of - * the maximum radius (max = the radius of the whole chart), default 50% - * - * @param percent - */ - public void setHoleRadius(final float percent) { - mHoleRadiusPercent = percent; - } - - /** - * Returns the size of the hole radius in percent of the total radius. - * - * @return - */ - public float getHoleRadius() { - return mHoleRadiusPercent; - } - - /** - * Sets the color the transparent-circle should have. - * - * @param color - */ - public void setTransparentCircleColor(int color) { - - Paint p = ((PieChartRenderer) mRenderer).getPaintTransparentCircle(); - int alpha = p.getAlpha(); - p.setColor(color); - p.setAlpha(alpha); - } - - /** - * sets the radius of the transparent circle that is drawn next to the hole - * in the piechart in percent of the maximum radius (max = the radius of the - * whole chart), default 55% -> means 5% larger than the center-hole by - * default - * - * @param percent - */ - public void setTransparentCircleRadius(final float percent) { - mTransparentCircleRadiusPercent = percent; - } - - public float getTransparentCircleRadius() { - return mTransparentCircleRadiusPercent; - } - - /** - * Sets the amount of transparency the transparent circle should have 0 = fully transparent, - * 255 = fully opaque. - * Default value is 100. - * - * @param alpha 0-255 - */ - public void setTransparentCircleAlpha(int alpha) { - ((PieChartRenderer) mRenderer).getPaintTransparentCircle().setAlpha(alpha); - } - - /** - * Set this to true to draw the entry labels into the pie slices (Provided by the getLabel() method of the PieEntry class). - * Deprecated -> use setDrawEntryLabels(...) instead. - * - * @param enabled - */ - @Deprecated - public void setDrawSliceText(boolean enabled) { - mDrawEntryLabels = enabled; - } - - /** - * Set this to true to draw the entry labels into the pie slices (Provided by the getLabel() method of the PieEntry class). - * - * @param enabled - */ - public void setDrawEntryLabels(boolean enabled) { - mDrawEntryLabels = enabled; - } - - /** - * Returns true if drawing the entry labels is enabled, false if not. - * - * @return - */ - public boolean isDrawEntryLabelsEnabled() { - return mDrawEntryLabels; - } - - /** - * Sets the color the entry labels are drawn with. - * - * @param color - */ - public void setEntryLabelColor(int color) { - ((PieChartRenderer) mRenderer).getPaintEntryLabels().setColor(color); - } - - /** - * Sets a custom Typeface for the drawing of the entry labels. - * - * @param tf - */ - public void setEntryLabelTypeface(Typeface tf) { - ((PieChartRenderer) mRenderer).getPaintEntryLabels().setTypeface(tf); - } - - /** - * Sets the size of the entry labels in dp. Default: 13dp - * - * @param size - */ - public void setEntryLabelTextSize(float size) { - ((PieChartRenderer) mRenderer).getPaintEntryLabels().setTextSize(Utils.convertDpToPixel(size)); - } - - /** - * Sets whether to draw slices in a curved fashion, only works if drawing the hole is enabled - * and if the slices are not drawn under the hole. - * - * @param enabled draw curved ends of slices - */ - public void setDrawRoundedSlices(boolean enabled) { - mDrawRoundedSlices = enabled; - } - - /** - * Returns true if the chart is set to draw each end of a pie-slice - * "rounded". - * - * @return - */ - public boolean isDrawRoundedSlicesEnabled() { - return mDrawRoundedSlices; - } - - /** - * If this is enabled, values inside the PieChart are drawn in percent and - * not with their original value. Values provided for the IValueFormatter to - * format are then provided in percent. - * - * @param enabled - */ - public void setUsePercentValues(boolean enabled) { - mUsePercentValues = enabled; - } - - /** - * Returns true if using percentage values is enabled for the chart. - * - * @return - */ - public boolean isUsePercentValuesEnabled() { - return mUsePercentValues; - } - - /** - * the rectangular radius of the bounding box for the center text, as a percentage of the pie - * hole - * default 1.f (100%) - */ - public void setCenterTextRadiusPercent(float percent) { - mCenterTextRadiusPercent = percent; - } - - /** - * the rectangular radius of the bounding box for the center text, as a percentage of the pie - * hole - * default 1.f (100%) - */ - public float getCenterTextRadiusPercent() { - return mCenterTextRadiusPercent; - } - - public float getMaxAngle() { - return mMaxAngle; - } - - /** - * Sets the max angle that is used for calculating the pie-circle. 360f means - * it's a full PieChart, 180f results in a half-pie-chart. Default: 360f - * - * @param maxangle min 90, max 360 - */ - public void setMaxAngle(float maxangle) { - - if (maxangle > 360) - maxangle = 360f; - - if (maxangle < 90) - maxangle = 90f; - - this.mMaxAngle = maxangle; - } - - /** - * The minimum angle slices on the chart are rendered with, default is 0f. - * - * @return minimum angle for slices - */ - public float getMinAngleForSlices() { - return mMinAngleForSlices; - } - - /** - * Set the angle to set minimum size for slices, you must call {@link #notifyDataSetChanged()} - * and {@link #invalidate()} when changing this, only works if there is enough room for all - * slices to have the minimum angle. - * - * @param minAngle minimum 0, maximum is half of {@link #setMaxAngle(float)} - */ - public void setMinAngleForSlices(float minAngle) { - - if (minAngle > (mMaxAngle / 2f)) - minAngle = mMaxAngle / 2f; - else if (minAngle < 0) - minAngle = 0f; - - this.mMinAngleForSlices = minAngle; - } - - @Override - protected void onDetachedFromWindow() { - // releases the bitmap in the renderer to avoid oom error - if (mRenderer != null && mRenderer instanceof PieChartRenderer) { - ((PieChartRenderer) mRenderer).releaseBitmap(); - } - super.onDetachedFromWindow(); - } - - @Override - public String getAccessibilityDescription() { - - PieData pieData = getData(); - - int entryCount = 0; - if (pieData != null) - entryCount = pieData.getEntryCount(); - - StringBuilder builder = new StringBuilder(); - - builder.append(String.format(Locale.getDefault(), "The pie chart has %d entries.", entryCount)); - - for (int i = 0; i < entryCount; i++) { - PieEntry entry = pieData.getDataSet().getEntryForIndex(i); - float percentage = (entry.getValue() / pieData.getYValueSum()) * 100; - builder.append(String.format(Locale.getDefault(), "%s has %.2f percent pie taken", - (TextUtils.isEmpty(entry.getLabel()) ? "No Label" : entry.getLabel()), - percentage)); - } - - return builder.toString(); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.kt new file mode 100644 index 0000000000..41ca66ca90 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieChart.kt @@ -0,0 +1,727 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.graphics.Canvas +import android.graphics.RectF +import android.graphics.Typeface +import android.text.TextUtils +import android.util.AttributeSet +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.highlight.PieHighlighter +import com.github.mikephil.charting.interfaces.datasets.IPieDataSet +import com.github.mikephil.charting.renderer.PieChartRenderer +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Utils +import java.util.Locale +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.min +import kotlin.math.sin + +/** + * View that represents a pie chart. Draws cake like slices. + * + * @author Philipp Jahoda + */ +open class PieChart : PieRadarChartBase { + /** + * returns the circlebox, the boundingbox of the pie-chart slices + * + * @return + */ + /** + * rect object that represents the bounds of the piechart, needed for + * drawing the circle + */ + val circleBox: RectF = RectF() + + override val data: PieData? + get() = super.mData + + /** + * Returns true if drawing the entry labels is enabled, false if not. + * + * @return + */ + /** + * flag indicating if entry labels should be drawn or not + */ + var isDrawEntryLabelsEnabled: Boolean = true + private set + + /** + * returns an integer array of all the different angles the chart slices + * have the angles in the returned array determine how much space (of 360°) + * each slice takes + * + * @return + */ + /** + * array that holds the width of each pie-slice in degrees + */ + var drawAngles: FloatArray = FloatArray(1) + private set + + /** + * returns the absolute angles of the different chart slices (where the + * slices end) + * + * @return + */ + /** + * array that holds the absolute angle in degrees of each slice + */ + var absoluteAngles: FloatArray = FloatArray(1) + private set + + /** + * returns true if the hole in the center of the pie-chart is set to be + * visible, false if not + * + * @return + */ + /** + * set this to true to draw the pie center empty + * + * @param enabled + */ + /** + * if true, the white hole inside the chart will be drawn + */ + var isDrawHoleEnabled: Boolean = true + + /** + * Returns true if the inner tips of the slices are visible behind the hole, + * false if not. + * + * @return true if slices are visible behind the hole. + */ + /** + * if true, the hole will see-through to the inner tips of the slices + */ + var isDrawSlicesUnderHoleEnabled: Boolean = false + private set + + /** + * Returns true if using percentage values is enabled for the chart. + * + * @return + */ + /** + * if true, the values inside the piechart are drawn as percent values + */ + var isUsePercentValuesEnabled: Boolean = false + private set + + /** + * Returns true if the chart is set to draw each end of a pie-slice + * "rounded". + * + * @return + */ + /** + * if true, the slices of the piechart are rounded + */ + var isDrawRoundedSlicesEnabled: Boolean = false + private set + + /** + * variable for the text that is drawn in the center of the pie-chart + */ + private var mCenterText: CharSequence? = "" + + private val mCenterTextOffset: MPPointF = MPPointF.Companion.getInstance(0f, 0f) + + /** + * Returns the size of the hole radius in percent of the total radius. + * + * @return + */ + /** + * sets the radius of the hole in the center of the piechart in percent of + * the maximum radius (max = the radius of the whole chart), default 50% + * + * @param percent + */ + /** + * indicates the size of the hole in the center of the piechart, default: + * radius / 2 + */ + var holeRadius: Float = 50f + + /** + * sets the radius of the transparent circle that is drawn next to the hole + * in the piechart in percent of the maximum radius (max = the radius of the + * whole chart), default 55% -> means 5% larger than the center-hole by + * default + * + * @param percent + */ + /** + * the radius of the transparent circle next to the chart-hole in the center + */ + var transparentCircleRadius: Float = 55f + + /** + * returns true if drawing the center text is enabled + * + * @return + */ + /** + * if enabled, centertext is drawn + */ + var isDrawCenterTextEnabled: Boolean = true + private set + + /** + * the rectangular radius of the bounding box for the center text, as a percentage of the pie + * hole + * default 1.f (100%) + */ + /** + * the rectangular radius of the bounding box for the center text, as a percentage of the pie + * hole + * default 1.f (100%) + */ + var centerTextRadiusPercent: Float = 100f + + protected var mMaxAngle: Float = 360f + + /** + * Minimum angle to draw slices, this only works if there is enough room for all slices to have + * the minimum angle, default 0f. + */ + private var mMinAngleForSlices = 0f + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + init { + mRenderer = PieChartRenderer(this, mAnimator, viewPortHandler) + + highlighter = PieHighlighter(this) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (mData == null) return + + mRenderer?.drawData(canvas) + + if (valuesToHighlight()) mRenderer?.drawHighlighted(canvas, highlighted!!) + + mRenderer?.drawExtras(canvas) + + mRenderer?.drawValues(canvas) + + legendRenderer.renderLegend(canvas) + + drawDescription(canvas) + + drawMarkers(canvas) + } + + override fun calculateOffsets() { + super.calculateOffsets() + + // prevent nullpointer when no data set + if (mData == null) return + + val diameter = diameter + val radius = diameter / 2f + + val c = centerOffsets + + val shift = mData!!.dataSet.selectionShift + + // create the circle box that will contain the pie-chart (the bounds of + // the pie-chart) + circleBox.set( + c.x - radius + shift, + c.y - radius + shift, + c.x + radius - shift, + c.y + radius - shift + ) + + MPPointF.Companion.recycleInstance(c) + } + + override fun calcMinMax() { + calcAngles() + } + + override fun getMarkerPosition(high: Highlight): FloatArray { + val center = this.centerCircleBox + var r = radius + + var off = r / 10f * 3.6f + + if (this.isDrawHoleEnabled) { + off = (r - (r / 100f * this.holeRadius)) / 2f + } + + r -= off // offset to keep things inside the chart + + val rotationAngle = rotationAngle + + val entryIndex = high.x.toInt() + + // offset needed to center the drawn text in the slice + val offset = this.drawAngles[entryIndex] / 2 + + // calculate the text position + val x = (r + * cos( + Math.toRadians( + ((rotationAngle + this.absoluteAngles[entryIndex] - offset) + * mAnimator.phaseY).toDouble() + ) + ) + center.x).toFloat() + val y = (r + * sin( + Math.toRadians( + ((rotationAngle + this.absoluteAngles[entryIndex] - offset) + * mAnimator.phaseY).toDouble() + ) + ) + center.y).toFloat() + + MPPointF.Companion.recycleInstance(center) + return floatArrayOf(x, y) + } + + /** + * calculates the needed angles for the chart slices + */ + private fun calcAngles() { + mData?.let { mData -> + val entryCount = mData.entryCount + + if (drawAngles.size != entryCount) { + this.drawAngles = FloatArray(entryCount) + } else { + for (i in 0.. a) return i + } + + return -1 // return -1 if no index found + } + + /** + * Returns the index of the DataSet this x-index belongs to. + * + * @param xIndex + * @return + */ + fun getDataSetIndexForIndex(xIndex: Int): Int { + val dataSets = mData?.dataSets ?: return -1 + + for (i in dataSets.indices) { + if (dataSets[i].getEntryForXValue(xIndex.toFloat(), Float.Companion.NaN) != null) return i + } + + return -1 + } + + /** + * Sets the color for the hole that is drawn in the center of the PieChart + * (if enabled). + * + * @param color + */ + fun setHoleColor(color: Int) { + (mRenderer as PieChartRenderer).paintHole.setColor(color) + } + + /** + * Enable or disable the visibility of the inner tips of the slices behind the hole + */ + fun setDrawSlicesUnderHole(enable: Boolean) { + this.isDrawSlicesUnderHoleEnabled = enable + } + + var centerText: CharSequence? + /** + * returns the text that is drawn in the center of the pie-chart + * + * @return + */ + get() = mCenterText + /** + * Sets the text String that is displayed in the center of the PieChart. + * + * @param text + */ + set(text) { + mCenterText = text ?: "" + } + + /** + * set this to true to draw the text that is displayed in the center of the + * pie chart + * + * @param enabled + */ + fun setDrawCenterText(enabled: Boolean) { + this.isDrawCenterTextEnabled = enabled + } + + override val requiredLegendOffset: Float + get() = legendRenderer.labelPaint.textSize * 2f + + override val requiredBaseOffset: Float + get() = 0f + + override val radius: Float + get() = min(circleBox.width() / 2f, circleBox.height() / 2f) + + val centerCircleBox: MPPointF + /** + * returns the center of the circlebox + * + * @return + */ + get() = MPPointF.Companion.getInstance(circleBox.centerX(), circleBox.centerY()) + + /** + * sets the typeface for the center-text paint + * + * @param t + */ + fun setCenterTextTypeface(t: Typeface?) { + (mRenderer as PieChartRenderer).paintCenterText.setTypeface(t) + } + + /** + * Sets the size of the center text of the PieChart in dp. + * + * @param sizeDp + */ + fun setCenterTextSize(sizeDp: Float) { + (mRenderer as PieChartRenderer).paintCenterText.textSize = Utils.convertDpToPixel(sizeDp) + } + + /** + * Sets the size of the center text of the PieChart in pixels. + * + * @param sizePixels + */ + fun setCenterTextSizePixels(sizePixels: Float) { + (mRenderer as PieChartRenderer).paintCenterText.textSize = sizePixels + } + + /** + * Sets the offset the center text should have from it's original position in dp. Default x = 0, y = 0 + * + * @param x + * @param y + */ + fun setCenterTextOffset(x: Float, y: Float) { + mCenterTextOffset.x = Utils.convertDpToPixel(x) + mCenterTextOffset.y = Utils.convertDpToPixel(y) + } + + val centerTextOffset: MPPointF + /** + * Returns the offset on the x- and y-axis the center text has in dp. + * + * @return + */ + get() = MPPointF.Companion.getInstance(mCenterTextOffset.x, mCenterTextOffset.y) + + /** + * Sets the color of the center text of the PieChart. + * + * @param color + */ + fun setCenterTextColor(color: Int) { + (mRenderer as PieChartRenderer).paintCenterText.setColor(color) + } + + /** + * Sets the color the transparent-circle should have. + * + * @param color + */ + fun setTransparentCircleColor(color: Int) { + val p = (mRenderer as PieChartRenderer).paintTransparentCircle + val alpha = p.alpha + p.setColor(color) + p.setAlpha(alpha) + } + + /** + * Sets the amount of transparency the transparent circle should have 0 = fully transparent, + * 255 = fully opaque. + * Default value is 100. + * + * @param alpha 0-255 + */ + fun setTransparentCircleAlpha(alpha: Int) { + (mRenderer as PieChartRenderer).paintTransparentCircle.setAlpha(alpha) + } + + /** + * Set this to true to draw the entry labels into the pie slices (Provided by the getLabel() method of the PieEntry class). + * Deprecated -> use setDrawEntryLabels(...) instead. + * + * @param enabled + */ + @Deprecated("") + fun setDrawSliceText(enabled: Boolean) { + this.isDrawEntryLabelsEnabled = enabled + } + + /** + * Set this to true to draw the entry labels into the pie slices (Provided by the getLabel() method of the PieEntry class). + * + * @param enabled + */ + fun setDrawEntryLabels(enabled: Boolean) { + this.isDrawEntryLabelsEnabled = enabled + } + + /** + * Sets the color the entry labels are drawn with. + * + * @param color + */ + fun setEntryLabelColor(color: Int) { + (mRenderer as PieChartRenderer).paintEntryLabels.setColor(color) + } + + /** + * Sets a custom Typeface for the drawing of the entry labels. + * + * @param tf + */ + fun setEntryLabelTypeface(tf: Typeface?) { + (mRenderer as PieChartRenderer).paintEntryLabels.setTypeface(tf) + } + + /** + * Sets the size of the entry labels in dp. Default: 13dp + * + * @param size + */ + fun setEntryLabelTextSize(size: Float) { + (mRenderer as PieChartRenderer).paintEntryLabels.textSize = Utils.convertDpToPixel(size) + } + + /** + * Sets whether to draw slices in a curved fashion, only works if drawing the hole is enabled + * and if the slices are not drawn under the hole. + * + * @param enabled draw curved ends of slices + */ + fun setDrawRoundedSlices(enabled: Boolean) { + this.isDrawRoundedSlicesEnabled = enabled + } + + /** + * If this is enabled, values inside the PieChart are drawn in percent and + * not with their original value. Values provided for the IValueFormatter to + * format are then provided in percent. + * + * @param enabled + */ + fun setUsePercentValues(enabled: Boolean) { + this.isUsePercentValuesEnabled = enabled + } + + var maxAngle: Float + get() = mMaxAngle + /** + * Sets the max angle that is used for calculating the pie-circle. 360f means + * it's a full PieChart, 180f results in a half-pie-chart. Default: 360f + * + * @param maxangle min 90, max 360 + */ + set(maxangle) { + var maxangle = maxangle + if (maxangle > 360) maxangle = 360f + + if (maxangle < 90) maxangle = 90f + + this.mMaxAngle = maxangle + } + + var minAngleForSlices: Float + /** + * The minimum angle slices on the chart are rendered with, default is 0f. + * + * @return minimum angle for slices + */ + get() = mMinAngleForSlices + /** + * Set the angle to set minimum size for slices, you must call [.notifyDataSetChanged] + * and [.invalidate] when changing this, only works if there is enough room for all + * slices to have the minimum angle. + * + * @param minAngle minimum 0, maximum is half of [.setMaxAngle] + */ + set(minAngle) { + var minAngle = minAngle + if (minAngle > (mMaxAngle / 2f)) minAngle = mMaxAngle / 2f + else if (minAngle < 0) minAngle = 0f + + this.mMinAngleForSlices = minAngle + } + + override fun onDetachedFromWindow() { + // releases the bitmap in the renderer to avoid oom error + if (mRenderer != null && mRenderer is PieChartRenderer) { + (mRenderer as PieChartRenderer).releaseBitmap() + } + super.onDetachedFromWindow() + } + + override val accessibilityDescription: String? + get() { + val pieData = mData + + var entryCount = 0 + if (pieData != null) entryCount = pieData.entryCount + + val builder = StringBuilder() + + builder.append(String.format(Locale.getDefault(), "The pie chart has %d entries.", entryCount)) + + for (i in 0..>> - extends Chart { - - /** - * holds the normalized version of the current rotation angle of the chart - */ - private float mRotationAngle = 270f; - - /** - * holds the raw version of the current rotation angle of the chart - */ - private float mRawRotationAngle = 270f; - - /** - * flag that indicates if rotation is enabled or not - */ - protected boolean mRotateEnabled = true; - - /** - * Sets the minimum offset (padding) around the chart, defaults to 0.f - */ - protected float mMinOffset = 0.f; - - 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(); - - mChartTouchListener = new PieRadarChartTouchListener(this); - } - - @Override - protected void calcMinMax() { - //mXAxis.mAxisRange = mData.getXVals().size() - 1; - } - - @Override - public int getMaxVisibleCount() { - return mData.getEntryCount(); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - // use the pie- and radarchart listener own listener - if (mTouchEnabled && mChartTouchListener != null) - return mChartTouchListener.onTouch(this, event); - else - return super.onTouchEvent(event); - } - - @Override - public void computeScroll() { - - if (mChartTouchListener instanceof PieRadarChartTouchListener) - ((PieRadarChartTouchListener) mChartTouchListener).computeScroll(); - } - - @Override - public void notifyDataSetChanged() { - if (mData == null) - return; - - calcMinMax(); - - if (mLegend != null) - mLegendRenderer.computeLegend(mData); - - calculateOffsets(); - } - - @Override - public void calculateOffsets() { - - float legendLeft = 0f, legendRight = 0f, legendBottom = 0f, legendTop = 0f; - - if (mLegend != null && mLegend.isEnabled() && !mLegend.isDrawInsideEnabled()) { - - float fullLegendWidth = Math.min(mLegend.mNeededWidth, - mViewPortHandler.getChartWidth() * mLegend.getMaxSizePercent()); - - switch (mLegend.getOrientation()) { - case VERTICAL: { - float xLegendOffset = 0.f; - - if (mLegend.getHorizontalAlignment() == Legend.LegendHorizontalAlignment.LEFT - || mLegend.getHorizontalAlignment() == Legend.LegendHorizontalAlignment.RIGHT) { - if (mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.CENTER) { - // this is the space between the legend and the chart - final float spacing = Utils.convertDpToPixel(13f); - - xLegendOffset = fullLegendWidth + spacing; - - } else { - // this is the space between the legend and the chart - float spacing = Utils.convertDpToPixel(8f); - - float legendWidth = fullLegendWidth + spacing; - float legendHeight = mLegend.mNeededHeight + mLegend.mTextHeightMax; - - MPPointF center = getCenter(); - - float bottomX = mLegend.getHorizontalAlignment() == - Legend.LegendHorizontalAlignment.RIGHT - ? getWidth() - legendWidth + 15.f - : legendWidth - 15.f; - float bottomY = legendHeight + 15.f; - float distLegend = distanceToCenter(bottomX, bottomY); - - MPPointF reference = getPosition(center, getRadius(), - getAngleForPoint(bottomX, bottomY)); - - float distReference = distanceToCenter(reference.x, reference.y); - float minOffset = Utils.convertDpToPixel(5f); - - if (bottomY >= center.y && getHeight() - legendWidth > getWidth()) { - xLegendOffset = legendWidth; - } else if (distLegend < distReference) { - - float diff = distReference - distLegend; - xLegendOffset = minOffset + diff; - } - - MPPointF.recycleInstance(center); - MPPointF.recycleInstance(reference); - } - } - - switch (mLegend.getHorizontalAlignment()) { - case LEFT: - legendLeft = xLegendOffset; - break; - - case RIGHT: - legendRight = xLegendOffset; - break; - - case CENTER: - switch (mLegend.getVerticalAlignment()) { - case TOP: - legendTop = Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - break; - case BOTTOM: - legendBottom = Math.min(mLegend.mNeededHeight, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - break; - } - break; - } - } - break; - - case HORIZONTAL: - float yLegendOffset = 0.f; - - if (mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.TOP || - mLegend.getVerticalAlignment() == Legend.LegendVerticalAlignment.BOTTOM) { - - // It's possible that we do not need this offset anymore as it - // is available through the extraOffsets, but changing it can mean - // changing default visibility for existing apps. - float yOffset = getRequiredLegendOffset(); - - yLegendOffset = Math.min(mLegend.mNeededHeight + yOffset, - mViewPortHandler.getChartHeight() * mLegend.getMaxSizePercent()); - - switch (mLegend.getVerticalAlignment()) { - case TOP: - legendTop = yLegendOffset; - break; - case BOTTOM: - legendBottom = yLegendOffset; - break; - } - } - break; - } - - legendLeft += getRequiredBaseOffset(); - legendRight += getRequiredBaseOffset(); - legendTop += getRequiredBaseOffset(); - legendBottom += getRequiredBaseOffset(); - } - - float minOffset = Utils.convertDpToPixel(mMinOffset); - - if (this instanceof RadarChart) { - XAxis x = this.getXAxis(); - - if (x.isEnabled() && x.isDrawLabelsEnabled()) { - minOffset = Math.max(minOffset, x.mLabelWidth); - } - } - - legendTop += getExtraTopOffset(); - legendRight += getExtraRightOffset(); - legendBottom += getExtraBottomOffset(); - legendLeft += getExtraLeftOffset(); - - float offsetLeft = Math.max(minOffset, legendLeft); - float offsetTop = Math.max(minOffset, legendTop); - float offsetRight = Math.max(minOffset, legendRight); - float offsetBottom = Math.max(minOffset, Math.max(getRequiredBaseOffset(), legendBottom)); - - mViewPortHandler.restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom); - - if (mLogEnabled) - Log.i(LOG_TAG, "offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop - + ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom); - } - - /** - * 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) { - - MPPointF 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; - - MPPointF.recycleInstance(c); - - return angle; - } - - /** - * Returns a recyclable MPPointF instance. - * 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 - */ - public MPPointF getPosition(MPPointF center, float dist, float angle) { - - MPPointF p = MPPointF.getInstance(0, 0); - getPosition(center, dist, angle, p); - return p; - } - - public void getPosition(MPPointF center, float dist, float angle, MPPointF outputPoint) { - outputPoint.x = (float) (center.x + dist * Math.cos(Math.toRadians(angle))); - outputPoint.y = (float) (center.y + dist * Math.sin(Math.toRadians(angle))); - } - - /** - * Returns the distance of a certain point on the chart to the center of the - * chart. - * - * @param x - * @param y - * @return - */ - public float distanceToCenter(float x, float y) { - - MPPointF 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)); - - MPPointF.recycleInstance(c); - - 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) { - mRawRotationAngle = angle; - mRotationAngle = Utils.getNormalizedAngle(mRawRotationAngle); - } - - /** - * gets the raw version of the current rotation angle of the pie chart the - * returned value could be any value, negative or positive, outside of the - * 360 degrees. this is used when working with rotation direction, mainly by - * gestures and animations. - * - * @return - */ - public float getRawRotationAngle() { - return mRawRotationAngle; - } - - /** - * gets a normalized version of the current rotation angle of the pie chart, - * which will always be between 0.0 < 360.0 - * - * @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; - } - - /** - * Gets the minimum offset (padding) around the chart, defaults to 0.f - */ - public float getMinOffset() { - return mMinOffset; - } - - /** - * Sets the minimum offset (padding) around the chart, defaults to 0.f - */ - public void setMinOffset(float minOffset) { - mMinOffset = minOffset; - } - - /** - * returns the diameter of the pie- or radar-chart - * - * @return - */ - public float getDiameter() { - RectF content = mViewPortHandler.getContentRect(); - content.left += getExtraLeftOffset(); - content.top += getExtraTopOffset(); - content.right -= getExtraRightOffset(); - content.bottom -= getExtraBottomOffset(); - return Math.min(content.width(), content.height()); - } - - /** - * Returns the radius of the chart in pixels. - * - * @return - */ - public abstract float getRadius(); - - /** - * Returns the required offset for the chart legend. - * - * @return - */ - protected abstract float getRequiredLegendOffset(); - - /** - * Returns the base offset needed for the chart without calculating the - * legend size. - * - * @return - */ - protected abstract float getRequiredBaseOffset(); - - @Override - public float getYChartMax() { - // TODO Auto-generated method stub - return 0; - } - - @Override - public float getYChartMin() { - // TODO Auto-generated method stub - return 0; - } - - /** - * ################ ################ ################ ################ - */ - /** CODE BELOW THIS RELATED TO ANIMATION */ - - /** - * Applys a spin animation to the Chart. - * - * @param durationmillis - * @param fromangle - * @param toangle - */ - @SuppressLint("NewApi") - public void spin(int durationmillis, float fromangle, float toangle, EasingFunction easing) { - - setRotationAngle(fromangle); - - ObjectAnimator spinAnimator = ObjectAnimator.ofFloat(this, "rotationAngle", fromangle, - toangle); - spinAnimator.setDuration(durationmillis); - spinAnimator.setInterpolator(easing); - - spinAnimator.addUpdateListener(new AnimatorUpdateListener() { - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - postInvalidate(); - } - }); - spinAnimator.start(); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieRadarChartBase.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieRadarChartBase.kt new file mode 100644 index 0000000000..e37864dd0c --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/PieRadarChartBase.kt @@ -0,0 +1,440 @@ +package com.github.mikephil.charting.charts + +import android.animation.ObjectAnimator +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import android.util.Log +import android.view.MotionEvent +import com.github.mikephil.charting.animation.Easing.EasingFunction +import com.github.mikephil.charting.components.Legend.LegendHorizontalAlignment +import com.github.mikephil.charting.components.Legend.LegendOrientation +import com.github.mikephil.charting.components.Legend.LegendVerticalAlignment +import com.github.mikephil.charting.data.ChartData +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.interfaces.datasets.IDataSet +import com.github.mikephil.charting.listener.PieRadarChartTouchListener +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Utils +import kotlin.math.acos +import kotlin.math.cos +import kotlin.math.max +import kotlin.math.min +import kotlin.math.pow +import kotlin.math.sin +import kotlin.math.sqrt + +/** + * Baseclass of PieChart and RadarChart. + * + * @author Philipp Jahoda + */ +abstract class PieRadarChartBase, T : ChartData> + : Chart { + /** + * holds the normalized version of the current rotation angle of the chart + */ + private var mRotationAngle = 270f + + /** + * gets the raw version of the current rotation angle of the pie chart the + * returned value could be any value, negative or positive, outside of the + * 360 degrees. this is used when working with rotation direction, mainly by + * gestures and animations. + * + * @return + */ + /** + * holds the raw version of the current rotation angle of the chart + */ + var rawRotationAngle: Float = 270f + private set + + /** + * Returns true if rotation of the chart by touch is enabled, false if not. + * + * @return + */ + /** + * 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 + */ + /** + * flag that indicates if rotation is enabled or not + */ + var isRotationEnabled: Boolean = true + + /** + * Gets the minimum offset (padding) around the chart, defaults to 0.f + */ + /** + * Sets the minimum offset (padding) around the chart, defaults to 0.f + */ + /** + * Sets the minimum offset (padding) around the chart, defaults to 0.f + */ + var minOffset: Float = 0f + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + init { + mChartTouchListener = PieRadarChartTouchListener(this) + } + + override fun calcMinMax() { + //mXAxis.mAxisRange = mData.getXVals().size() - 1; + } + + override val maxVisibleCount: Int + get() = mData?.entryCount ?: 0 + + override fun onTouchEvent(event: MotionEvent?): Boolean { + // use the pie- and radarchart listener own listener + return if (mTouchEnabled && mChartTouchListener != null) mChartTouchListener?.onTouch(this, event) == true + else super.onTouchEvent(event) + } + + override fun computeScroll() { + if (mChartTouchListener is PieRadarChartTouchListener) (mChartTouchListener as PieRadarChartTouchListener).computeScroll() + } + + override fun notifyDataSetChanged() { + if (mData == null) return + + calcMinMax() + + legendRenderer.computeLegend(mData!!) + + calculateOffsets() + } + + public override fun calculateOffsets() { + var legendLeft = 0f + var legendRight = 0f + var legendBottom = 0f + var legendTop = 0f + + if (legend.isEnabled && !legend.isDrawInsideEnabled) { + val fullLegendWidth = min( + legend.mNeededWidth, + viewPortHandler.chartWidth * legend.maxSizePercent + ) + + when (legend.orientation) { + LegendOrientation.VERTICAL -> { + var xLegendOffset = 0f + + if (legend.horizontalAlignment == LegendHorizontalAlignment.LEFT + || legend.horizontalAlignment == LegendHorizontalAlignment.RIGHT + ) { + if (legend.verticalAlignment == LegendVerticalAlignment.CENTER) { + // this is the space between the legend and the chart + val spacing = Utils.convertDpToPixel(13f) + + xLegendOffset = fullLegendWidth + spacing + } else { + // this is the space between the legend and the chart + val spacing = Utils.convertDpToPixel(8f) + + val legendWidth = fullLegendWidth + spacing + val legendHeight = legend.mNeededHeight + legend.mTextHeightMax + + val center = center + + val bottomX = if (legend.horizontalAlignment == + LegendHorizontalAlignment.RIGHT + ) + width - legendWidth + 15f + else + legendWidth - 15f + val bottomY = legendHeight + 15f + val distLegend = distanceToCenter(bottomX, bottomY) + + val reference = getPosition( + center, this.radius, + getAngleForPoint(bottomX, bottomY) + ) + + val distReference = distanceToCenter(reference.x, reference.y) + val minOffset = Utils.convertDpToPixel(5f) + + if (bottomY >= center.y && height - legendWidth > width) { + xLegendOffset = legendWidth + } else if (distLegend < distReference) { + val diff = distReference - distLegend + xLegendOffset = minOffset + diff + } + + MPPointF.Companion.recycleInstance(center) + MPPointF.Companion.recycleInstance(reference) + } + } + + when (legend.horizontalAlignment) { + LegendHorizontalAlignment.LEFT -> legendLeft = xLegendOffset + LegendHorizontalAlignment.RIGHT -> legendRight = xLegendOffset + LegendHorizontalAlignment.CENTER -> when (legend.verticalAlignment) { + LegendVerticalAlignment.TOP -> legendTop = min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + + LegendVerticalAlignment.BOTTOM -> legendBottom = min( + legend.mNeededHeight, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + + else -> {} + } + } + } + + LegendOrientation.HORIZONTAL -> { + var yLegendOffset: Float + + if (legend.verticalAlignment == LegendVerticalAlignment.TOP || + legend.verticalAlignment == LegendVerticalAlignment.BOTTOM + ) { + // It's possible that we do not need this offset anymore as it + // is available through the extraOffsets, but changing it can mean + // changing default visibility for existing apps. + + val yOffset = this.requiredLegendOffset + + yLegendOffset = min( + legend.mNeededHeight + yOffset, + viewPortHandler.chartHeight * legend.maxSizePercent + ) + + when (legend.verticalAlignment) { + LegendVerticalAlignment.TOP -> legendTop = yLegendOffset + LegendVerticalAlignment.BOTTOM -> legendBottom = yLegendOffset + else -> {} + } + } + } + } + + legendLeft += this.requiredBaseOffset + legendRight += this.requiredBaseOffset + legendTop += this.requiredBaseOffset + legendBottom += this.requiredBaseOffset + } + + var minOffset = Utils.convertDpToPixel(this.minOffset) + + if (this is RadarChart) { + val x = this.xAxis + + if (x.isEnabled && x.isDrawLabelsEnabled) { + minOffset = max(minOffset, x.mLabelWidth.toFloat()) + } + } + + legendTop += extraTopOffset + legendRight += extraRightOffset + legendBottom += extraBottomOffset + legendLeft += extraLeftOffset + + val offsetLeft = max(minOffset, legendLeft) + val offsetTop = max(minOffset, legendTop) + val offsetRight = max(minOffset, legendRight) + val offsetBottom = max(minOffset, max(this.requiredBaseOffset, legendBottom)) + + viewPortHandler.restrainViewPort(offsetLeft, offsetTop, offsetRight, offsetBottom) + + if (isLogEnabled) Log.i( + LOG_TAG, ("offsetLeft: " + offsetLeft + ", offsetTop: " + offsetTop + + ", offsetRight: " + offsetRight + ", offsetBottom: " + offsetBottom) + ) + } + + /** + * 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 + */ + fun getAngleForPoint(x: Float, y: Float): Float { + val c = centerOffsets + + val tx = (x - c.x).toDouble() + val ty = (y - c.y).toDouble() + val length = sqrt(tx * tx + ty * ty) + val r = acos(ty / length) + + var angle = Math.toDegrees(r).toFloat() + + if (x > c.x) angle = 360f - angle + + // add 90° because chart starts EAST + angle = angle + 90f + + // neutralize overflow + if (angle > 360f) angle = angle - 360f + + MPPointF.Companion.recycleInstance(c) + + return angle + } + + /** + * Returns a recyclable MPPointF instance. + * 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 + */ + fun getPosition(center: MPPointF, dist: Float, angle: Float): MPPointF { + val p: MPPointF = MPPointF.Companion.getInstance(0f, 0f) + getPosition(center, dist, angle, p) + return p + } + + fun getPosition(center: MPPointF, dist: Float, angle: Float, outputPoint: MPPointF) { + outputPoint.x = (center.x + dist * cos(Math.toRadians(angle.toDouble()))).toFloat() + outputPoint.y = (center.y + dist * sin(Math.toRadians(angle.toDouble()))).toFloat() + } + + /** + * Returns the distance of a certain point on the chart to the center of the + * chart. + * + * @param x + * @param y + * @return + */ + fun distanceToCenter(x: Float, y: Float): Float { + val c = centerOffsets + + var dist: Float + + val xDist = if (x > c.x) { + x - c.x + } else { + c.x - x + } + + val yDist = if (y > c.y) { + y - c.y + } else { + c.y - y + } + + // pythagoras + dist = sqrt(xDist.toDouble().pow(2.0) + yDist.toDouble().pow(2.0)).toFloat() + + MPPointF.Companion.recycleInstance(c) + + return dist + } + + /** + * Returns the xIndex for the given angle around the center of the chart. + * Returns -1 if not found / outofbounds. + * + * @param angle + * @return + */ + abstract fun getIndexForAngle(angle: Float): Int + + var rotationAngle: Float + /** + * gets a normalized version of the current rotation angle of the pie chart, + * which will always be between 0.0 < 360.0 + * + * @return + */ + get() = mRotationAngle + /** + * Set an offset for the rotation of the RadarChart in degrees. Default 270f + * --> top (NORTH) + * + * @param angle + */ + set(angle) { + this.rawRotationAngle = angle + mRotationAngle = Utils.getNormalizedAngle(this.rawRotationAngle) + } + + val diameter: Float + /** + * returns the diameter of the pie- or radar-chart + * + * @return + */ + get() { + val content = viewPortHandler.contentRect + content.left += extraLeftOffset + content.top += extraTopOffset + content.right -= extraRightOffset + content.bottom -= extraBottomOffset + return min(content.width(), content.height()) + } + + /** + * Returns the radius of the chart in pixels. + * + * @return + */ + abstract val radius: Float + + /** + * Returns the required offset for the chart legend. + * + * @return + */ + protected abstract val requiredLegendOffset: Float + + /** + * Returns the base offset needed for the chart without calculating the + * legend size. + * + * @return + */ + protected abstract val requiredBaseOffset: Float + + override val yChartMax: Float + get() = 0f + + override val yChartMin: Float + get() = 0f + + /** + * ################ ################ ################ ################ + */ + /** CODE BELOW THIS RELATED TO ANIMATION */ + /** + * Applys a spin animation to the Chart. + * + * @param durationmillis + * @param fromangle + * @param toangle + */ + @SuppressLint("NewApi") + fun spin(durationmillis: Int, fromangle: Float, toangle: Float, easing: EasingFunction?) { + this.rotationAngle = fromangle + + val spinAnimator = ObjectAnimator.ofFloat( + this, "rotationAngle", fromangle, + toangle + ) + spinAnimator.setDuration(durationmillis.toLong()) + spinAnimator.interpolator = easing + + spinAnimator.addUpdateListener { postInvalidate() } + spinAnimator.start() + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.java deleted file mode 100644 index 524e222bb6..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.java +++ /dev/null @@ -1,390 +0,0 @@ - -package com.github.mikephil.charting.charts; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Color; -import android.graphics.RectF; -import android.util.AttributeSet; - -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.data.RadarData; -import com.github.mikephil.charting.highlight.RadarHighlighter; -import com.github.mikephil.charting.renderer.RadarChartRenderer; -import com.github.mikephil.charting.renderer.XAxisRendererRadarChart; -import com.github.mikephil.charting.renderer.YAxisRendererRadarChart; -import com.github.mikephil.charting.utils.Utils; - -import java.util.List; - -/** - * Implementation of the RadarChart, a "spidernet"-like chart. It works best - * when displaying 5-10 entries per DataSet. - * - * @author Philipp Jahoda - */ -public class RadarChart extends PieRadarChartBase { - - /** - * width of the main web lines - */ - private float mWebLineWidth = 2.5f; - - /** - * width of the inner web lines - */ - private float mInnerWebLineWidth = 1.5f; - - /** - * color for the main web lines - */ - private int mWebColor = Color.rgb(122, 122, 122); - - /** - * color for the inner web - */ - private int mWebColorInner = Color.rgb(122, 122, 122); - - /** - * transparency the grid is drawn with (0-255) - */ - private int mWebAlpha = 150; - - /** - * flag indicating if the web lines should be drawn or not - */ - private boolean mDrawWeb = true; - - /** - * modulus that determines how many labels and web-lines are skipped before the next is drawn - */ - private int mSkipWebLineCount = 0; - - /** - * the object reprsenting the y-axis labels - */ - private YAxis mYAxis; - - private List colorList; - - protected YAxisRendererRadarChart mYAxisRenderer; - protected XAxisRendererRadarChart mXAxisRenderer; - - public RadarChart(Context context) { - super(context); - } - - public RadarChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public RadarChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - @Override - protected void init() { - super.init(); - - mYAxis = new YAxis(AxisDependency.LEFT); - mYAxis.setLabelXOffset(10f); - - mWebLineWidth = Utils.convertDpToPixel(1.5f); - mInnerWebLineWidth = Utils.convertDpToPixel(0.75f); - - mRenderer = new RadarChartRenderer(this, mAnimator, mViewPortHandler); - mYAxisRenderer = new YAxisRendererRadarChart(mViewPortHandler, mYAxis, this); - mXAxisRenderer = new XAxisRendererRadarChart(mViewPortHandler, mXAxis, this); - - mHighlighter = new RadarHighlighter(this); - } - - @Override - protected void calcMinMax() { - super.calcMinMax(); - - mYAxis.calculate(mData.getYMin(AxisDependency.LEFT), mData.getYMax(AxisDependency.LEFT)); - mXAxis.calculate(0, mData.getMaxEntryCountSet().getEntryCount()); - } - - @Override - public void notifyDataSetChanged() { - if (mData == null) - return; - - calcMinMax(); - - mYAxisRenderer.computeAxis(mYAxis.mAxisMinimum, mYAxis.mAxisMaximum, mYAxis.isInverted()); - mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false); - - if (mLegend != null && !mLegend.isLegendCustom()) - mLegendRenderer.computeLegend(mData); - - calculateOffsets(); - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mData == null) - return; - -// if (mYAxis.isEnabled()) -// mYAxisRenderer.computeAxis(mYAxis.mAxisMinimum, mYAxis.mAxisMaximum, mYAxis.isInverted()); - - if (mXAxis.isEnabled()) - mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false); - - mXAxisRenderer.renderAxisLabels(canvas); - - if (mDrawWeb) - mRenderer.drawExtras(canvas); - - if (mYAxis.isEnabled() && mYAxis.isDrawLimitLinesBehindDataEnabled()) - mYAxisRenderer.renderLimitLines(canvas); - - mRenderer.drawData(canvas); - - if (valuesToHighlight()) - mRenderer.drawHighlighted(canvas, mIndicesToHighlight); - - if (mYAxis.isEnabled() && !mYAxis.isDrawLimitLinesBehindDataEnabled()) - mYAxisRenderer.renderLimitLines(canvas); - - mYAxisRenderer.renderAxisLabels(canvas); - - mRenderer.drawValues(canvas); - - mLegendRenderer.renderLegend(canvas); - - drawDescription(canvas); - - drawMarkers(canvas); - } - - /** - * Returns the factor that is needed to transform values into pixels. - * - * @return - */ - public float getFactor() { - RectF content = mViewPortHandler.getContentRect(); - return Math.min(content.width() / 2f, content.height() / 2f) / mYAxis.mAxisRange; - } - - /** - * Returns the angle that each slice in the radar chart occupies. - * - * @return - */ - public float getSliceAngle() { - return 360f / (float) mData.getMaxEntryCountSet().getEntryCount(); - } - - - public void setLayerColorList(List colorList) { - if (colorList == null || colorList.size() == 0) { - return; - } - this.colorList = colorList; - } - - public boolean isCustomLayerColorEnable() { - if (mData == null) { - return false; - } - return colorList != null && colorList.size() == getYAxis().mEntryCount; - } - - public List getLayerColorList() { - return colorList; - } - - @Override - public int getIndexForAngle(float angle) { - - // take the current angle of the chart into consideration - float a = Utils.getNormalizedAngle(angle - getRotationAngle()); - - float sliceangle = getSliceAngle(); - - int max = mData.getMaxEntryCountSet().getEntryCount(); - - int index = 0; - - for (int i = 0; i < max; i++) { - - float referenceAngle = sliceangle * (i + 1) - sliceangle / 2f; - - if (referenceAngle > a) { - index = i; - break; - } - } - - return index; - } - - /** - * Returns the object that represents all y-labels of the RadarChart. - * - * @return - */ - public YAxis getYAxis() { - return mYAxis; - } - - /** - * Sets the width of the web lines that come from the center. - * - * @param width - */ - public void setWebLineWidth(float width) { - mWebLineWidth = Utils.convertDpToPixel(width); - } - - public float getWebLineWidth() { - return mWebLineWidth; - } - - /** - * Sets the width of the web lines that are in between the lines coming from - * the center. - * - * @param width - */ - public void setWebLineWidthInner(float width) { - mInnerWebLineWidth = Utils.convertDpToPixel(width); - } - - public float getWebLineWidthInner() { - return mInnerWebLineWidth; - } - - /** - * Sets the transparency (alpha) value for all web lines, default: 150, 255 - * = 100% opaque, 0 = 100% transparent - * - * @param alpha - */ - public void setWebAlpha(int alpha) { - mWebAlpha = alpha; - } - - /** - * Returns the alpha value for all web lines. - * - * @return - */ - public int getWebAlpha() { - return mWebAlpha; - } - - /** - * Sets the color for the web lines that come from the center. Don't forget - * to use getResources().getColor(...) when loading a color from the - * resources. Default: Color.rgb(122, 122, 122) - * - * @param color - */ - public void setWebColor(int color) { - mWebColor = color; - } - - public int getWebColor() { - return mWebColor; - } - - /** - * Sets the color for the web lines in between the lines that come from the - * center. Don't forget to use getResources().getColor(...) when loading a - * color from the resources. Default: Color.rgb(122, 122, 122) - * - * @param color - */ - public void setWebColorInner(int color) { - mWebColorInner = color; - } - - public int getWebColorInner() { - return mWebColorInner; - } - - /** - * If set to true, drawing the web is enabled, if set to false, drawing the - * whole web is disabled. Default: true - * - * @param enabled - */ - public void setDrawWeb(boolean enabled) { - mDrawWeb = enabled; - } - - /** - * Sets the number of web-lines that should be skipped on chart web before the - * next one is drawn. This targets the lines that come from the center of the RadarChart. - * - * @param count if count = 1 -> 1 line is skipped in between - */ - public void setSkipWebLineCount(int count) { - - mSkipWebLineCount = Math.max(0, count); - } - - /** - * Returns the modulus that is used for skipping web-lines. - * - * @return - */ - public int getSkipWebLineCount() { - return mSkipWebLineCount; - } - - @Override - protected float getRequiredLegendOffset() { - return mLegendRenderer.getLabelPaint().getTextSize() * 4.f; - } - - @Override - protected float getRequiredBaseOffset() { - return mXAxis.isEnabled() && mXAxis.isDrawLabelsEnabled() ? - mXAxis.mLabelWidth : - Utils.convertDpToPixel(10f); - } - - @Override - public float getRadius() { - RectF content = mViewPortHandler.getContentRect(); - return Math.min(content.width() / 2f, content.height() / 2f); - } - - /** - * Returns the maximum value this chart can display on it's y-axis. - */ - public float getYChartMax() { - return mYAxis.mAxisMaximum; - } - - /** - * Returns the minimum value this chart can display on it's y-axis. - */ - public float getYChartMin() { - return mYAxis.mAxisMinimum; - } - - /** - * Returns the range of y-values this chart can display. - * - * @return - */ - public float getYRange() { - return mYAxis.mAxisRange; - } - - @Override - public String getAccessibilityDescription() { - return "This is a Radar chart"; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.kt new file mode 100644 index 0000000000..3ff51154f5 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/RadarChart.kt @@ -0,0 +1,320 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.util.AttributeSet +import com.github.mikephil.charting.components.YAxis +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.RadarData +import com.github.mikephil.charting.data.RadarEntry +import com.github.mikephil.charting.highlight.RadarHighlighter +import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet +import com.github.mikephil.charting.renderer.RadarChartRenderer +import com.github.mikephil.charting.renderer.XAxisRendererRadarChart +import com.github.mikephil.charting.renderer.YAxisRendererRadarChart +import com.github.mikephil.charting.utils.Utils +import kotlin.math.max +import kotlin.math.min + +/** + * Implementation of the RadarChart, a "spidernet"-like chart. It works best + * when displaying 5-10 entries per DataSet. + * + * @author Philipp Jahoda + */ +open class RadarChart : PieRadarChartBase { + /** + * width of the main web lines + */ + private var mWebLineWidth = 2.5f + + /** + * width of the inner web lines + */ + private var mInnerWebLineWidth = 1.5f + + /** + * Sets the color for the web lines that come from the center. Don't forget + * to use getResources().getColor(...) when loading a color from the + * resources. Default: Color.rgb(122, 122, 122) + * + * @param color + */ + /** + * color for the main web lines + */ + var webColor: Int = Color.rgb(122, 122, 122) + + /** + * Sets the color for the web lines in between the lines that come from the + * center. Don't forget to use getResources().getColor(...) when loading a + * color from the resources. Default: Color.rgb(122, 122, 122) + * + * @param color + */ + /** + * color for the inner web + */ + var webColorInner: Int = Color.rgb(122, 122, 122) + + /** + * Returns the alpha value for all web lines. + * + * @return + */ + /** + * Sets the transparency (alpha) value for all web lines, default: 150, 255 + * = 100% opaque, 0 = 100% transparent + * + * @param alpha + */ + /** + * transparency the grid is drawn with (0-255) + */ + var webAlpha: Int = 150 + + /** + * flag indicating if the web lines should be drawn or not + */ + private var mDrawWeb = true + + /** + * modulus that determines how many labels and web-lines are skipped before the next is drawn + */ + private var mSkipWebLineCount = 0 + + /** + * the object reprsenting the y-axis labels + */ + private val mYAxis: YAxis = YAxis(AxisDependency.LEFT) + + private var colorList: MutableList = arrayListOf() + + protected val mYAxisRenderer: YAxisRendererRadarChart = YAxisRendererRadarChart(viewPortHandler, mYAxis, this) + protected val mXAxisRenderer: XAxisRendererRadarChart = XAxisRendererRadarChart(viewPortHandler, mXAxis, this) + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + init { + mYAxis.labelXOffset = 10f + + mWebLineWidth = Utils.convertDpToPixel(1.5f) + mInnerWebLineWidth = Utils.convertDpToPixel(0.75f) + + mRenderer = RadarChartRenderer(this, mAnimator, viewPortHandler) + + highlighter = RadarHighlighter(this) + } + + override fun calcMinMax() { + super.calcMinMax() + + mData?.let { mData -> + mYAxis.calculate(mData.getYMin(AxisDependency.LEFT), mData.getYMax(AxisDependency.LEFT)) + mXAxis.calculate(0f, mData.maxEntryCountSet?.entryCount?.toFloat() ?: 0f) + } + } + + override fun notifyDataSetChanged() { + if (mData == null) return + + calcMinMax() + + mYAxisRenderer.computeAxis(mYAxis.mAxisMinimum, mYAxis.mAxisMaximum, mYAxis.isInverted) + mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false) + + if (!legend.isLegendCustom) legendRenderer.computeLegend(mData!!) + + calculateOffsets() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (mData == null) return + + // if (mYAxis.isEnabled()) +// mYAxisRenderer.computeAxis(mYAxis.mAxisMinimum, mYAxis.mAxisMaximum, mYAxis.isInverted()); + if (mXAxis.isEnabled) mXAxisRenderer.computeAxis(mXAxis.mAxisMinimum, mXAxis.mAxisMaximum, false) + + mXAxisRenderer.renderAxisLabels(canvas) + + if (mDrawWeb) mRenderer?.drawExtras(canvas) + + if (mYAxis.isEnabled && mYAxis.isDrawLimitLinesBehindDataEnabled) mYAxisRenderer.renderLimitLines(canvas) + + mRenderer?.drawData(canvas) + + if (valuesToHighlight()) mRenderer?.drawHighlighted(canvas, highlighted!!) + + if (mYAxis.isEnabled && !mYAxis.isDrawLimitLinesBehindDataEnabled) mYAxisRenderer.renderLimitLines(canvas) + + mYAxisRenderer.renderAxisLabels(canvas) + + mRenderer?.drawValues(canvas) + + legendRenderer.renderLegend(canvas) + + drawDescription(canvas) + + drawMarkers(canvas) + } + + val factor: Float + /** + * Returns the factor that is needed to transform values into pixels. + * + * @return + */ + get() { + val content = viewPortHandler.contentRect + return min(content.width() / 2f, content.height() / 2f) / mYAxis.mAxisRange + } + + val sliceAngle: Float + /** + * Returns the angle that each slice in the radar chart occupies. + * + * @return + */ + get() = 360f / (mData?.maxEntryCountSet?.entryCount?.toFloat() ?: 1f) + + + val isCustomLayerColorEnable: Boolean + get() { + if (mData == null) { + return false + } + return colorList.size == this.yAxis.mEntryCount + } + + var layerColorList: MutableList + get() = colorList + set(colorList) { + if (colorList.isEmpty()) { + return + } + this.colorList = colorList + } + + override fun getIndexForAngle(angle: Float): Int { + // take the current angle of the chart into consideration + + val a = Utils.getNormalizedAngle(angle - rotationAngle) + + val sliceangle = this.sliceAngle + + val max = mData?.maxEntryCountSet?.entryCount ?: return -1 + + var index = 0 + + for (i in 0.. a) { + index = i + break + } + } + + return index + } + + val yAxis: YAxis + /** + * Returns the object that represents all y-labels of the RadarChart. + * + * @return + */ + get() = mYAxis + + var webLineWidth: Float + get() = mWebLineWidth + /** + * Sets the width of the web lines that come from the center. + * + * @param width + */ + set(width) { + mWebLineWidth = Utils.convertDpToPixel(width) + } + + var webLineWidthInner: Float + get() = mInnerWebLineWidth + /** + * Sets the width of the web lines that are in between the lines coming from + * the center. + * + * @param width + */ + set(width) { + mInnerWebLineWidth = Utils.convertDpToPixel(width) + } + + /** + * If set to true, drawing the web is enabled, if set to false, drawing the + * whole web is disabled. Default: true + * + * @param enabled + */ + fun setDrawWeb(enabled: Boolean) { + mDrawWeb = enabled + } + + var skipWebLineCount: Int + /** + * Returns the modulus that is used for skipping web-lines. + * + * @return + */ + get() = mSkipWebLineCount + /** + * Sets the number of web-lines that should be skipped on chart web before the + * next one is drawn. This targets the lines that come from the center of the RadarChart. + * + * @param count if count = 1 -> 1 line is skipped in between + */ + set(count) { + mSkipWebLineCount = max(0, count) + } + + override val requiredLegendOffset: Float + get() = legendRenderer.labelPaint.textSize * 4f + + override val requiredBaseOffset: Float + get() = if (mXAxis.isEnabled && mXAxis.isDrawLabelsEnabled) mXAxis.mLabelWidth.toFloat() else Utils.convertDpToPixel(10f) + + override val radius: Float + get() { + val content = viewPortHandler.contentRect + return min(content.width() / 2f, content.height() / 2f) + } + + /** + * Returns the maximum value this chart can display on it's y-axis. + */ + override val yChartMax: Float + get() = mYAxis.mAxisMaximum + + /** + * Returns the minimum value this chart can display on it's y-axis. + */ + override val yChartMin: Float + get() = mYAxis.mAxisMinimum + + val yRange: Float + /** + * Returns the range of y-values this chart can display. + * + * @return + */ + get() = mYAxis.mAxisRange + + override val accessibilityDescription: String? + get() = "This is a Radar chart" +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/ScatterChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/ScatterChart.java deleted file mode 100644 index 231ed0288b..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/ScatterChart.java +++ /dev/null @@ -1,82 +0,0 @@ - -package com.github.mikephil.charting.charts; - -import android.content.Context; -import android.util.AttributeSet; - -import com.github.mikephil.charting.data.ScatterData; -import com.github.mikephil.charting.interfaces.dataprovider.ScatterDataProvider; -import com.github.mikephil.charting.renderer.ScatterChartRenderer; - -/** - * The ScatterChart. Draws dots, triangles, squares and custom shapes into the - * Chart-View. CIRCLE and SCQUARE offer the best performance, TRIANGLE has the - * worst performance. - * - * @author Philipp Jahoda - */ -public class ScatterChart extends BarLineChartBase implements ScatterDataProvider { - - public ScatterChart(Context context) { - super(context); - } - - public ScatterChart(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public ScatterChart(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - - - @Override - protected void init() { - super.init(); - - mRenderer = new ScatterChartRenderer(this, mAnimator, mViewPortHandler); - - getXAxis().setSpaceMin(0.5f); - getXAxis().setSpaceMax(0.5f); - } - - @Override - public ScatterData getScatterData() { - return mData; - } - - /** - * Predefined ScatterShapes that allow the specification of a shape a ScatterDataSet should be drawn with. - * If a ScatterShape is specified for a ScatterDataSet, the required renderer is set. - */ - public enum ScatterShape { - - SQUARE("SQUARE"), - CIRCLE("CIRCLE"), - TRIANGLE("TRIANGLE"), - CROSS("CROSS"), - X("X"), - CHEVRON_UP("CHEVRON_UP"), - CHEVRON_DOWN("CHEVRON_DOWN"); - - private final String shapeIdentifier; - - ScatterShape(final String shapeIdentifier) { - this.shapeIdentifier = shapeIdentifier; - } - - @Override - public String toString() { - return shapeIdentifier; - } - - public static ScatterShape[] getAllDefaultShapes() { - return new ScatterShape[]{SQUARE, CIRCLE, TRIANGLE, CROSS, X, CHEVRON_UP, CHEVRON_DOWN}; - } - } - - @Override - public String getAccessibilityDescription() { - return "This is scatter chart"; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/charts/ScatterChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/ScatterChart.kt new file mode 100644 index 0000000000..29f74b9673 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/charts/ScatterChart.kt @@ -0,0 +1,73 @@ +package com.github.mikephil.charting.charts + +import android.content.Context +import android.util.AttributeSet +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.ScatterData +import com.github.mikephil.charting.interfaces.dataprovider.ScatterDataProvider +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.renderer.ScatterChartRenderer + +/** + * The ScatterChart. Draws dots, triangles, squares and custom shapes into the + * Chart-View. CIRCLE and SCQUARE offer the best performance, TRIANGLE has the + * worst performance. + * + * @author Philipp Jahoda + */ +class ScatterChart : BarLineChartBase, ScatterDataProvider { + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) + + + init { + mRenderer = ScatterChartRenderer(this, mAnimator, viewPortHandler) + + xAxis.spaceMin = 0.5f + xAxis.spaceMax = 0.5f + } + + override var scatterData: ScatterData? + get() = mData + set(value) { + mData = scatterData + } + + /** + * Predefined ScatterShapes that allow the specification of a shape a ScatterDataSet should be drawn with. + * If a ScatterShape is specified for a ScatterDataSet, the required renderer is set. + */ + enum class ScatterShape(private val shapeIdentifier: String) { + SQUARE("SQUARE"), + CIRCLE("CIRCLE"), + TRIANGLE("TRIANGLE"), + CROSS("CROSS"), + X("X"), + CHEVRON_UP("CHEVRON_UP"), + CHEVRON_DOWN("CHEVRON_DOWN"); + + override fun toString(): String { + return shapeIdentifier + } + + companion object { + @JvmStatic + val allDefaultShapes: Array + get() = arrayOf( + SQUARE, + CIRCLE, + TRIANGLE, + CROSS, + X, + CHEVRON_UP, + CHEVRON_DOWN + ) + } + } + + override val accessibilityDescription: String? + get() = "This is scatter chart" +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.java deleted file mode 100644 index 677def35be..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.java +++ /dev/null @@ -1,927 +0,0 @@ - -package com.github.mikephil.charting.components; - -import android.graphics.Color; -import android.graphics.DashPathEffect; -import android.graphics.Paint; -import android.util.Log; - -import com.github.mikephil.charting.formatter.DefaultAxisValueFormatter; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.utils.Utils; - -import java.util.ArrayList; -import java.util.List; - -/** - * Base-class of all axes (previously called labels). - * - * @author Philipp Jahoda - */ -public abstract class AxisBase extends ComponentBase { - - /** - * custom formatter that is used instead of the auto-formatter if set - */ - protected IAxisValueFormatter mAxisValueFormatter; - - private int mGridColor = Color.GRAY; - - private float mGridLineWidth = 1f; - - private int mAxisLineColor = Color.GRAY; - - private float mAxisLineWidth = 1f; - - /** - * the actual array of entries - */ - public float[] mEntries = new float[]{}; - - /** - * axis label entries only used for centered labels - */ - public float[] mCenteredEntries = new float[]{}; - - /** - * the number of entries the legend contains - */ - public int mEntryCount; - - /** - * the number of decimal digits to use - */ - public int mDecimals; - - /** - * the number of label entries the axis should have, default 6 - */ - private int mLabelCount = 6; - - /** - * the minimum interval between axis values - */ - protected float mGranularity = 1.0f; - - /** - * When true, axis labels are controlled by the `granularity` property. - * When false, axis values could possibly be repeated. - * This could happen if two adjacent axis values are rounded to same value. - * If using granularity this could be avoided by having fewer axis values visible. - */ - protected boolean mGranularityEnabled = false; - - /** - * if true, the set number of y-labels will be forced - */ - protected boolean mForceLabels = false; - - /** - * flag indicating if the grid lines for this axis should be drawn - */ - protected boolean mDrawGridLines = true; - - /** - * flag that indicates if the line alongside the axis is drawn or not - */ - protected boolean mDrawAxisLine = true; - - /** - * flag that indicates of the labels of this axis should be drawn or not - */ - protected boolean mDrawLabels = true; - - protected boolean mCenterAxisLabels = false; - - /** - * the path effect of the axis line that makes dashed lines possible - */ - private DashPathEffect mAxisLineDashPathEffect = null; - - /** - * the path effect of the grid lines that makes dashed lines possible - */ - private DashPathEffect mGridDashPathEffect = null; - - /** - * array of limit lines that can be set for the axis - */ - protected List mLimitLines; - - /** - * array of limit ranges that can be set for the axis - */ - protected List mLimitRanges; - - /** - * flag indicating the limit lines layer depth - */ - protected boolean mDrawLimitLineBehindData = false; - - /** - * flag indicating the grid lines layer depth - */ - protected boolean mDrawGridLinesBehindData = true; - - /** - * Extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum` - */ - protected float mSpaceMin = 0.f; - - /** - * Extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum` - */ - protected float mSpaceMax = 0.f; - - /** - * flag indicating that the axis-min value has been customized - */ - protected boolean mCustomAxisMin = false; - - /** - * flag indicating that the axis-max value has been customized - */ - protected boolean mCustomAxisMax = false; - - /** - * don't touch this direclty, use setter - */ - public float mAxisMaximum = 0f; - - /** - * don't touch this directly, use setter - */ - public float mAxisMinimum = 0f; - - /** - * the total range of values this axis covers - */ - public float mAxisRange = 0f; - - private int mAxisMinLabels = 2; - private int mAxisMaxLabels = 25; - - /** - * The minumum number of labels on the axis - */ - public int getAxisMinLabels() { - return mAxisMinLabels; - } - - /** - * The minumum number of labels on the axis - */ - public void setAxisMinLabels(int labels) { - if (labels > 0) - mAxisMinLabels = labels; - } - - /** - * The maximum number of labels on the axis - */ - public int getAxisMaxLabels() { - return mAxisMaxLabels; - } - - /** - * The maximum number of labels on the axis - */ - public void setAxisMaxLabels(int labels) { - if (labels > 0) - mAxisMaxLabels = labels; - } - - /** - * if true, then labels and lines are displayed using specificPositions instead of computed ones - */ - private boolean showSpecificPositions = false; - - /** - * specify to which values labels and lines must be displayed. has no effect if not used showSpecificPositions set to true - */ - private float[] specificPositions = new float[]{}; - - /** - * default constructor - */ - public AxisBase() { - this.mTextSize = Utils.convertDpToPixel(10f); - this.mXOffset = Utils.convertDpToPixel(5f); - this.mYOffset = Utils.convertDpToPixel(5f); - this.mLimitLines = new ArrayList(); - this.mLimitRanges = new ArrayList(); - } - - /** - * Set this to true to enable drawing the grid lines for this axis. - * - * @param enabled - */ - public void setDrawGridLines(boolean enabled) { - mDrawGridLines = enabled; - } - - /** - * Returns true if drawing grid lines is enabled for this axis. - * - * @return - */ - public boolean isDrawGridLinesEnabled() { - return mDrawGridLines; - } - - /** - * Set this to true if the line alongside the axis should be drawn or not. - * - * @param enabled - */ - public void setDrawAxisLine(boolean enabled) { - mDrawAxisLine = enabled; - } - - /** - * Returns true if the line alongside the axis should be drawn. - * - * @return - */ - public boolean isDrawAxisLineEnabled() { - return mDrawAxisLine; - } - - /** - * Centers the axis labels instead of drawing them at their original position. - * This is useful especially for grouped BarChart. - * - * @param enabled - */ - public void setCenterAxisLabels(boolean enabled) { - mCenterAxisLabels = enabled; - } - - public boolean isCenterAxisLabelsEnabled() { - return mCenterAxisLabels && mEntryCount > 0; - } - - /** - * Sets the color of the grid lines for this axis (the horizontal lines - * coming from each label). - * - * @param color - */ - public void setGridColor(int color) { - mGridColor = color; - } - - /** - * Returns the color of the grid lines for this axis (the horizontal lines - * coming from each label). - * - * @return - */ - public int getGridColor() { - return mGridColor; - } - - /** - * Sets the width of the border surrounding the chart in dp. - * - * @param width - */ - public void setAxisLineWidth(float width) { - mAxisLineWidth = Utils.convertDpToPixel(width); - } - - /** - * Returns the width of the axis line (line alongside the axis). - * - * @return - */ - public float getAxisLineWidth() { - return mAxisLineWidth; - } - - /** - * Sets the width of the grid lines that are drawn away from each axis - * label. - * - * @param width - */ - public void setGridLineWidth(float width) { - mGridLineWidth = Utils.convertDpToPixel(width); - } - - /** - * Returns the width of the grid lines that are drawn away from each axis - * label. - * - * @return - */ - public float getGridLineWidth() { - return mGridLineWidth; - } - - /** - * Sets the color of the border surrounding the chart. - * - * @param color - */ - public void setAxisLineColor(int color) { - mAxisLineColor = color; - } - - /** - * Returns the color of the axis line (line alongside the axis). - * - * @return - */ - public int getAxisLineColor() { - return mAxisLineColor; - } - - /** - * Set this to true to enable drawing the labels of this axis (this will not - * affect drawing the grid lines or axis lines). - * - * @param enabled - */ - public void setDrawLabels(boolean enabled) { - mDrawLabels = enabled; - } - - /** - * Returns true if drawing the labels is enabled for this axis. - * - * @return - */ - public boolean isDrawLabelsEnabled() { - return mDrawLabels; - } - - /** - * Sets the number of label entries for the y-axis max = 25, min = 2, default: 6, be aware - * that this number is not fixed. - * - * @param count the number of y-axis labels that should be displayed - */ - public void setLabelCount(int count) { - - if (count > getAxisMaxLabels()) - count = getAxisMaxLabels(); - if (count < getAxisMinLabels()) - count = getAxisMinLabels(); - - mLabelCount = count; - mForceLabels = false; - } - - /** - * sets the number of label entries for the y-axis max = 25, min = 2, default: 6, be aware - * that this number is not - * fixed (if force == false) and can only be approximated. - * - * @param count the number of y-axis labels that should be displayed - * @param force if enabled, the set label count will be forced, meaning that the exact - * specified count of labels will - * be drawn and evenly distributed alongside the axis - this might cause labels - * to have uneven values - */ - public void setLabelCount(int count, boolean force) { - - setLabelCount(count); - mForceLabels = force; - } - - /** - * Returns true if focing the y-label count is enabled. Default: false - * - * @return - */ - public boolean isForceLabelsEnabled() { - return mForceLabels; - } - - /** - * Returns the number of label entries the y-axis should have - * - * @return - */ - public int getLabelCount() { - return mLabelCount; - } - - /** - * @return true if granularity is enabled - */ - public boolean isGranularityEnabled() { - return mGranularityEnabled; - } - - /** - * Enabled/disable granularity control on axis value intervals. If enabled, the axis - * interval is not allowed to go below a certain granularity. Default: false - * - * @param enabled - */ - public void setGranularityEnabled(boolean enabled) { - mGranularityEnabled = enabled; - } - - /** - * @return the minimum interval between axis values - */ - public float getGranularity() { - return mGranularity; - } - - /** - * Set a minimum interval for the axis when zooming in. The axis is not allowed to go below - * that limit. This can be used to avoid label duplicating when zooming in. - * - * @param granularity - */ - public void setGranularity(float granularity) { - mGranularity = granularity; - // set this to true if it was disabled, as it makes no sense to call this method with granularity disabled - mGranularityEnabled = true; - } - - /** - * Adds a new LimitLine to this axis. - * - * @param l - */ - public void addLimitLine(LimitLine l) { - mLimitLines.add(l); - - if (mLimitLines.size() > 6) { - Log.e("MPAndroiChart", - "Warning! You have more than 6 LimitLines on your axis, do you really want that?"); - } - } - - /** - * Adds a new LimitLine to this axis. - */ - public void addLimitRange(LimitRange l) { - mLimitRanges.add(l); - - if (mLimitRanges.size() > 6) { - Log.e("MPAndroiChart", - "Warning! You have more than 6 LimitLines on your axis, do you really want that?"); - } - } - - /** - * Removes the specified LimitLine from the axis. - * - * @param l - */ - public void removeLimitLine(LimitLine l) { - mLimitLines.remove(l); - } - - /** - * Removes all LimitLines from the axis. - */ - public void removeAllLimitLines() { - mLimitLines.clear(); - } - - /** - * Removes the specified LimitRange from the axis. - * - * @param l - */ - public void removeLimitRange(LimitRange l) { - mLimitRanges.remove(l); - } - - /** - * Removes all LimitLines from the axis. - */ - public void removeAllLimitRanges() { - mLimitRanges.clear(); - } - - /** - * Returns the LimitLines of this axis. - * - * @return - */ - public List getLimitLines() { - return mLimitLines; - } - - /** - * Returns the LimitRanges of this axis. - * - * @return - */ - public List getLimitRanges() { - return mLimitRanges; - } - - /** - * If this is set to true, the LimitLines are drawn behind the actual data, - * otherwise on top. Default: false - * - * @param enabled - */ - public void setDrawLimitLinesBehindData(boolean enabled) { - mDrawLimitLineBehindData = enabled; - } - - public boolean isDrawLimitLinesBehindDataEnabled() { - return mDrawLimitLineBehindData; - } - - /** - * If this is set to false, the grid lines are draw on top of the actual data, - * otherwise behind. Default: true - * - * @param enabled - */ - public void setDrawGridLinesBehindData(boolean enabled) { mDrawGridLinesBehindData = enabled; } - - public boolean isDrawGridLinesBehindDataEnabled() { - return mDrawGridLinesBehindData; - } - - /** - * Returns the longest formatted label (in terms of characters), this axis - * contains. - * - * @return - */ - public String getLongestLabel() { - - String longest = ""; - - for (int i = 0; i < mEntries.length; i++) { - String text = getFormattedLabel(i); - - if (text != null && longest.length() < text.length()) - longest = text; - } - - return longest; - } - - /** - * Returns the longest formatted label (in terms of px), this axis - * contains. - * If paint is null, then returns the longest formatted label (in terms of characters), this axis contains. - * - * @return - */ - public String getLongestLabel(Paint p) { - if (p == null) { - return getLongestLabel(); - } - String longest = ""; - float max = 0f; - - for (int i = 0; i < mEntries.length; i++) { - String text = getFormattedLabel(i); - if (text != null) { - float width = p.measureText(text); - if (max < width) { - longest = text; - } - } - } - - return longest; - } - - public String getFormattedLabel(int index) { - - if (index < 0 || index >= mEntries.length) - return ""; - else - return getValueFormatter().getFormattedValue(mEntries[index], this); - } - - /** - * Sets the formatter to be used for formatting the axis labels. If no formatter is set, the - * chart will - * automatically determine a reasonable formatting (concerning decimals) for all the values - * that are drawn inside - * the chart. Use chart.getDefaultValueFormatter() to use the formatter calculated by the chart. - * - * @param f - */ - public void setValueFormatter(IAxisValueFormatter f) { - - if (f == null) - mAxisValueFormatter = new DefaultAxisValueFormatter(mDecimals); - else - mAxisValueFormatter = f; - } - - /** - * Returns the formatter used for formatting the axis labels. - * - * @return - */ - public IAxisValueFormatter getValueFormatter() { - - if (mAxisValueFormatter == null || - (mAxisValueFormatter instanceof DefaultAxisValueFormatter && - ((DefaultAxisValueFormatter)mAxisValueFormatter).getDecimalDigits() != mDecimals)) - mAxisValueFormatter = new DefaultAxisValueFormatter(mDecimals); - - return mAxisValueFormatter; - } - - /** - * Enables the grid line to be drawn in dashed mode, e.g. like this - * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. - * Keep in mind that hardware acceleration boosts performance. - * - * @param lineLength the length of the line pieces - * @param spaceLength the length of space in between the pieces - * @param phase offset, in degrees (normally, use 0) - */ - public void enableGridDashedLine(float lineLength, float spaceLength, float phase) { - mGridDashPathEffect = new DashPathEffect(new float[]{ - lineLength, spaceLength - }, phase); - } - - /** - * Enables the grid line to be drawn in dashed mode, e.g. like this - * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. - * Keep in mind that hardware acceleration boosts performance. - * - * @param effect the DashPathEffect - */ - public void setGridDashedLine(DashPathEffect effect) { - mGridDashPathEffect = effect; - } - - /** - * Disables the grid line to be drawn in dashed mode. - */ - public void disableGridDashedLine() { - mGridDashPathEffect = null; - } - - /** - * Returns true if the grid dashed-line effect is enabled, false if not. - * - * @return - */ - public boolean isGridDashedLineEnabled() { - return mGridDashPathEffect != null; - } - - /** - * returns the DashPathEffect that is set for grid line - * - * @return - */ - public DashPathEffect getGridDashPathEffect() { - return mGridDashPathEffect; - } - - - /** - * Enables the axis line to be drawn in dashed mode, e.g. like this - * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. - * Keep in mind that hardware acceleration boosts performance. - * - * @param lineLength the length of the line pieces - * @param spaceLength the length of space in between the pieces - * @param phase offset, in degrees (normally, use 0) - */ - public void enableAxisLineDashedLine(float lineLength, float spaceLength, float phase) { - mAxisLineDashPathEffect = new DashPathEffect(new float[]{ - lineLength, spaceLength - }, phase); - } - - /** - * Enables the axis line to be drawn in dashed mode, e.g. like this - * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. - * Keep in mind that hardware acceleration boosts performance. - * - * @param effect the DashPathEffect - */ - public void setAxisLineDashedLine(DashPathEffect effect) { - mAxisLineDashPathEffect = effect; - } - - /** - * Disables the axis line to be drawn in dashed mode. - */ - public void disableAxisLineDashedLine() { - mAxisLineDashPathEffect = null; - } - - /** - * Returns true if the axis dashed-line effect is enabled, false if not. - * - * @return - */ - public boolean isAxisLineDashedLineEnabled() { - return mAxisLineDashPathEffect != null; - } - - /** - * returns the DashPathEffect that is set for axis line - * - * @return - */ - public DashPathEffect getAxisLineDashPathEffect() { - return mAxisLineDashPathEffect; - } - - /** - * ###### BELOW CODE RELATED TO CUSTOM AXIS VALUES ###### - */ - - public float getAxisMaximum() { - return mAxisMaximum; - } - - public float getAxisMinimum() { - return mAxisMinimum; - } - - /** - * By calling this method, any custom maximum value that has been previously set is reseted, - * and the calculation is - * done automatically. - */ - public void resetAxisMaximum() { - mCustomAxisMax = false; - } - - /** - * Returns true if the axis max value has been customized (and is not calculated automatically) - * - * @return - */ - public boolean isAxisMaxCustom() { - return mCustomAxisMax; - } - - /** - * By calling this method, any custom minimum value that has been previously set is reseted, - * and the calculation is - * done automatically. - */ - public void resetAxisMinimum() { - mCustomAxisMin = false; - } - - /** - * Returns true if the axis min value has been customized (and is not calculated automatically) - * - * @return - */ - public boolean isAxisMinCustom() { - return mCustomAxisMin; - } - - /** - * Set a custom minimum value for this axis. If set, this value will not be calculated - * automatically depending on - * the provided data. Use resetAxisMinValue() to undo this. Do not forget to call - * setStartAtZero(false) if you use - * this method. Otherwise, the axis-minimum value will still be forced to 0. - * - * @param min - */ - public void setAxisMinimum(float min) { - mCustomAxisMin = true; - mAxisMinimum = min; - this.mAxisRange = Math.abs(mAxisMaximum - min); - } - - /** - * Use setAxisMinimum(...) instead. - * - * @param min - */ - @Deprecated - public void setAxisMinValue(float min) { - setAxisMinimum(min); - } - - /** - * Set a custom maximum value for this axis. If set, this value will not be calculated - * automatically depending on - * the provided data. Use resetAxisMaxValue() to undo this. - * - * @param max - */ - public void setAxisMaximum(float max) { - mCustomAxisMax = true; - mAxisMaximum = max; - this.mAxisRange = Math.abs(max - mAxisMinimum); - } - - /** - * Use setAxisMaximum(...) instead. - * - * @param max - */ - @Deprecated - public void setAxisMaxValue(float max) { - setAxisMaximum(max); - } - - /** - * Calculates the minimum / maximum and range values of the axis with the given - * minimum and maximum values from the chart data. - * - * @param dataMin the min value according to chart data - * @param dataMax the max value according to chart data - */ - public void calculate(float dataMin, float dataMax) { - - // if custom, use value as is, else use data value - float min = mCustomAxisMin ? mAxisMinimum : (dataMin - mSpaceMin); - float max = mCustomAxisMax ? mAxisMaximum : (dataMax + mSpaceMax); - - // temporary range (before calculations) - float range = Math.abs(max - min); - - // in case all values are equal - if (range == 0f) { - max = max + 1f; - min = min - 1f; - } - - this.mAxisMinimum = min; - this.mAxisMaximum = max; - - // actual range - this.mAxisRange = Math.abs(max - min); - } - - /** - * Gets extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum` - */ - public float getSpaceMin() - { - return mSpaceMin; - } - - /** - * Sets extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum` - */ - public void setSpaceMin(float mSpaceMin) - { - this.mSpaceMin = mSpaceMin; - } - - /** - * Gets extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum` - */ - public float getSpaceMax() - { - return mSpaceMax; - } - - /** - * Sets extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum` - */ - public void setSpaceMax(float mSpaceMax) - { - this.mSpaceMax = mSpaceMax; - } - - /** - * if set to true, labels and lines will be displayed at the specific positions passed in via setSpecificPositions - */ - public void setShowSpecificPositions(boolean showSpecificPositions) - { - this.showSpecificPositions = showSpecificPositions; - } - - public boolean isShowSpecificPositions() - { - return showSpecificPositions; - } - - public void setSpecificPositions(float[] specificPositions) - { - this.specificPositions = specificPositions; - } - - public float[] getSpecificPositions() - { - return specificPositions; - } - - /** - * Sets the text color to use for the labels. Make sure to use - * getResources().getColor(...) when using a color from the resources. - */ - public void setTextColor(int color) { - super.setTextColor(color); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.kt new file mode 100644 index 0000000000..8d13e64d32 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/AxisBase.kt @@ -0,0 +1,787 @@ +package com.github.mikephil.charting.components + +import android.graphics.Color +import android.graphics.DashPathEffect +import android.graphics.Paint +import android.util.Log +import com.github.mikephil.charting.formatter.DefaultAxisValueFormatter +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.utils.Utils +import kotlin.math.abs + +/** + * Base-class of all axes (previously called labels). + * + * @author Philipp Jahoda + */ +abstract class AxisBase : ComponentBase() { + /** + * custom formatter that is used instead of the auto-formatter if set + */ + protected var mAxisValueFormatter: IAxisValueFormatter = DefaultAxisValueFormatter(1) + + /** + * Returns the color of the grid lines for this axis (the horizontal lines + * coming from each label). + * + * @return + */ + /** + * Sets the color of the grid lines for this axis (the horizontal lines + * coming from each label). + * + * @param color + */ + var gridColor: Int = Color.GRAY + + private var mGridLineWidth = 1f + + /** + * Returns the color of the axis line (line alongside the axis). + * + * @return + */ + /** + * Sets the color of the border surrounding the chart. + * + * @param color + */ + var axisLineColor: Int = Color.GRAY + + private var mAxisLineWidth = 1f + + /** + * the actual array of entries + */ + var mEntries: FloatArray = floatArrayOf() + + /** + * axis label entries only used for centered labels + */ + var mCenteredEntries: FloatArray = floatArrayOf() + + /** + * the number of entries the legend contains + */ + var mEntryCount: Int = 0 + + /** + * the number of decimal digits to use + */ + var mDecimals: Int = 0 + + /** + * the number of label entries the axis should have, default 6 + */ + private var mLabelCount = 6 + + /** + * the minimum interval between axis values + */ + protected var mGranularity: Float = 1.0f + + /** + * @return true if granularity is enabled + */ + /** + * Enabled/disable granularity control on axis value intervals. If enabled, the axis + * interval is not allowed to go below a certain granularity. Default: false + * + * @param enabled + */ + /** + * When true, axis labels are controlled by the `granularity` property. + * When false, axis values could possibly be repeated. + * This could happen if two adjacent axis values are rounded to same value. + * If using granularity this could be avoided by having fewer axis values visible. + */ + var isGranularityEnabled: Boolean = false + + /** + * Returns true if focing the y-label count is enabled. Default: false + * + * @return + */ + /** + * if true, the set number of y-labels will be forced + */ + var isForceLabelsEnabled: Boolean = false + protected set + + /** + * Returns true if drawing grid lines is enabled for this axis. + * + * @return + */ + /** + * flag indicating if the grid lines for this axis should be drawn + */ + var isDrawGridLinesEnabled: Boolean = true + protected set + + /** + * Returns true if the line alongside the axis should be drawn. + * + * @return + */ + /** + * flag that indicates if the line alongside the axis is drawn or not + */ + var isDrawAxisLineEnabled: Boolean = true + protected set + + /** + * Returns true if drawing the labels is enabled for this axis. + * + * @return + */ + /** + * flag that indicates of the labels of this axis should be drawn or not + */ + var isDrawLabelsEnabled: Boolean = true + protected set + + protected var mCenterAxisLabels: Boolean = false + + /** + * returns the DashPathEffect that is set for axis line + * + * @return + */ + /** + * the path effect of the axis line that makes dashed lines possible + */ + var axisLineDashPathEffect: DashPathEffect? = null + private set + + /** + * returns the DashPathEffect that is set for grid line + * + * @return + */ + /** + * the path effect of the grid lines that makes dashed lines possible + */ + var gridDashPathEffect: DashPathEffect? = null + private set + + /** + * Returns the LimitLines of this axis. + * + * @return + */ + /** + * array of limit lines that can be set for the axis + */ + var limitLines: MutableList + protected set + + /** + * Returns the LimitRanges of this axis. + * + * @return + */ + /** + * array of limit ranges that can be set for the axis + */ + var limitRanges: MutableList + protected set + + /** + * flag indicating the limit lines layer depth + */ + var isDrawLimitLinesBehindDataEnabled: Boolean = false + protected set + + /** + * flag indicating the grid lines layer depth + */ + var isDrawGridLinesBehindDataEnabled: Boolean = true + protected set + + /** + * Gets extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum` + */ + /** + * Sets extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum` + */ + /** + * Extra spacing for `axisMinimum` to be added to automatically calculated `axisMinimum` + */ + var spaceMin: Float = 0f + + /** + * Gets extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum` + */ + /** + * Sets extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum` + */ + /** + * Extra spacing for `axisMaximum` to be added to automatically calculated `axisMaximum` + */ + var spaceMax: Float = 0f + + /** + * Returns true if the axis min value has been customized (and is not calculated automatically) + * + * @return + */ + /** + * flag indicating that the axis-min value has been customized + */ + var isAxisMinCustom: Boolean = false + protected set + + /** + * Returns true if the axis max value has been customized (and is not calculated automatically) + * + * @return + */ + /** + * flag indicating that the axis-max value has been customized + */ + var isAxisMaxCustom: Boolean = false + protected set + + /** + * don't touch this direclty, use setter + */ + var mAxisMaximum: Float = 0f + + /** + * don't touch this directly, use setter + */ + var mAxisMinimum: Float = 0f + + /** + * the total range of values this axis covers + */ + var mAxisRange: Float = 0f + + private var mAxisMinLabels = 2 + private var mAxisMaxLabels = 25 + + var axisMinLabels: Int + /** + * The minumum number of labels on the axis + */ + get() = mAxisMinLabels + /** + * The minumum number of labels on the axis + */ + set(labels) { + if (labels > 0) mAxisMinLabels = labels + } + + var axisMaxLabels: Int + /** + * The maximum number of labels on the axis + */ + get() = mAxisMaxLabels + /** + * The maximum number of labels on the axis + */ + set(labels) { + if (labels > 0) mAxisMaxLabels = labels + } + + /** + * if set to true, labels and lines will be displayed at the specific positions passed in via setSpecificPositions + */ + /** + * if true, then labels and lines are displayed using specificPositions instead of computed ones + */ + var isShowSpecificPositions: Boolean = false + + /** + * specify to which values labels and lines must be displayed. has no effect if not used showSpecificPositions set to true + */ + var specificPositions: FloatArray = floatArrayOf() + + /** + * default constructor + */ + init { + this.mTextSize = Utils.convertDpToPixel(10f) + this.mXOffset = Utils.convertDpToPixel(5f) + this.mYOffset = Utils.convertDpToPixel(5f) + this.limitLines = ArrayList() + this.limitRanges = ArrayList() + } + + /** + * Set this to true to enable drawing the grid lines for this axis. + * + * @param enabled + */ + fun setDrawGridLines(enabled: Boolean) { + this.isDrawGridLinesEnabled = enabled + } + + /** + * Set this to true if the line alongside the axis should be drawn or not. + * + * @param enabled + */ + fun setDrawAxisLine(enabled: Boolean) { + this.isDrawAxisLineEnabled = enabled + } + + /** + * Centers the axis labels instead of drawing them at their original position. + * This is useful especially for grouped BarChart. + * + * @param enabled + */ + fun setCenterAxisLabels(enabled: Boolean) { + mCenterAxisLabels = enabled + } + + val isCenterAxisLabelsEnabled: Boolean + get() = mCenterAxisLabels && mEntryCount > 0 + + var axisLineWidth: Float + /** + * Returns the width of the axis line (line alongside the axis). + * + * @return + */ + get() = mAxisLineWidth + /** + * Sets the width of the border surrounding the chart in dp. + * + * @param width + */ + set(width) { + mAxisLineWidth = Utils.convertDpToPixel(width) + } + + var gridLineWidth: Float + /** + * Returns the width of the grid lines that are drawn away from each axis + * label. + * + * @return + */ + get() = mGridLineWidth + /** + * Sets the width of the grid lines that are drawn away from each axis + * label. + * + * @param width + */ + set(width) { + mGridLineWidth = Utils.convertDpToPixel(width) + } + + /** + * Set this to true to enable drawing the labels of this axis (this will not + * affect drawing the grid lines or axis lines). + * + * @param enabled + */ + fun setDrawLabels(enabled: Boolean) { + this.isDrawLabelsEnabled = enabled + } + + /** + * sets the number of label entries for the y-axis max = 25, min = 2, default: 6, be aware + * that this number is not + * fixed (if force == false) and can only be approximated. + * + * @param count the number of y-axis labels that should be displayed + * @param force if enabled, the set label count will be forced, meaning that the exact + * specified count of labels will + * be drawn and evenly distributed alongside the axis - this might cause labels + * to have uneven values + */ + fun setLabelCount(count: Int, force: Boolean) { + this.labelCount = count + this.isForceLabelsEnabled = force + } + + var labelCount: Int + /** + * Returns the number of label entries the y-axis should have + * + * @return + */ + get() = mLabelCount + /** + * Sets the number of label entries for the y-axis max = 25, min = 2, default: 6, be aware + * that this number is not fixed. + * + * @param count the number of y-axis labels that should be displayed + */ + set(count) { + var count = count + if (count > this.axisMaxLabels) count = this.axisMaxLabels + if (count < this.axisMinLabels) count = this.axisMinLabels + + mLabelCount = count + this.isForceLabelsEnabled = false + } + + var granularity: Float + /** + * @return the minimum interval between axis values + */ + get() = mGranularity + /** + * Set a minimum interval for the axis when zooming in. The axis is not allowed to go below + * that limit. This can be used to avoid label duplicating when zooming in. + * + * @param granularity + */ + set(granularity) { + mGranularity = granularity + // set this to true if it was disabled, as it makes no sense to call this method with granularity disabled + this.isGranularityEnabled = true + } + + /** + * Adds a new LimitLine to this axis. + * + * @param l + */ + fun addLimitLine(l: LimitLine) { + limitLines.add(l) + + if (limitLines.size > 6) { + Log.e( + "MPAndroiChart", + "Warning! You have more than 6 LimitLines on your axis, do you really want that?" + ) + } + } + + /** + * Adds a new LimitLine to this axis. + */ + fun addLimitRange(l: LimitRange) { + limitRanges.add(l) + + if (limitRanges.size > 6) { + Log.e( + "MPAndroiChart", + "Warning! You have more than 6 LimitLines on your axis, do you really want that?" + ) + } + } + + /** + * Removes the specified LimitLine from the axis. + * + * @param l + */ + fun removeLimitLine(l: LimitLine?) { + limitLines.remove(l) + } + + /** + * Removes all LimitLines from the axis. + */ + fun removeAllLimitLines() { + limitLines.clear() + } + + /** + * Removes the specified LimitRange from the axis. + * + * @param l + */ + fun removeLimitRange(l: LimitRange?) { + limitRanges.remove(l) + } + + /** + * Removes all LimitLines from the axis. + */ + fun removeAllLimitRanges() { + limitRanges.clear() + } + + /** + * If this is set to true, the LimitLines are drawn behind the actual data, + * otherwise on top. Default: false + * + * @param enabled + */ + fun setDrawLimitLinesBehindData(enabled: Boolean) { + this.isDrawLimitLinesBehindDataEnabled = enabled + } + + /** + * If this is set to false, the grid lines are draw on top of the actual data, + * otherwise behind. Default: true + * + * @param enabled + */ + fun setDrawGridLinesBehindData(enabled: Boolean) { + this.isDrawGridLinesBehindDataEnabled = enabled + } + + val longestLabel: String + /** + * Returns the longest formatted label (in terms of characters), this axis + * contains. + * + * @return + */ + get() { + var longest = "" + + for (i in mEntries.indices) { + val text = getFormattedLabel(i) + + if (longest.length < text.length) longest = text + } + + return longest + } + + /** + * Returns the longest formatted label (in terms of px), this axis + * contains. + * If paint is null, then returns the longest formatted label (in terms of characters), this axis contains. + * + * @return + */ + fun getLongestLabel(p: Paint?): String { + if (p == null) { + return this.longestLabel + } + var longest = "" + val max = 0f + + for (i in mEntries.indices) { + val text = getFormattedLabel(i) + val width = p.measureText(text) + if (max < width) { + longest = text + } + } + + return longest + } + + fun getFormattedLabel(index: Int): String { + return if (index < 0 || index >= mEntries.size) "" + else this.valueFormatter.getFormattedValue(mEntries[index], this) + } + + var valueFormatter: IAxisValueFormatter + /** + * Returns the formatter used for formatting the axis labels. + * + * @return + */ + get() { + if (mAxisValueFormatter is DefaultAxisValueFormatter && + (mAxisValueFormatter as DefaultAxisValueFormatter).decimalDigits != mDecimals + ) mAxisValueFormatter = DefaultAxisValueFormatter(mDecimals) + + return mAxisValueFormatter + } + /** + * Sets the formatter to be used for formatting the axis labels. If no formatter is set, the + * chart will + * automatically determine a reasonable formatting (concerning decimals) for all the values + * that are drawn inside + * the chart. Use chart.getDefaultValueFormatter() to use the formatter calculated by the chart. + * + * @param f + */ + set(f) { + mAxisValueFormatter = f + } + + /** + * Enables the grid line to be drawn in dashed mode, e.g. like this + * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. + * Keep in mind that hardware acceleration boosts performance. + * + * @param lineLength the length of the line pieces + * @param spaceLength the length of space in between the pieces + * @param phase offset, in degrees (normally, use 0) + */ + fun enableGridDashedLine(lineLength: Float, spaceLength: Float, phase: Float) { + this.gridDashPathEffect = DashPathEffect( + floatArrayOf( + lineLength, spaceLength + ), phase + ) + } + + /** + * Enables the grid line to be drawn in dashed mode, e.g. like this + * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. + * Keep in mind that hardware acceleration boosts performance. + * + * @param effect the DashPathEffect + */ + fun setGridDashedLine(effect: DashPathEffect?) { + this.gridDashPathEffect = effect + } + + /** + * Disables the grid line to be drawn in dashed mode. + */ + fun disableGridDashedLine() { + this.gridDashPathEffect = null + } + + val isGridDashedLineEnabled: Boolean + /** + * Returns true if the grid dashed-line effect is enabled, false if not. + * + * @return + */ + get() = this.gridDashPathEffect != null + + + /** + * Enables the axis line to be drawn in dashed mode, e.g. like this + * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. + * Keep in mind that hardware acceleration boosts performance. + * + * @param lineLength the length of the line pieces + * @param spaceLength the length of space in between the pieces + * @param phase offset, in degrees (normally, use 0) + */ + fun enableAxisLineDashedLine(lineLength: Float, spaceLength: Float, phase: Float) { + this.axisLineDashPathEffect = DashPathEffect( + floatArrayOf( + lineLength, spaceLength + ), phase + ) + } + + /** + * Enables the axis line to be drawn in dashed mode, e.g. like this + * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. + * Keep in mind that hardware acceleration boosts performance. + * + * @param effect the DashPathEffect + */ + fun setAxisLineDashedLine(effect: DashPathEffect?) { + this.axisLineDashPathEffect = effect + } + + /** + * Disables the axis line to be drawn in dashed mode. + */ + fun disableAxisLineDashedLine() { + this.axisLineDashPathEffect = null + } + + val isAxisLineDashedLineEnabled: Boolean + /** + * Returns true if the axis dashed-line effect is enabled, false if not. + * + * @return + */ + get() = this.axisLineDashPathEffect != null + + var axisMaximum: Float + /** + * ###### BELOW CODE RELATED TO CUSTOM AXIS VALUES ###### + */ + get() = mAxisMaximum + /** + * Set a custom maximum value for this axis. If set, this value will not be calculated + * automatically depending on + * the provided data. Use resetAxisMaxValue() to undo this. + * + * @param max + */ + set(max) { + this.isAxisMaxCustom = true + mAxisMaximum = max + this.mAxisRange = abs(max - mAxisMinimum) + } + + var axisMinimum: Float + get() = mAxisMinimum + /** + * Set a custom minimum value for this axis. If set, this value will not be calculated + * automatically depending on + * the provided data. Use resetAxisMinValue() to undo this. Do not forget to call + * setStartAtZero(false) if you use + * this method. Otherwise, the axis-minimum value will still be forced to 0. + * + * @param min + */ + set(min) { + this.isAxisMinCustom = true + mAxisMinimum = min + this.mAxisRange = abs(mAxisMaximum - min) + } + + /** + * By calling this method, any custom maximum value that has been previously set is reseted, + * and the calculation is + * done automatically. + */ + fun resetAxisMaximum() { + this.isAxisMaxCustom = false + } + + /** + * By calling this method, any custom minimum value that has been previously set is reseted, + * and the calculation is + * done automatically. + */ + fun resetAxisMinimum() { + this.isAxisMinCustom = false + } + + /** + * Use setAxisMinimum(...) instead. + * + * @param min + */ + @Deprecated("") + fun setAxisMinValue(min: Float) { + this.axisMinimum = min + } + + /** + * Use setAxisMaximum(...) instead. + * + * @param max + */ + @Deprecated("") + fun setAxisMaxValue(max: Float) { + this.axisMaximum = max + } + + /** + * Calculates the minimum / maximum and range values of the axis with the given + * minimum and maximum values from the chart data. + * + * @param dataMin the min value according to chart data + * @param dataMax the max value according to chart data + */ + open fun calculate(dataMin: Float, dataMax: Float) { + // if custom, use value as is, else use data value + + var min = if (this.isAxisMinCustom) mAxisMinimum else (dataMin - this.spaceMin) + var max = if (this.isAxisMaxCustom) mAxisMaximum else (dataMax + this.spaceMax) + + // temporary range (before calculations) + val range = abs(max - min) + + // in case all values are equal + if (range == 0f) { + max = max + 1f + min = min - 1f + } + + this.mAxisMinimum = min + this.mAxisMaximum = max + + // actual range + this.mAxisRange = abs(max - min) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Description.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Description.kt similarity index 51% rename from MPChartLib/src/main/java/com/github/mikephil/charting/components/Description.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/components/Description.kt index 18294a3270..e565b68e83 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Description.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Description.kt @@ -1,95 +1,72 @@ -package com.github.mikephil.charting.components; +package com.github.mikephil.charting.components -import android.graphics.Paint; - -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.Utils; +import android.graphics.Paint.Align +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Utils /** * Created by Philipp Jahoda on 17/09/16. */ -public class Description extends ComponentBase { - +class Description : ComponentBase() { /** - * the text used in the description - */ - private String text = "Description Label"; - - /** - * the custom position of the description text - */ - private MPPointF mPosition; - - /** - * the alignment of the description text + * Returns the description text. + * + * @return */ - private Paint.Align mTextAlign = Paint.Align.RIGHT; - - public Description() { - super(); - - // default size - mTextSize = Utils.convertDpToPixel(8f); - } - /** * Sets the text to be shown as the description. * Never set this to null as this will cause nullpointer exception when drawing with Android Canvas. * * @param text */ - public void setText(String text) { - this.text = text; - } + /** + * the text used in the description + */ + var text: String = "Description Label" /** - * Returns the description text. + * Returns the customized position of the description, or null if none set. * * @return */ - public String getText() { - return text; - } - /** - * Sets a custom position for the description text in pixels on the screen. - * - * @param x - xcoordinate - * @param y - ycoordinate + * the custom position of the description text */ - public void setPosition(float x, float y) { - if (mPosition == null) { - mPosition = MPPointF.getInstance(x, y); - } else { - mPosition.x = x; - mPosition.y = y; - } - } + var position: MPPointF? = null + private set /** - * Returns the customized position of the description, or null if none set. + * Returns the text alignment of the description. * * @return */ - public MPPointF getPosition() { - return mPosition; - } - /** * Sets the text alignment of the description text. Default RIGHT. * * @param align */ - public void setTextAlign(Paint.Align align) { - this.mTextAlign = align; + /** + * the alignment of the description text + */ + var textAlign: Align? = Align.RIGHT + + init { + // default size + mTextSize = Utils.convertDpToPixel(8f) } /** - * Returns the text alignment of the description. + * Sets a custom position for the description text in pixels on the screen. * - * @return + * @param x - xcoordinate + * @param y - ycoordinate */ - public Paint.Align getTextAlign() { - return mTextAlign; + fun setPosition(x: Float, y: Float) { + if (this.position == null) { + this.position = MPPointF.Companion.getInstance(x, y) + } else { + position!!.x = x + position!!.y = y + } } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/IMarker.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/IMarker.java deleted file mode 100644 index 3b8ca43c81..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/IMarker.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.github.mikephil.charting.components; - -import android.graphics.Canvas; - -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.MPPointF; - -public interface IMarker { - - /** - * @return The desired (general) offset you wish the IMarker to have on the x- and y-axis. - * By returning x: -(width / 2) you will center the IMarker horizontally. - * By returning y: -(height / 2) you will center the IMarker vertically. - */ - MPPointF getOffset(); - - /** - * @return The offset for drawing at the specific `point`. This allows conditional adjusting of the Marker position. - * If you have no adjustments to make, return getOffset(). - * - * @param posX This is the X position at which the marker wants to be drawn. - * You can adjust the offset conditionally based on this argument. - * @param posY This is the X position at which the marker wants to be drawn. - * You can adjust the offset conditionally based on this argument. - */ - MPPointF getOffsetForDrawingAtPoint(float posX, float posY); - - /** - * This method enables a specified custom IMarker to update it's content every time the IMarker is redrawn. - * - * @param e The Entry the IMarker belongs to. This can also be any subclass of Entry, like BarEntry or - * CandleEntry, simply cast it at runtime. - * @param highlight The highlight object contains information about the highlighted value such as it's dataset-index, the - * selected range or stack-index (only stacked bar entries). - */ - void refreshContent(Entry e, Highlight highlight); - - /** - * Draws the IMarker on the given position on the screen with the given Canvas object. - * - * @param canvas - * @param posX - * @param posY - */ - void draw(Canvas canvas, float posX, float posY); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/IMarker.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/IMarker.kt new file mode 100644 index 0000000000..3780cd2844 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/IMarker.kt @@ -0,0 +1,45 @@ +package com.github.mikephil.charting.components + +import android.graphics.Canvas +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF + +interface IMarker { + /** + * @return The desired (general) offset you wish the IMarker to have on the x- and y-axis. + * By returning x: -(width / 2) you will center the IMarker horizontally. + * By returning y: -(height / 2) you will center the IMarker vertically. + */ + val offset: MPPointF + + /** + * @return The offset for drawing at the specific `point`. This allows conditional adjusting of the Marker position. + * If you have no adjustments to make, return getOffset(). + * + * @param posX This is the X position at which the marker wants to be drawn. + * You can adjust the offset conditionally based on this argument. + * @param posY This is the X position at which the marker wants to be drawn. + * You can adjust the offset conditionally based on this argument. + */ + fun getOffsetForDrawingAtPoint(posX: Float, posY: Float): MPPointF? + + /** + * This method enables a specified custom IMarker to update it's content every time the IMarker is redrawn. + * + * @param e The Entry the IMarker belongs to. This can also be any subclass of Entry, like BarEntry or + * CandleEntry, simply cast it at runtime. + * @param highlight The highlight object contains information about the highlighted value such as it's dataset-index, the + * selected range or stack-index (only stacked bar entries). + */ + fun refreshContent(e: Entry, highlight: Highlight) + + /** + * Draws the IMarker on the given position on the screen with the given Canvas object. + * + * @param canvas + * @param posX + * @param posY + */ + fun draw(canvas: Canvas?, posX: Float, posY: Float) +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java deleted file mode 100644 index 708129259b..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.java +++ /dev/null @@ -1,825 +0,0 @@ -package com.github.mikephil.charting.components; - -import android.graphics.DashPathEffect; -import android.graphics.Paint; - -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.FSize; -import com.github.mikephil.charting.utils.Utils; -import com.github.mikephil.charting.utils.ViewPortHandler; - -import java.util.ArrayList; -import java.util.List; - -/** - * Class representing the legend of the chart. The legend will contain one entry - * per color and DataSet. Multiple colors in one DataSet are grouped together. - * The legend object is NOT available before setting data to the chart. - * - * @author Philipp Jahoda - */ -public class Legend extends ComponentBase { - - public enum LegendForm { - /** - * Avoid drawing a form - */ - NONE, - - /** - * Do not draw the a form, but leave space for it - */ - EMPTY, - - /** - * Use default (default dataset's form to the legend's form) - */ - DEFAULT, - - /** - * Draw a square - */ - SQUARE, - - /** - * Draw a circle - */ - CIRCLE, - - /** - * Draw a horizontal line - */ - LINE - } - - public enum LegendHorizontalAlignment { - LEFT, CENTER, RIGHT - } - - public enum LegendVerticalAlignment { - TOP, CENTER, BOTTOM - } - - public enum LegendOrientation { - HORIZONTAL, VERTICAL - } - - public enum LegendDirection { - LEFT_TO_RIGHT, RIGHT_TO_LEFT - } - - /** - * The legend entries array - */ - private LegendEntry[] mEntries = new LegendEntry[]{}; - - /** - * Entries that will be appended to the end of the auto calculated entries after calculating the legend. - * (if the legend has already been calculated, you will need to call notifyDataSetChanged() to let the changes take effect) - */ - private LegendEntry[] mExtraEntries; - - /** - * Are the legend labels/colors a custom value or auto calculated? If false, - * then it's auto, if true, then custom. default false (automatic legend) - */ - private boolean mIsLegendCustom = false; - - private LegendHorizontalAlignment mHorizontalAlignment = LegendHorizontalAlignment.LEFT; - private LegendVerticalAlignment mVerticalAlignment = LegendVerticalAlignment.BOTTOM; - private LegendOrientation mOrientation = LegendOrientation.HORIZONTAL; - private boolean mDrawInside = false; - - /** - * the text direction for the legend - */ - private LegendDirection mDirection = LegendDirection.LEFT_TO_RIGHT; - - /** - * the shape/form the legend colors are drawn in - */ - private LegendForm mShape = LegendForm.SQUARE; - - /** - * the size of the legend forms/shapes - */ - private float mFormSize = 8f; - - /** - * the size of the legend forms/shapes - */ - private float mFormLineWidth = 3f; - - /** - * Line dash path effect used for shapes that consist of lines. - */ - private DashPathEffect mFormLineDashEffect = null; - - /** - * the space between the legend entries on a horizontal axis, default 6f - */ - private float mXEntrySpace = 6f; - - /** - * the space between the legend entries on a vertical axis, default 5f - */ - private float mYEntrySpace = 0f; - - /** - * the space between the legend entries on a vertical axis, default 2f - * private float mYEntrySpace = 2f; /** the space between the form and the - * actual label/text - */ - private float mFormToTextSpace = 5f; - - /** - * the space that should be left between stacked forms - */ - private float mStackSpace = 3f; - - /** - * the maximum relative size out of the whole chart view in percent - */ - private float mMaxSizePercent = 0.95f; - - /** - * default constructor - */ - public Legend() { - - this.mTextSize = Utils.convertDpToPixel(10f); - this.mXOffset = Utils.convertDpToPixel(5f); - this.mYOffset = Utils.convertDpToPixel(3f); // 2 - } - - /** - * Constructor. Provide entries for the legend. - * - * @param entries - */ - public Legend(LegendEntry[] entries) { - this(); - - if (entries == null) { - throw new IllegalArgumentException("entries array is NULL"); - } - - this.mEntries = entries; - } - - /** - * This method sets the automatically computed colors for the legend. Use setCustom(...) to set custom colors. - * - * @param entries - */ - public void setEntries(List entries) { - mEntries = entries.toArray(new LegendEntry[entries.size()]); - } - - public LegendEntry[] getEntries() { - return mEntries; - } - - /** - * returns the maximum length in pixels across all legend labels + formsize - * + formtotextspace - * - * @param p the paint object used for rendering the text - * @return - */ - public float getMaximumEntryWidth(Paint p) { - - float max = 0f; - float maxFormSize = 0f; - float formToTextSpace = Utils.convertDpToPixel(mFormToTextSpace); - - for (LegendEntry entry : mEntries) { - final float formSize = Utils.convertDpToPixel( - Float.isNaN(entry.formSize) - ? mFormSize : entry.formSize); - if (formSize > maxFormSize) - maxFormSize = formSize; - - String label = entry.label; - if (label == null) continue; - - float length = (float) Utils.calcTextWidth(p, label); - - if (length > max) - max = length; - } - - return max + maxFormSize + formToTextSpace; - } - - /** - * returns the maximum height in pixels across all legend labels - * - * @param p the paint object used for rendering the text - * @return - */ - public float getMaximumEntryHeight(Paint p) { - - float max = 0f; - - for (LegendEntry entry : mEntries) { - String label = entry.label; - if (label == null) continue; - - float length = (float) Utils.calcTextHeight(p, label); - - if (length > max) - max = length; - } - - return max; - } - - public LegendEntry[] getExtraEntries() { - - return mExtraEntries; - } - - public void setExtra(List entries) { - mExtraEntries = entries.toArray(new LegendEntry[entries.size()]); - } - - public void setExtra(LegendEntry[] entries) { - if (entries == null) - entries = new LegendEntry[]{}; - mExtraEntries = entries; - } - - /** - * Entries that will be appended to the end of the auto calculated - * entries after calculating the legend. - * (if the legend has already been calculated, you will need to call notifyDataSetChanged() - * to let the changes take effect) - */ - public void setExtra(int[] colors, String[] labels) { - - List entries = new ArrayList<>(); - - for (int i = 0; i < Math.min(colors.length, labels.length); i++) { - final LegendEntry entry = new LegendEntry(); - entry.formColor = colors[i]; - entry.label = labels[i]; - - if (entry.formColor == ColorTemplate.COLOR_SKIP || - entry.formColor == 0) - entry.form = LegendForm.NONE; - else if (entry.formColor == ColorTemplate.COLOR_NONE) - entry.form = LegendForm.EMPTY; - - entries.add(entry); - } - - mExtraEntries = entries.toArray(new LegendEntry[entries.size()]); - } - - /** - * Sets a custom legend's entries array. - * * A null label will start a group. - * This will disable the feature that automatically calculates the legend - * entries from the datasets. - * Call resetCustom() to re-enable automatic calculation (and then - * notifyDataSetChanged() is needed to auto-calculate the legend again) - */ - public void setCustom(LegendEntry[] entries) { - - mEntries = entries; - mIsLegendCustom = true; - } - - /** - * Sets a custom legend's entries array. - * * A null label will start a group. - * This will disable the feature that automatically calculates the legend - * entries from the datasets. - * Call resetCustom() to re-enable automatic calculation (and then - * notifyDataSetChanged() is needed to auto-calculate the legend again) - */ - public void setCustom(List entries) { - - mEntries = entries.toArray(new LegendEntry[entries.size()]); - mIsLegendCustom = true; - } - - /** - * Calling this will disable the custom legend entries (set by - * setCustom(...)). Instead, the entries will again be calculated - * automatically (after notifyDataSetChanged() is called). - */ - public void resetCustom() { - mIsLegendCustom = false; - } - - /** - * @return true if a custom legend entries has been set default - * false (automatic legend) - */ - public boolean isLegendCustom() { - return mIsLegendCustom; - } - - /** - * returns the horizontal alignment of the legend - * - * @return - */ - public LegendHorizontalAlignment getHorizontalAlignment() { - return mHorizontalAlignment; - } - - /** - * sets the horizontal alignment of the legend - * - * @param value - */ - public void setHorizontalAlignment(LegendHorizontalAlignment value) { - mHorizontalAlignment = value; - } - - /** - * returns the vertical alignment of the legend - * - * @return - */ - public LegendVerticalAlignment getVerticalAlignment() { - return mVerticalAlignment; - } - - /** - * sets the vertical alignment of the legend - * - * @param value - */ - public void setVerticalAlignment(LegendVerticalAlignment value) { - mVerticalAlignment = value; - } - - /** - * returns the orientation of the legend - * - * @return - */ - public LegendOrientation getOrientation() { - return mOrientation; - } - - /** - * sets the orientation of the legend - * - * @param value - */ - public void setOrientation(LegendOrientation value) { - mOrientation = value; - } - - /** - * returns whether the legend will draw inside the chart or outside - * - * @return - */ - public boolean isDrawInsideEnabled() { - return mDrawInside; - } - - /** - * sets whether the legend will draw inside the chart or outside - * - * @param value - */ - public void setDrawInside(boolean value) { - mDrawInside = value; - } - - /** - * returns the text direction of the legend - * - * @return - */ - public LegendDirection getDirection() { - return mDirection; - } - - /** - * sets the text direction of the legend - * - * @param pos - */ - public void setDirection(LegendDirection pos) { - mDirection = pos; - } - - /** - * returns the current form/shape that is set for the legend - * - * @return - */ - public LegendForm getForm() { - return mShape; - } - - /** - * sets the form/shape of the legend forms - * - * @param shape - */ - public void setForm(LegendForm shape) { - mShape = shape; - } - - /** - * sets the size in dp of the legend forms, default 8f - * - * @param size - */ - public void setFormSize(float size) { - mFormSize = size; - } - - /** - * returns the size in dp of the legend forms - * - * @return - */ - public float getFormSize() { - return mFormSize; - } - - /** - * sets the line width in dp for forms that consist of lines, default 3f - * - * @param size - */ - public void setFormLineWidth(float size) { - mFormLineWidth = size; - } - - /** - * returns the line width in dp for drawing forms that consist of lines - * - * @return - */ - public float getFormLineWidth() { - return mFormLineWidth; - } - - /** - * Sets the line dash path effect used for shapes that consist of lines. - * - * @param dashPathEffect - */ - public void setFormLineDashEffect(DashPathEffect dashPathEffect) { - mFormLineDashEffect = dashPathEffect; - } - - /** - * @return The line dash path effect used for shapes that consist of lines. - */ - public DashPathEffect getFormLineDashEffect() { - return mFormLineDashEffect; - } - - /** - * returns the space between the legend entries on a horizontal axis in - * pixels - * - * @return - */ - public float getXEntrySpace() { - return mXEntrySpace; - } - - /** - * sets the space between the legend entries on a horizontal axis in pixels, - * converts to dp internally - * - * @param space - */ - public void setXEntrySpace(float space) { - mXEntrySpace = space; - } - - /** - * returns the space between the legend entries on a vertical axis in pixels - * - * @return - */ - public float getYEntrySpace() { - return mYEntrySpace; - } - - /** - * sets the space between the legend entries on a vertical axis in pixels, - * converts to dp internally - * - * @param space - */ - public void setYEntrySpace(float space) { - mYEntrySpace = space; - } - - /** - * returns the space between the form and the actual label/text - * - * @return - */ - public float getFormToTextSpace() { - return mFormToTextSpace; - } - - /** - * sets the space between the form and the actual label/text, converts to dp - * internally - * - * @param space - */ - public void setFormToTextSpace(float space) { - this.mFormToTextSpace = space; - } - - /** - * returns the space that is left out between stacked forms (with no label) - * - * @return - */ - public float getStackSpace() { - return mStackSpace; - } - - /** - * sets the space that is left out between stacked forms (with no label) - * - * @param space - */ - public void setStackSpace(float space) { - mStackSpace = space; - } - - /** - * the total width of the legend (needed width space) - */ - public float mNeededWidth = 0f; - - /** - * the total height of the legend (needed height space) - */ - public float mNeededHeight = 0f; - - public float mTextHeightMax = 0f; - - public float mTextWidthMax = 0f; - - /** - * flag that indicates if word wrapping is enabled - */ - private boolean mWordWrapEnabled = false; - - /** - * Should the legend word wrap? / this is currently supported only for: - * BelowChartLeft, BelowChartRight, BelowChartCenter. / note that word - * wrapping a legend takes a toll on performance. / you may want to set - * maxSizePercent when word wrapping, to set the point where the text wraps. - * / default: false - * - * @param enabled - */ - public void setWordWrapEnabled(boolean enabled) { - mWordWrapEnabled = enabled; - } - - /** - * If this is set, then word wrapping the legend is enabled. This means the - * legend will not be cut off if too long. - * - * @return - */ - public boolean isWordWrapEnabled() { - return mWordWrapEnabled; - } - - /** - * The maximum relative size out of the whole chart view. / If the legend is - * to the right/left of the chart, then this affects the width of the - * legend. / If the legend is to the top/bottom of the chart, then this - * affects the height of the legend. / If the legend is the center of the - * piechart, then this defines the size of the rectangular bounds out of the - * size of the "hole". / default: 0.95f (95%) - * - * @return - */ - public float getMaxSizePercent() { - return mMaxSizePercent; - } - - /** - * The maximum relative size out of the whole chart view. / If - * the legend is to the right/left of the chart, then this affects the width - * of the legend. / If the legend is to the top/bottom of the chart, then - * this affects the height of the legend. / default: 0.95f (95%) - * - * @param maxSize - */ - public void setMaxSizePercent(float maxSize) { - mMaxSizePercent = maxSize; - } - - private List mCalculatedLabelSizes = new ArrayList<>(16); - private List mCalculatedLabelBreakPoints = new ArrayList<>(16); - private List mCalculatedLineSizes = new ArrayList<>(16); - - public List getCalculatedLabelSizes() { - return mCalculatedLabelSizes; - } - - public List getCalculatedLabelBreakPoints() { - return mCalculatedLabelBreakPoints; - } - - public List getCalculatedLineSizes() { - return mCalculatedLineSizes; - } - - /** - * Calculates the dimensions of the Legend. This includes the maximum width - * and height of a single entry, as well as the total width and height of - * the Legend. - * - * @param labelpaint - */ - public void calculateDimensions(Paint labelpaint, ViewPortHandler viewPortHandler) { - - float defaultFormSize = Utils.convertDpToPixel(mFormSize); - float stackSpace = Utils.convertDpToPixel(mStackSpace); - float formToTextSpace = Utils.convertDpToPixel(mFormToTextSpace); - float xEntrySpace = Utils.convertDpToPixel(mXEntrySpace); - float yEntrySpace = Utils.convertDpToPixel(mYEntrySpace); - boolean wordWrapEnabled = mWordWrapEnabled; - LegendEntry[] entries = mEntries; - int entryCount = entries.length; - - mTextWidthMax = getMaximumEntryWidth(labelpaint); - mTextHeightMax = getMaximumEntryHeight(labelpaint); - - switch (mOrientation) { - case VERTICAL: { - - float maxWidth = 0f, maxHeight = 0f, width = 0f; - float labelLineHeight = Utils.getLineHeight(labelpaint); - boolean wasStacked = false; - - for (int i = 0; i < entryCount; i++) { - - LegendEntry e = entries[i]; - boolean drawingForm = e.form != LegendForm.NONE; - float formSize = Float.isNaN(e.formSize) - ? defaultFormSize - : Utils.convertDpToPixel(e.formSize); - String label = e.label; - - if (!wasStacked) - width = 0.f; - - if (drawingForm) { - if (wasStacked) - width += stackSpace; - width += formSize; - } - - // grouped forms have null labels - if (label != null) { - - // make a step to the left - if (drawingForm && !wasStacked) - width += formToTextSpace; - else if (wasStacked) { - maxWidth = Math.max(maxWidth, width); - maxHeight += labelLineHeight + yEntrySpace; - width = 0.f; - wasStacked = false; - } - - width += Utils.calcTextWidth(labelpaint, label); - - maxHeight += labelLineHeight + yEntrySpace; - } else { - wasStacked = true; - width += formSize; - if (i < entryCount - 1) - width += stackSpace; - } - - maxWidth = Math.max(maxWidth, width); - } - - mNeededWidth = maxWidth; - mNeededHeight = maxHeight; - - break; - } - case HORIZONTAL: { - - float labelLineHeight = Utils.getLineHeight(labelpaint); - float labelLineSpacing = Utils.getLineSpacing(labelpaint) + yEntrySpace; - float contentWidth = viewPortHandler.contentWidth() * mMaxSizePercent; - - // Start calculating layout - float maxLineWidth = 0.f; - float currentLineWidth = 0.f; - float requiredWidth = 0.f; - int stackedStartIndex = -1; - - mCalculatedLabelBreakPoints.clear(); - mCalculatedLabelSizes.clear(); - mCalculatedLineSizes.clear(); - - for (int i = 0; i < entryCount; i++) { - - LegendEntry e = entries[i]; - boolean drawingForm = e.form != LegendForm.NONE; - float formSize = Float.isNaN(e.formSize) - ? defaultFormSize - : Utils.convertDpToPixel(e.formSize); - String label = e.label; - - mCalculatedLabelBreakPoints.add(false); - - if (stackedStartIndex == -1) { - // we are not stacking, so required width is for this label - // only - requiredWidth = 0.f; - } else { - // add the spacing appropriate for stacked labels/forms - requiredWidth += stackSpace; - } - - // grouped forms have null labels - if (label != null) { - - mCalculatedLabelSizes.add(Utils.calcTextSize(labelpaint, label)); - requiredWidth += drawingForm ? formToTextSpace + formSize : 0.f; - requiredWidth += mCalculatedLabelSizes.get(i).width; - } else { - - mCalculatedLabelSizes.add(FSize.getInstance(0.f, 0.f)); - requiredWidth += drawingForm ? formSize : 0.f; - - if (stackedStartIndex == -1) { - // mark this index as we might want to break here later - stackedStartIndex = i; - } - } - - if (label != null || i == entryCount - 1) { - - float requiredSpacing = currentLineWidth == 0.f ? 0.f : xEntrySpace; - - if (!wordWrapEnabled // No word wrapping, it must fit. - // The line is empty, it must fit - || currentLineWidth == 0.f - // It simply fits - || (contentWidth - currentLineWidth >= - requiredSpacing + requiredWidth)) { - // Expand current line - currentLineWidth += requiredSpacing + requiredWidth; - } else { // It doesn't fit, we need to wrap a line - - // Add current line size to array - mCalculatedLineSizes.add(FSize.getInstance(currentLineWidth, labelLineHeight)); - maxLineWidth = Math.max(maxLineWidth, currentLineWidth); - - // Start a new line - mCalculatedLabelBreakPoints.set( - stackedStartIndex > -1 ? stackedStartIndex - : i, true); - currentLineWidth = requiredWidth; - } - - if (i == entryCount - 1) { - // Add last line size to array - mCalculatedLineSizes.add(FSize.getInstance(currentLineWidth, labelLineHeight)); - maxLineWidth = Math.max(maxLineWidth, currentLineWidth); - } - } - - stackedStartIndex = label != null ? -1 : stackedStartIndex; - } - - mNeededWidth = maxLineWidth; - mNeededHeight = labelLineHeight - * (float) (mCalculatedLineSizes.size()) - + labelLineSpacing * - (float) (mCalculatedLineSizes.size() == 0 - ? 0 - : (mCalculatedLineSizes.size() - 1)); - - break; - } - } - - mNeededHeight += mYOffset; - mNeededWidth += mXOffset; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.kt new file mode 100644 index 0000000000..5714a1bd42 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/Legend.kt @@ -0,0 +1,668 @@ +package com.github.mikephil.charting.components + +import android.graphics.DashPathEffect +import android.graphics.Paint +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.FSize +import com.github.mikephil.charting.utils.Utils +import com.github.mikephil.charting.utils.ViewPortHandler +import kotlin.math.max +import kotlin.math.min + +/** + * Class representing the legend of the chart. The legend will contain one entry + * per color and DataSet. Multiple colors in one DataSet are grouped together. + * The legend object is NOT available before setting data to the chart. + * + * @author Philipp Jahoda + */ +class Legend() : ComponentBase() { + enum class LegendForm { + /** + * Avoid drawing a form + */ + NONE, + + /** + * Do not draw the a form, but leave space for it + */ + EMPTY, + + /** + * Use default (default dataset's form to the legend's form) + */ + DEFAULT, + + /** + * Draw a square + */ + SQUARE, + + /** + * Draw a circle + */ + CIRCLE, + + /** + * Draw a horizontal line + */ + LINE + } + + enum class LegendHorizontalAlignment { + LEFT, CENTER, RIGHT + } + + enum class LegendVerticalAlignment { + TOP, CENTER, BOTTOM + } + + enum class LegendOrientation { + HORIZONTAL, VERTICAL + } + + enum class LegendDirection { + LEFT_TO_RIGHT, RIGHT_TO_LEFT + } + + /** + * The legend entries array + */ + var entries: Array = arrayOf() + private set + + /** + * Entries that will be appended to the end of the auto calculated entries after calculating the legend. + * (if the legend has already been calculated, you will need to call notifyDataSetChanged() to let the changes take effect) + */ + var extraEntries: Array = arrayOf() + private set + + /** + * @return true if a custom legend entries has been set default + * false (automatic legend) + */ + /** + * Are the legend labels/colors a custom value or auto calculated? If false, + * then it's auto, if true, then custom. default false (automatic legend) + */ + var isLegendCustom: Boolean = false + private set + + /** + * returns the horizontal alignment of the legend + * + * @return + */ + /** + * sets the horizontal alignment of the legend + * + * @param value + */ + var horizontalAlignment: LegendHorizontalAlignment = LegendHorizontalAlignment.LEFT + /** + * returns the vertical alignment of the legend + * + * @return + */ + /** + * sets the vertical alignment of the legend + * + * @param value + */ + var verticalAlignment: LegendVerticalAlignment = LegendVerticalAlignment.BOTTOM + /** + * returns the orientation of the legend + * + * @return + */ + /** + * sets the orientation of the legend + * + * @param value + */ + var orientation: LegendOrientation = LegendOrientation.HORIZONTAL + + /** + * returns whether the legend will draw inside the chart or outside + * + * @return + */ + var isDrawInsideEnabled: Boolean = false + private set + + /** + * returns the text direction of the legend + * + * @return + */ + /** + * sets the text direction of the legend + * + * @param pos + */ + /** + * the text direction for the legend + */ + var direction: LegendDirection? = LegendDirection.LEFT_TO_RIGHT + + /** + * returns the current form/shape that is set for the legend + * + * @return + */ + /** + * sets the form/shape of the legend forms + * + * @param shape + */ + /** + * the shape/form the legend colors are drawn in + */ + var form: LegendForm? = LegendForm.SQUARE + + /** + * returns the size in dp of the legend forms + * + * @return + */ + /** + * sets the size in dp of the legend forms, default 8f + * + * @param size + */ + /** + * the size of the legend forms/shapes + */ + var formSize: Float = 8f + + /** + * returns the line width in dp for drawing forms that consist of lines + * + * @return + */ + /** + * sets the line width in dp for forms that consist of lines, default 3f + * + * @param size + */ + /** + * the size of the legend forms/shapes + */ + var formLineWidth: Float = 3f + + /** + * @return The line dash path effect used for shapes that consist of lines. + */ + /** + * Sets the line dash path effect used for shapes that consist of lines. + * + * @param dashPathEffect + */ + /** + * Line dash path effect used for shapes that consist of lines. + */ + var formLineDashEffect: DashPathEffect? = null + + /** + * returns the space between the legend entries on a horizontal axis in + * pixels + * + * @return + */ + /** + * sets the space between the legend entries on a horizontal axis in pixels, + * converts to dp internally + * + * @param space + */ + /** + * the space between the legend entries on a horizontal axis, default 6f + */ + var xEntrySpace: Float = 6f + + /** + * returns the space between the legend entries on a vertical axis in pixels + * + * @return + */ + /** + * sets the space between the legend entries on a vertical axis in pixels, + * converts to dp internally + * + * @param space + */ + /** + * the space between the legend entries on a vertical axis, default 5f + */ + var yEntrySpace: Float = 0f + + /** + * returns the space between the form and the actual label/text + * + * @return + */ + /** + * sets the space between the form and the actual label/text, converts to dp + * internally + * + * @param space + */ + /** + * the space between the legend entries on a vertical axis, default 2f + * private float mYEntrySpace = 2f; / ** the space between the form and the + * actual label/text + */ + var formToTextSpace: Float = 5f + + /** + * returns the space that is left out between stacked forms (with no label) + * + * @return + */ + /** + * sets the space that is left out between stacked forms (with no label) + * + * @param space + */ + /** + * the space that should be left between stacked forms + */ + var stackSpace: Float = 3f + + /** + * The maximum relative size out of the whole chart view. / If the legend is + * to the right/left of the chart, then this affects the width of the + * legend. / If the legend is to the top/bottom of the chart, then this + * affects the height of the legend. / If the legend is the center of the + * piechart, then this defines the size of the rectangular bounds out of the + * size of the "hole". / default: 0.95f (95%) + * + * @return + */ + /** + * The maximum relative size out of the whole chart view. / If + * the legend is to the right/left of the chart, then this affects the width + * of the legend. / If the legend is to the top/bottom of the chart, then + * this affects the height of the legend. / default: 0.95f (95%) + * + * @param maxSize + */ + /** + * the maximum relative size out of the whole chart view in percent + */ + var maxSizePercent: Float = 0.95f + + /** + * Constructor. Provide entries for the legend. + * + * @param entries + */ + constructor(entries: Array) : this() { + requireNotNull(entries) { "entries array is NULL" } + + this.entries = entries + } + + /** + * This method sets the automatically computed colors for the legend. Use setCustom(...) to set custom colors. + * + * @param entries + */ + fun setEntries(entries: MutableList) { + this.entries = entries.toTypedArray() + } + + /** + * returns the maximum length in pixels across all legend labels + formsize + * + formtotextspace + * + * @param p the paint object used for rendering the text + * @return + */ + fun getMaximumEntryWidth(p: Paint): Float { + var max = 0f + var maxFormSize = 0f + val formToTextSpace = Utils.convertDpToPixel(this.formToTextSpace) + + for (entry in this.entries) { + val formSize = Utils.convertDpToPixel( + if (entry.formSize.isNaN()) + this.formSize + else + entry.formSize + ) + if (formSize > maxFormSize) maxFormSize = formSize + + val label = entry.label + if (label == null) continue + + val length = Utils.calcTextWidth(p, label).toFloat() + + if (length > max) max = length + } + + return max + maxFormSize + formToTextSpace + } + + /** + * returns the maximum height in pixels across all legend labels + * + * @param p the paint object used for rendering the text + * @return + */ + fun getMaximumEntryHeight(p: Paint): Float { + var max = 0f + + for (entry in this.entries) { + val label = entry.label + if (label == null) continue + + val length = Utils.calcTextHeight(p, label).toFloat() + + if (length > max) max = length + } + + return max + } + + fun setExtra(entries: MutableList) { + this.extraEntries = entries.toTypedArray() + } + + fun setExtra(entries: Array) { + this.extraEntries = entries + } + + /** + * Entries that will be appended to the end of the auto calculated + * entries after calculating the legend. + * (if the legend has already been calculated, you will need to call notifyDataSetChanged() + * to let the changes take effect) + */ + fun setExtra(colors: IntArray, labels: Array) { + val entries: MutableList = ArrayList() + + for (i in 0..() + } + + /** + * Sets a custom legend's entries array. + * * A null label will start a group. + * This will disable the feature that automatically calculates the legend + * entries from the datasets. + * Call resetCustom() to re-enable automatic calculation (and then + * notifyDataSetChanged() is needed to auto-calculate the legend again) + */ + fun setCustom(entries: Array) { + this.entries = entries + this.isLegendCustom = true + } + + /** + * Sets a custom legend's entries array. + * * A null label will start a group. + * This will disable the feature that automatically calculates the legend + * entries from the datasets. + * Call resetCustom() to re-enable automatic calculation (and then + * notifyDataSetChanged() is needed to auto-calculate the legend again) + */ + fun setCustom(entries: MutableList) { + this.entries = entries.toTypedArray() + this.isLegendCustom = true + } + + /** + * Calling this will disable the custom legend entries (set by + * setCustom(...)). Instead, the entries will again be calculated + * automatically (after notifyDataSetChanged() is called). + */ + fun resetCustom() { + this.isLegendCustom = false + } + + /** + * sets whether the legend will draw inside the chart or outside + * + * @param value + */ + fun setDrawInside(value: Boolean) { + this.isDrawInsideEnabled = value + } + + /** + * the total width of the legend (needed width space) + */ + var mNeededWidth: Float = 0f + + /** + * the total height of the legend (needed height space) + */ + var mNeededHeight: Float = 0f + + var mTextHeightMax: Float = 0f + + var mTextWidthMax: Float = 0f + + /** + * If this is set, then word wrapping the legend is enabled. This means the + * legend will not be cut off if too long. + * + * @return + */ + /** + * Should the legend word wrap? / this is currently supported only for: + * BelowChartLeft, BelowChartRight, BelowChartCenter. / note that word + * wrapping a legend takes a toll on performance. / you may want to set + * maxSizePercent when word wrapping, to set the point where the text wraps. + * / default: false + * + * @param enabled + */ + /** + * flag that indicates if word wrapping is enabled + */ + var isWordWrapEnabled: Boolean = false + + val calculatedLabelSizes: MutableList = ArrayList(16) + val calculatedLabelBreakPoints: MutableList = ArrayList(16) + val calculatedLineSizes: MutableList = ArrayList(16) + + /** + * default constructor + */ + init { + this.mTextSize = Utils.convertDpToPixel(10f) + this.mXOffset = Utils.convertDpToPixel(5f) + this.mYOffset = Utils.convertDpToPixel(3f) // 2 + } + + /** + * Calculates the dimensions of the Legend. This includes the maximum width + * and height of a single entry, as well as the total width and height of + * the Legend. + * + * @param labelpaint + */ + fun calculateDimensions(labelpaint: Paint, viewPortHandler: ViewPortHandler) { + val defaultFormSize = Utils.convertDpToPixel(this.formSize) + val stackSpace = Utils.convertDpToPixel(this.stackSpace) + val formToTextSpace = Utils.convertDpToPixel(this.formToTextSpace) + val xEntrySpace = Utils.convertDpToPixel(this.xEntrySpace) + val yEntrySpace = Utils.convertDpToPixel(this.yEntrySpace) + val wordWrapEnabled = this.isWordWrapEnabled + val entries = this.entries + val entryCount = entries.size + + mTextWidthMax = getMaximumEntryWidth(labelpaint) + mTextHeightMax = getMaximumEntryHeight(labelpaint) + + when (this.orientation) { + LegendOrientation.VERTICAL -> { + var maxWidth = 0f + var maxHeight = 0f + var width = 0f + val labelLineHeight = Utils.getLineHeight(labelpaint) + var wasStacked = false + + var i = 0 + while (i < entryCount) { + val e = entries[i] + val drawingForm = e.form != LegendForm.NONE + val formSize = if (e.formSize.isNaN()) + defaultFormSize + else + Utils.convertDpToPixel(e.formSize) + val label = e.label + + if (!wasStacked) width = 0f + + if (drawingForm) { + if (wasStacked) width += stackSpace + width += formSize + } + + // grouped forms have null labels + if (label != null) { + // make a step to the left + + if (drawingForm && !wasStacked) width += formToTextSpace + else if (wasStacked) { + maxWidth = max(maxWidth, width) + maxHeight += labelLineHeight + yEntrySpace + width = 0f + wasStacked = false + } + + width += Utils.calcTextWidth(labelpaint, label).toFloat() + + maxHeight += labelLineHeight + yEntrySpace + } else { + wasStacked = true + width += formSize + if (i < entryCount - 1) width += stackSpace + } + + maxWidth = max(maxWidth, width) + i++ + } + + mNeededWidth = maxWidth + mNeededHeight = maxHeight + } + + LegendOrientation.HORIZONTAL -> { + val labelLineHeight = Utils.getLineHeight(labelpaint) + val labelLineSpacing = Utils.getLineSpacing(labelpaint) + yEntrySpace + val contentWidth = viewPortHandler.contentWidth() * this.maxSizePercent + + // Start calculating layout + var maxLineWidth = 0f + var currentLineWidth = 0f + var requiredWidth = 0f + var stackedStartIndex = -1 + + calculatedLabelBreakPoints.clear() + calculatedLabelSizes.clear() + calculatedLineSizes.clear() + + var i = 0 + while (i < entryCount) { + val e = entries[i] + val drawingForm = e.form != LegendForm.NONE + val formSize = if (e.formSize.isNaN()) + defaultFormSize + else + Utils.convertDpToPixel(e.formSize) + val label = e.label + + calculatedLabelBreakPoints.add(false) + + if (stackedStartIndex == -1) { + // we are not stacking, so required width is for this label + // only + requiredWidth = 0f + } else { + // add the spacing appropriate for stacked labels/forms + requiredWidth += stackSpace + } + + // grouped forms have null labels + if (label != null) { + calculatedLabelSizes.add(Utils.calcTextSize(labelpaint, label)) + requiredWidth += if (drawingForm) formToTextSpace + formSize else 0f + requiredWidth += calculatedLabelSizes[i].width + } else { + calculatedLabelSizes.add(FSize.Companion.getInstance(0f, 0f)) + requiredWidth += if (drawingForm) formSize else 0f + + if (stackedStartIndex == -1) { + // mark this index as we might want to break here later + stackedStartIndex = i + } + } + + if (label != null || i == entryCount - 1) { + val requiredSpacing = if (currentLineWidth == 0f) 0f else xEntrySpace + + if (!wordWrapEnabled // No word wrapping, it must fit. + // The line is empty, it must fit + || currentLineWidth == 0f // It simply fits + || (contentWidth - currentLineWidth >= + requiredSpacing + requiredWidth) + ) { + // Expand current line + currentLineWidth += requiredSpacing + requiredWidth + } else { // It doesn't fit, we need to wrap a line + + // Add current line size to array + + calculatedLineSizes.add(FSize.Companion.getInstance(currentLineWidth, labelLineHeight)) + maxLineWidth = max(maxLineWidth, currentLineWidth) + + // Start a new line + calculatedLabelBreakPoints[if (stackedStartIndex > -1) + stackedStartIndex + else + i] = true + currentLineWidth = requiredWidth + } + + if (i == entryCount - 1) { + // Add last line size to array + calculatedLineSizes.add(FSize.Companion.getInstance(currentLineWidth, labelLineHeight)) + maxLineWidth = max(maxLineWidth, currentLineWidth) + } + } + + stackedStartIndex = if (label != null) -1 else stackedStartIndex + i++ + } + + mNeededWidth = maxLineWidth + mNeededHeight = (labelLineHeight + * (calculatedLineSizes.size).toFloat() + + labelLineSpacing * (if (calculatedLineSizes.isEmpty()) + 0 + else + (calculatedLineSizes.size - 1)).toFloat()) + } + } + + mNeededHeight += mYOffset + mNeededWidth += mXOffset + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.kt similarity index 55% rename from MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.kt index 3acec0f461..b429168437 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LegendEntry.kt @@ -1,14 +1,12 @@ -package com.github.mikephil.charting.components; +package com.github.mikephil.charting.components +import android.graphics.DashPathEffect +import com.github.mikephil.charting.components.Legend.LegendForm +import com.github.mikephil.charting.utils.ColorTemplate -import android.graphics.DashPathEffect; -import com.github.mikephil.charting.utils.ColorTemplate; - -public class LegendEntry { - public LegendEntry() { - - } +class LegendEntry { + constructor() /** * @@ -19,26 +17,27 @@ public LegendEntry() { * @param formLineDashEffect Set to nil to use the legend's default. * @param formColor The color for drawing the form. */ - public LegendEntry(String label, - Legend.LegendForm form, - float formSize, - float formLineWidth, - DashPathEffect formLineDashEffect, - int formColor) - { - this.label = label; - this.form = form; - this.formSize = formSize; - this.formLineWidth = formLineWidth; - this.formLineDashEffect = formLineDashEffect; - this.formColor = formColor; + constructor( + label: String?, + form: LegendForm?, + formSize: Float, + formLineWidth: Float, + formLineDashEffect: DashPathEffect?, + formColor: Int + ) { + this.label = label + this.form = form + this.formSize = formSize + this.formLineWidth = formLineWidth + this.formLineDashEffect = formLineDashEffect + this.formColor = formColor } /** * The legend entry text. * A `null` label will start a group. */ - public String label; + var label: String? = null /** * The form to draw for this entry. @@ -47,32 +46,31 @@ public LegendEntry(String label, * `EMPTY` will avoid drawing a form, but keep its space. * `DEFAULT` will use the Legend's default. */ - public Legend.LegendForm form = Legend.LegendForm.DEFAULT; + var form: LegendForm? = LegendForm.DEFAULT /** * Form size will be considered except for when .None is used * * Set as NaN to use the legend's default */ - public float formSize = Float.NaN; + var formSize: Float = Float.Companion.NaN /** * Line width used for shapes that consist of lines. * * Set as NaN to use the legend's default */ - public float formLineWidth = Float.NaN; + var formLineWidth: Float = Float.Companion.NaN /** * Line dash path effect used for shapes that consist of lines. * * Set to null to use the legend's default */ - public DashPathEffect formLineDashEffect = null; + var formLineDashEffect: DashPathEffect? = null /** * The color for drawing the form */ - public int formColor = ColorTemplate.COLOR_NONE; - + var formColor: Int = ColorTemplate.COLOR_NONE } \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitLine.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitLine.java deleted file mode 100644 index 8fcdee8fc1..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitLine.java +++ /dev/null @@ -1,215 +0,0 @@ - -package com.github.mikephil.charting.components; - -import android.graphics.Color; -import android.graphics.DashPathEffect; -import android.graphics.Paint; -import android.graphics.Typeface; - -import com.github.mikephil.charting.utils.Utils; - -/** - * The limit line is an additional feature for all Line-, Bar- and - * ScatterCharts. It allows the displaying of an additional line in the chart - * that marks a certain maximum / limit on the specified axis (x- or y-axis). - * - * @author Philipp Jahoda - */ -public class LimitLine extends ComponentBase { - - /** limit / maximum (the y-value or xIndex) */ - private float mLimit = 0f; - - /** the width of the limit line */ - private float mLineWidth = 2f; - - /** the color of the limit line */ - private int mLineColor = Color.rgb(237, 91, 91); - - /** the style of the label text */ - private Paint.Style mTextStyle = Paint.Style.FILL_AND_STROKE; - - /** label string that is drawn next to the limit line */ - private String mLabel = ""; - - /** the path effect of this LimitLine that makes dashed lines possible */ - private DashPathEffect mDashPathEffect = null; - - /** indicates the position of the LimitLine label */ - private LimitLabelPosition mLabelPosition = LimitLabelPosition.RIGHT_TOP; - - /** enum that indicates the position of the LimitLine label */ - public enum LimitLabelPosition { - LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM - } - - /** - * Constructor with limit. - * - * @param limit - the position (the value) on the y-axis (y-value) or x-axis - * (xIndex) where this line should appear - */ - public LimitLine(float limit) { - mLimit = limit; - } - - /** - * Constructor with limit and label. - * - * @param limit - the position (the value) on the y-axis (y-value) or x-axis - * (xIndex) where this line should appear - * @param label - provide "" if no label is required - */ - public LimitLine(float limit, String label) { - mLimit = limit; - mLabel = label; - } - - /** - * Returns the limit that is set for this line. - * - * @return - */ - public float getLimit() { - return mLimit; - } - - /** - * set the line width of the chart (min = 0.2f, max = 12f); default 2f NOTE: - * thinner line == better performance, thicker line == worse performance - * - * @param width - */ - public void setLineWidth(float width) { - - if (width < 0.2f) - width = 0.2f; - if (width > 12.0f) - width = 12.0f; - mLineWidth = Utils.convertDpToPixel(width); - } - - /** - * returns the width of limit line - * - * @return - */ - public float getLineWidth() { - return mLineWidth; - } - - /** - * Sets the linecolor for this LimitLine. Make sure to use - * getResources().getColor(...) - * - * @param color - */ - public void setLineColor(int color) { - mLineColor = color; - } - - /** - * Returns the color that is used for this LimitLine - * - * @return - */ - public int getLineColor() { - return mLineColor; - } - - /** - * Enables the line to be drawn in dashed mode, e.g. like this "- - - - - -" - * - * @param lineLength the length of the line pieces - * @param spaceLength the length of space inbetween the pieces - * @param phase offset, in degrees (normally, use 0) - */ - public void enableDashedLine(float lineLength, float spaceLength, float phase) { - mDashPathEffect = new DashPathEffect(new float[] { - lineLength, spaceLength - }, phase); - } - - /** - * Disables the line to be drawn in dashed mode. - */ - public void disableDashedLine() { - mDashPathEffect = null; - } - - /** - * Returns true if the dashed-line effect is enabled, false if not. Default: - * disabled - * - * @return - */ - public boolean isDashedLineEnabled() { - return mDashPathEffect == null ? false : true; - } - - /** - * returns the DashPathEffect that is set for this LimitLine - * - * @return - */ - public DashPathEffect getDashPathEffect() { - return mDashPathEffect; - } - - /** - * Sets the color of the value-text that is drawn next to the LimitLine. - * Default: Paint.Style.FILL_AND_STROKE - * - * @param style - */ - public void setTextStyle(Paint.Style style) { - this.mTextStyle = style; - } - - /** - * Returns the color of the value-text that is drawn next to the LimitLine. - * - * @return - */ - public Paint.Style getTextStyle() { - return mTextStyle; - } - - /** - * Sets the position of the LimitLine value label (either on the right or on - * the left edge of the chart). Not supported for RadarChart. - * - * @param pos - */ - public void setLabelPosition(LimitLabelPosition pos) { - mLabelPosition = pos; - } - - /** - * Returns the position of the LimitLine label (value). - * - * @return - */ - public LimitLabelPosition getLabelPosition() { - return mLabelPosition; - } - - /** - * Sets the label that is drawn next to the limit line. Provide "" if no - * label is required. - * - * @param label - */ - public void setLabel(String label) { - mLabel = label; - } - - /** - * Returns the label that is drawn next to the limit line. - * - * @return - */ - public String getLabel() { - return mLabel; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitLine.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitLine.kt new file mode 100644 index 0000000000..884c404cf7 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitLine.kt @@ -0,0 +1,170 @@ +package com.github.mikephil.charting.components + +import android.graphics.Color +import android.graphics.DashPathEffect +import android.graphics.Paint +import com.github.mikephil.charting.utils.Utils + +/** + * The limit line is an additional feature for all Line-, Bar- and + * ScatterCharts. It allows the displaying of an additional line in the chart + * that marks a certain maximum / limit on the specified axis (x- or y-axis). + * + * @author Philipp Jahoda + */ +class LimitLine : ComponentBase { + /** + * Returns the limit that is set for this line. + * + * @return + */ + /** limit / maximum (the y-value or xIndex) */ + var limit: Float = 0f + private set + + /** the width of the limit line */ + private var mLineWidth = 2f + + /** + * Returns the color that is used for this LimitLine + * + * @return + */ + /** + * Sets the linecolor for this LimitLine. Make sure to use + * getResources().getColor(...) + * + * @param color + */ + /** the color of the limit line */ + var lineColor: Int = Color.rgb(237, 91, 91) + + /** + * Returns the color of the value-text that is drawn next to the LimitLine. + * + * @return + */ + /** + * Sets the color of the value-text that is drawn next to the LimitLine. + * Default: Paint.Style.FILL_AND_STROKE + * + * @param style + */ + /** the style of the label text */ + var textStyle: Paint.Style? = Paint.Style.FILL_AND_STROKE + + /** + * Returns the label that is drawn next to the limit line. + * + * @return + */ + /** + * Sets the label that is drawn next to the limit line. Provide "" if no + * label is required. + * + * @param label + */ + /** label string that is drawn next to the limit line */ + var label: String? = "" + + /** + * returns the DashPathEffect that is set for this LimitLine + * + * @return + */ + /** the path effect of this LimitLine that makes dashed lines possible */ + var dashPathEffect: DashPathEffect? = null + private set + + /** + * Returns the position of the LimitLine label (value). + * + * @return + */ + /** + * Sets the position of the LimitLine value label (either on the right or on + * the left edge of the chart). Not supported for RadarChart. + * + * @param pos + */ + /** indicates the position of the LimitLine label */ + var labelPosition: LimitLabelPosition? = LimitLabelPosition.RIGHT_TOP + + /** enum that indicates the position of the LimitLine label */ + enum class LimitLabelPosition { + LEFT_TOP, LEFT_BOTTOM, RIGHT_TOP, RIGHT_BOTTOM + } + + /** + * Constructor with limit. + * + * @param limit - the position (the value) on the y-axis (y-value) or x-axis + * (xIndex) where this line should appear + */ + constructor(limit: Float) { + this.limit = limit + } + + /** + * Constructor with limit and label. + * + * @param limit - the position (the value) on the y-axis (y-value) or x-axis + * (xIndex) where this line should appear + * @param label - provide "" if no label is required + */ + constructor(limit: Float, label: String?) { + this.limit = limit + this.label = label + } + + var lineWidth: Float + /** + * returns the width of limit line + * + * @return + */ + get() = mLineWidth + /** + * set the line width of the chart (min = 0.2f, max = 12f); default 2f NOTE: + * thinner line == better performance, thicker line == worse performance + * + * @param width + */ + set(width) { + var width = width + if (width < 0.2f) width = 0.2f + if (width > 12.0f) width = 12.0f + mLineWidth = Utils.convertDpToPixel(width) + } + + /** + * Enables the line to be drawn in dashed mode, e.g. like this "- - - - - -" + * + * @param lineLength the length of the line pieces + * @param spaceLength the length of space inbetween the pieces + * @param phase offset, in degrees (normally, use 0) + */ + fun enableDashedLine(lineLength: Float, spaceLength: Float, phase: Float) { + this.dashPathEffect = DashPathEffect( + floatArrayOf( + lineLength, spaceLength + ), phase + ) + } + + /** + * Disables the line to be drawn in dashed mode. + */ + fun disableDashedLine() { + this.dashPathEffect = null + } + + val isDashedLineEnabled: Boolean + /** + * Returns true if the dashed-line effect is enabled, false if not. Default: + * disabled + * + * @return + */ + get() = this.dashPathEffect != null +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitRange.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitRange.java deleted file mode 100644 index 926c844998..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitRange.java +++ /dev/null @@ -1,270 +0,0 @@ - -package com.github.mikephil.charting.components; - -import android.graphics.Color; -import android.graphics.DashPathEffect; -import android.graphics.Paint; - -import com.github.mikephil.charting.utils.Utils; - -/** - * The limit line is an additional feature for all Line-, Bar- and - * ScatterCharts. It allows the displaying of an additional line in the chart - * that marks a certain maximum / limit on the specified axis (x- or y-axis). - */ -public class LimitRange extends ComponentBase { - - public static class Range { - private final float mLow; - private final float mHigh; - - Range(float r1, float r2) { - if (r1 < r2) { - mLow = r1; - mHigh = r2; - } else { - mLow = r2; - mHigh = r1; - } - } - - public float getLow() { - return mLow; - } - - public float getHigh() { - return mHigh; - } - } - - /** - * limit / maximum (the y-value or xIndex) - */ - private Range mLimit; - - /** - * the width of the limit line - */ - private float mLineWidth = 0f; - - /** - * the color of the limit line - */ - private int mLineColor = Color.rgb(237, 91, 91); - - /** - * the color of the Range - */ - private int mRangeColor = Color.rgb(128, 128, 128); - - /** - * the style of the label text - */ - private Paint.Style mTextStyle = Paint.Style.FILL; - - /** - * label string that is drawn next to the limit line - */ - private String mLabel = ""; - - /** - * the path effect of this LimitLine that makes dashed lines possible - */ - private DashPathEffect mDashPathEffect = null; - - /** - * indicates the position of the LimitLine label - */ - private LimitLine.LimitLabelPosition mLabelPosition = LimitLine.LimitLabelPosition.RIGHT_TOP; - - /** - * Constructor with limit. - * - * @param firstLimit - the position (the value) on the y-axis (y-value) or x-axis - * (xIndex) where this line should appear - * @param secondLimit - the position (the value) on the y-axis (y-value) or x-axis - * (xIndex) where this line should appear - */ - public LimitRange(float firstLimit, float secondLimit) { - mLimit = new Range(firstLimit, secondLimit); - } - - /** - * Constructor with limit and label. - * - * @param firstLimit - the position (the value) on the y-axis (y-value) or x-axis - * (xIndex) where this line should appear - * @param secondLimit - the position (the value) on the y-axis (y-value) or x-axis - * (xIndex) where this line should appear - * @param label - provide "" if no label is required - */ - public LimitRange(float firstLimit, float secondLimit, String label) { - mLimit = new Range(firstLimit, secondLimit); - mLabel = label; - } - - /** - * Returns the limit that is set for this line. - * - * @return - */ - public Range getLimit() { - return mLimit; - } - - /** - * set the line width of the chart (min = 0.2f, max = 12f); default 2f NOTE: - * thinner line == better performance, thicker line == worse performance - * - * @param width - */ - public void setLineWidth(float width) { - if (width > 12.0f) { - width = 12.0f; - } - mLineWidth = Utils.convertDpToPixel(width); - } - - /** - * returns the width of limit line - * - * @return - */ - public float getLineWidth() { - return mLineWidth; - } - - /** - * Sets the linecolor for this LimitLine. Make sure to use - * getResources().getColor(...) - * - * @param color - */ - public void setLineColor(int color) { - mLineColor = color; - } - - /** - * Sets the range color for this LimitRange. Make sure to use - * getResources().getColor(...) - * - * @param color - */ - public void setRangeColor(int color) { - mRangeColor = color; - } - - /** - * Returns the color that is used for this LimitLine - * - * @return - */ - public int getLineColor() { - return mLineColor; - } - - /** - * Returns the color that is used for this LimitRange - * - * @return - */ - public int getRangeColor() { - return mRangeColor; - } - - /** - * Enables the line to be drawn in dashed mode, e.g. like this "- - - - - -" - * - * @param lineLength the length of the line pieces - * @param spaceLength the length of space inbetween the pieces - * @param phase offset, in degrees (normally, use 0) - */ - public void enableDashedLine(float lineLength, float spaceLength, float phase) { - mDashPathEffect = new DashPathEffect(new float[]{ - lineLength, spaceLength - }, phase); - } - - /** - * Disables the line to be drawn in dashed mode. - */ - public void disableDashedLine() { - mDashPathEffect = null; - } - - /** - * Returns true if the dashed-line effect is enabled, false if not. Default: - * disabled - * - * @return - */ - public boolean isDashedLineEnabled() { - return mDashPathEffect == null ? false : true; - } - - /** - * returns the DashPathEffect that is set for this LimitLine - * - * @return - */ - public DashPathEffect getDashPathEffect() { - return mDashPathEffect; - } - - /** - * Sets the color of the value-text that is drawn next to the LimitLine. - * Default: Paint.Style.FILL_AND_STROKE - * - * @param style - */ - public void setTextStyle(Paint.Style style) { - this.mTextStyle = style; - } - - /** - * Returns the color of the value-text that is drawn next to the LimitLine. - * - * @return - */ - public Paint.Style getTextStyle() { - return mTextStyle; - } - - /** - * Sets the position of the LimitLine value label (either on the right or on - * the left edge of the chart). Not supported for RadarChart. - * - * @param pos - */ - public void setLabelPosition(LimitLine.LimitLabelPosition pos) { - mLabelPosition = pos; - } - - /** - * Returns the position of the LimitLine label (value). - * - * @return - */ - public LimitLine.LimitLabelPosition getLabelPosition() { - return mLabelPosition; - } - - /** - * Sets the label that is drawn next to the limit line. Provide "" if no - * label is required. - * - * @param label - */ - public void setLabel(String label) { - mLabel = label; - } - - /** - * Returns the label that is drawn next to the limit line. - * - * @return - */ - public String getLabel() { - return mLabel; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitRange.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitRange.kt new file mode 100644 index 0000000000..3a538869c0 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/LimitRange.kt @@ -0,0 +1,213 @@ +package com.github.mikephil.charting.components + +import android.graphics.Color +import android.graphics.DashPathEffect +import android.graphics.Paint +import com.github.mikephil.charting.components.LimitLine.LimitLabelPosition +import com.github.mikephil.charting.utils.Utils + +/** + * The limit line is an additional feature for all Line-, Bar- and + * ScatterCharts. It allows the displaying of an additional line in the chart + * that marks a certain maximum / limit on the specified axis (x- or y-axis). + */ +class LimitRange : ComponentBase { + class Range internal constructor(r1: Float, r2: Float) { + val low: Float + val high: Float + + init { + if (r1 < r2) { + this.low = r1 + this.high = r2 + } else { + this.low = r2 + this.high = r1 + } + } + } + + /** + * Returns the limit that is set for this line. + * + * @return + */ + /** + * limit / maximum (the y-value or xIndex) + */ + val limit: Range + + /** + * the width of the limit line + */ + private var mLineWidth = 0f + + /** + * Returns the color that is used for this LimitLine + * + * @return + */ + /** + * Sets the linecolor for this LimitLine. Make sure to use + * getResources().getColor(...) + * + * @param color + */ + /** + * the color of the limit line + */ + var lineColor: Int = Color.rgb(237, 91, 91) + + /** + * Returns the color that is used for this LimitRange + * + * @return + */ + /** + * Sets the range color for this LimitRange. Make sure to use + * getResources().getColor(...) + * + * @param color + */ + /** + * the color of the Range + */ + var rangeColor: Int = Color.rgb(128, 128, 128) + + /** + * Returns the color of the value-text that is drawn next to the LimitLine. + * + * @return + */ + /** + * Sets the color of the value-text that is drawn next to the LimitLine. + * Default: Paint.Style.FILL_AND_STROKE + * + * @param style + */ + /** + * the style of the label text + */ + var textStyle: Paint.Style? = Paint.Style.FILL + + /** + * Returns the label that is drawn next to the limit line. + * + * @return + */ + /** + * Sets the label that is drawn next to the limit line. Provide "" if no + * label is required. + * + * @param label + */ + /** + * label string that is drawn next to the limit line + */ + var label: String? = "" + + /** + * returns the DashPathEffect that is set for this LimitLine + * + * @return + */ + /** + * the path effect of this LimitLine that makes dashed lines possible + */ + var dashPathEffect: DashPathEffect? = null + private set + + /** + * Returns the position of the LimitLine label (value). + * + * @return + */ + /** + * Sets the position of the LimitLine value label (either on the right or on + * the left edge of the chart). Not supported for RadarChart. + * + * @param pos + */ + /** + * indicates the position of the LimitLine label + */ + var labelPosition: LimitLabelPosition? = LimitLabelPosition.RIGHT_TOP + + /** + * Constructor with limit. + * + * @param firstLimit - the position (the value) on the y-axis (y-value) or x-axis + * (xIndex) where this line should appear + * @param secondLimit - the position (the value) on the y-axis (y-value) or x-axis + * (xIndex) where this line should appear + */ + constructor(firstLimit: Float, secondLimit: Float) { + this.limit = Range(firstLimit, secondLimit) + } + + /** + * Constructor with limit and label. + * + * @param firstLimit - the position (the value) on the y-axis (y-value) or x-axis + * (xIndex) where this line should appear + * @param secondLimit - the position (the value) on the y-axis (y-value) or x-axis + * (xIndex) where this line should appear + * @param label - provide "" if no label is required + */ + constructor(firstLimit: Float, secondLimit: Float, label: String?) { + this.limit = Range(firstLimit, secondLimit) + this.label = label + } + + var lineWidth: Float + /** + * returns the width of limit line + * + * @return + */ + get() = mLineWidth + /** + * set the line width of the chart (min = 0.2f, max = 12f); default 2f NOTE: + * thinner line == better performance, thicker line == worse performance + * + * @param width + */ + set(width) { + var width = width + if (width > 12.0f) { + width = 12.0f + } + mLineWidth = Utils.convertDpToPixel(width) + } + + /** + * Enables the line to be drawn in dashed mode, e.g. like this "- - - - - -" + * + * @param lineLength the length of the line pieces + * @param spaceLength the length of space inbetween the pieces + * @param phase offset, in degrees (normally, use 0) + */ + fun enableDashedLine(lineLength: Float, spaceLength: Float, phase: Float) { + this.dashPathEffect = DashPathEffect( + floatArrayOf( + lineLength, spaceLength + ), phase + ) + } + + /** + * Disables the line to be drawn in dashed mode. + */ + fun disableDashedLine() { + this.dashPathEffect = null + } + + val isDashedLineEnabled: Boolean + /** + * Returns true if the dashed-line effect is enabled, false if not. Default: + * disabled + * + * @return + */ + get() = this.dashPathEffect != null +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerImage.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerImage.java deleted file mode 100644 index 7bd7b8e6c3..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerImage.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.github.mikephil.charting.components; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.RelativeLayout; - -import com.github.mikephil.charting.charts.Chart; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.FSize; -import com.github.mikephil.charting.utils.MPPointF; - -import java.lang.ref.WeakReference; - -/** - * View that can be displayed when selecting values in the chart. Extend this class to provide custom layouts for your - * markers. - * - * @author Philipp Jahoda - */ -public class MarkerImage implements IMarker { - - private Context mContext; - private Drawable mDrawable; - - private MPPointF mOffset = new MPPointF(); - private MPPointF mOffset2 = new MPPointF(); - private WeakReference mWeakChart; - - private FSize mSize = new FSize(); - private Rect mDrawableBoundsCache = new Rect(); - - /** - * Constructor. Sets up the MarkerView with a custom layout resource. - * - * @param context - * @param drawableResourceId the drawable resource to render - */ - public MarkerImage(Context context, int drawableResourceId) { - mContext = context; - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) - { - mDrawable = mContext.getResources().getDrawable(drawableResourceId, null); - } - else - { - mDrawable = mContext.getResources().getDrawable(drawableResourceId); - } - } - - public void setOffset(MPPointF offset) { - mOffset = offset; - - if (mOffset == null) { - mOffset = new MPPointF(); - } - } - - public void setOffset(float offsetX, float offsetY) { - mOffset.x = offsetX; - mOffset.y = offsetY; - } - - @Override - public MPPointF getOffset() { - return mOffset; - } - - public void setSize(FSize size) { - mSize = size; - - if (mSize == null) { - mSize = new FSize(); - } - } - - public FSize getSize() { - return mSize; - } - - public void setChartView(Chart chart) { - mWeakChart = new WeakReference<>(chart); - } - - public Chart getChartView() { - return mWeakChart == null ? null : mWeakChart.get(); - } - - @Override - public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) { - - MPPointF offset = getOffset(); - mOffset2.x = offset.x; - mOffset2.y = offset.y; - - Chart chart = getChartView(); - - float width = mSize.width; - float height = mSize.height; - - if (width == 0.f && mDrawable != null) { - width = mDrawable.getIntrinsicWidth(); - } - if (height == 0.f && mDrawable != null) { - height = mDrawable.getIntrinsicHeight(); - } - - if (posX + mOffset2.x < 0) { - mOffset2.x = - posX; - } else if (chart != null && posX + width + mOffset2.x > chart.getWidth()) { - mOffset2.x = chart.getWidth() - posX - width; - } - - if (posY + mOffset2.y < 0) { - mOffset2.y = - posY; - } else if (chart != null && posY + height + mOffset2.y > chart.getHeight()) { - mOffset2.y = chart.getHeight() - posY - height; - } - - return mOffset2; - } - - @Override - public void refreshContent(Entry e, Highlight highlight) { - - } - - @Override - public void draw(Canvas canvas, float posX, float posY) { - - if (mDrawable == null) return; - - MPPointF offset = getOffsetForDrawingAtPoint(posX, posY); - - float width = mSize.width; - float height = mSize.height; - - if (width == 0.f) { - width = mDrawable.getIntrinsicWidth(); - } - if (height == 0.f) { - height = mDrawable.getIntrinsicHeight(); - } - - mDrawable.copyBounds(mDrawableBoundsCache); - mDrawable.setBounds( - mDrawableBoundsCache.left, - mDrawableBoundsCache.top, - mDrawableBoundsCache.left + (int)width, - mDrawableBoundsCache.top + (int)height); - - int saveId = canvas.save(); - // translate to the correct position and draw - canvas.translate(posX + offset.x, posY + offset.y); - mDrawable.draw(canvas); - canvas.restoreToCount(saveId); - - mDrawable.setBounds(mDrawableBoundsCache); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerImage.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerImage.kt new file mode 100644 index 0000000000..cc93738068 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerImage.kt @@ -0,0 +1,120 @@ +package com.github.mikephil.charting.components + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.withTranslation +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.FSize +import com.github.mikephil.charting.utils.MPPointF +import java.lang.ref.WeakReference + +/** + * View that can be displayed when selecting values in the chart. Extend this class to provide custom layouts for your + * markers. + * + * @author Philipp Jahoda + */ +class MarkerImage(private var mContext: Context, drawableResourceId: Int) : IMarker { + private var mDrawable: Drawable = + ResourcesCompat.getDrawable(mContext.resources, drawableResourceId, mContext.theme)!! + + private var mOffset: MPPointF = MPPointF() + private val mOffset2 = MPPointF() + private var mWeakChart: WeakReference?>? = null + + private var mSize: FSize = FSize() + private val mDrawableBoundsCache = Rect() + + fun setOffset(offset: MPPointF?) { + mOffset = offset ?: MPPointF() + } + + fun setOffset(offsetX: Float, offsetY: Float) { + mOffset.x = offsetX + mOffset.y = offsetY + } + + override val offset: MPPointF + get() = mOffset + + var size: FSize? + get() = mSize + set(size) { + mSize = size ?: FSize() + } + + var chartView: Chart<*, *, *>? + get() = mWeakChart?.get() + set(chart) { + mWeakChart = WeakReference?>(chart) + } + + override fun getOffsetForDrawingAtPoint(posX: Float, posY: Float): MPPointF { + val offset = offset + mOffset2.x = offset.x + mOffset2.y = offset.y + + val chart = this.chartView + + var width = mSize.width + var height = mSize.height + + if (width == 0f) { + width = mDrawable.intrinsicWidth.toFloat() + } + if (height == 0f) { + height = mDrawable.intrinsicHeight.toFloat() + } + + if (posX + mOffset2.x < 0) { + mOffset2.x = -posX + } else if (chart != null && posX + width + mOffset2.x > chart.width) { + mOffset2.x = chart.width - posX - width + } + + if (posY + mOffset2.y < 0) { + mOffset2.y = -posY + } else if (chart != null && posY + height + mOffset2.y > chart.height) { + mOffset2.y = chart.height - posY - height + } + + return mOffset2 + } + + override fun refreshContent(e: Entry, highlight: Highlight) { + } + + override fun draw(canvas: Canvas?, posX: Float, posY: Float) { + val offset = getOffsetForDrawingAtPoint(posX, posY) + + var width = mSize.width + var height = mSize.height + + if (width == 0f) { + width = mDrawable.intrinsicWidth.toFloat() + } + if (height == 0f) { + height = mDrawable.intrinsicHeight.toFloat() + } + + mDrawable.copyBounds(mDrawableBoundsCache) + mDrawable.setBounds( + mDrawableBoundsCache.left, + mDrawableBoundsCache.top, + mDrawableBoundsCache.left + width.toInt(), + mDrawableBoundsCache.top + height.toInt() + ) + + canvas?.withTranslation(posX + offset.x, posY + offset.y) { + // translate to the correct position and draw + mDrawable.draw(canvas) + } + + mDrawable.bounds = mDrawableBoundsCache + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerView.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerView.java deleted file mode 100644 index 162e88e33c..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerView.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.github.mikephil.charting.components; - -import android.content.Context; -import android.graphics.Canvas; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.RelativeLayout; - -import com.github.mikephil.charting.charts.Chart; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.FSize; -import com.github.mikephil.charting.utils.MPPointF; - -import java.lang.ref.WeakReference; - -/** - * View that can be displayed when selecting values in the chart. Extend this class to provide custom layouts for your - * markers. - * - * @author Philipp Jahoda - */ -public class MarkerView extends RelativeLayout implements IMarker { - - private MPPointF mOffset = new MPPointF(); - private MPPointF mOffset2 = new MPPointF(); - private WeakReference mWeakChart; - - /** - * Constructor. Sets up the MarkerView with a custom layout resource. - * - * @param context - * @param layoutResource the layout resource to use for the MarkerView - */ - public MarkerView(Context context, int layoutResource) { - super(context); - setupLayoutResource(layoutResource); - } - - /** - * Sets the layout resource for a custom MarkerView. - * - * @param layoutResource - */ - private void setupLayoutResource(int layoutResource) { - - View inflated = LayoutInflater.from(getContext()).inflate(layoutResource, this); - - inflated.setLayoutParams(new LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT)); - inflated.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - - // measure(getWidth(), getHeight()); - inflated.layout(0, 0, inflated.getMeasuredWidth(), inflated.getMeasuredHeight()); - } - - public void setOffset(MPPointF offset) { - mOffset = offset; - - if (mOffset == null) { - mOffset = new MPPointF(); - } - } - - public void setOffset(float offsetX, float offsetY) { - mOffset.x = offsetX; - mOffset.y = offsetY; - } - - @Override - public MPPointF getOffset() { - return mOffset; - } - - public void setChartView(Chart chart) { - mWeakChart = new WeakReference<>(chart); - } - - public Chart getChartView() { - return mWeakChart == null ? null : mWeakChart.get(); - } - - @Override - public MPPointF getOffsetForDrawingAtPoint(float posX, float posY) { - - MPPointF offset = getOffset(); - mOffset2.x = offset.x; - mOffset2.y = offset.y; - - Chart chart = getChartView(); - - float width = getWidth(); - float height = getHeight(); - - if (posX + mOffset2.x < 0) { - mOffset2.x = - posX; - } else if (chart != null && posX + width + mOffset2.x > chart.getWidth()) { - mOffset2.x = chart.getWidth() - posX - width; - } - - if (posY + mOffset2.y < 0) { - mOffset2.y = - posY; - } else if (chart != null && posY + height + mOffset2.y > chart.getHeight()) { - mOffset2.y = chart.getHeight() - posY - height; - } - - return mOffset2; - } - - @Override - public void refreshContent(Entry e, Highlight highlight) { - - measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), - MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - layout(0, 0, getMeasuredWidth(), getMeasuredHeight()); - - } - - @Override - public void draw(Canvas canvas, float posX, float posY) { - - MPPointF offset = getOffsetForDrawingAtPoint(posX, posY); - - int saveId = canvas.save(); - // translate to the correct position and draw - canvas.translate(posX + offset.x, posY + offset.y); - draw(canvas); - canvas.restoreToCount(saveId); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerView.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerView.kt new file mode 100644 index 0000000000..d9ff82321e --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/MarkerView.kt @@ -0,0 +1,109 @@ +package com.github.mikephil.charting.components + +import android.content.Context +import android.graphics.Canvas +import android.view.LayoutInflater +import android.widget.RelativeLayout +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF +import java.lang.ref.WeakReference +import androidx.core.graphics.withTranslation + +/** + * View that can be displayed when selecting values in the chart. Extend this class to provide custom layouts for your + * markers. + * + * @author Philipp Jahoda + */ +open class MarkerView(context: Context, layoutResource: Int) : RelativeLayout(context), IMarker { + private var mOffset: MPPointF = MPPointF() + private val mOffset2 = MPPointF() + private var mWeakChart: WeakReference?>? = null + + /** + * Constructor. Sets up the MarkerView with a custom layout resource. + * + * @param context + * @param layoutResource the layout resource to use for the MarkerView + */ + init { + setupLayoutResource(layoutResource) + } + + /** + * Sets the layout resource for a custom MarkerView. + * + * @param layoutResource + */ + private fun setupLayoutResource(layoutResource: Int) { + val inflated = LayoutInflater.from(context).inflate(layoutResource, this) + + inflated.setLayoutParams(LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)) + inflated.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)) + + // measure(getWidth(), getHeight()); + inflated.layout(0, 0, inflated.measuredWidth, inflated.measuredHeight) + } + + fun setOffset(offset: MPPointF?) { + mOffset = offset ?: MPPointF() + } + + fun setOffset(offsetX: Float, offsetY: Float) { + mOffset.x = offsetX + mOffset.y = offsetY + } + + override val offset: MPPointF + get() = mOffset + + var chartView: Chart<*, *, *>? + get() = mWeakChart?.get() + set(chart) { + mWeakChart = WeakReference?>(chart) + } + + override fun getOffsetForDrawingAtPoint(posX: Float, posY: Float): MPPointF { + val offset = offset + mOffset2.x = offset.x + mOffset2.y = offset.y + + val chart = this.chartView + + val width = width.toFloat() + val height = height.toFloat() + + if (posX + mOffset2.x < 0) { + mOffset2.x = -posX + } else if (chart != null && posX + width + mOffset2.x > chart.width) { + mOffset2.x = chart.width - posX - width + } + + if (posY + mOffset2.y < 0) { + mOffset2.y = -posY + } else if (chart != null && posY + height + mOffset2.y > chart.height) { + mOffset2.y = chart.height - posY - height + } + + return mOffset2 + } + + override fun refreshContent(e: Entry, highlight: Highlight) { + measure( + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), + MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) + ) + layout(0, 0, measuredWidth, measuredHeight) + } + + override fun draw(canvas: Canvas?, posX: Float, posY: Float) { + val offset = getOffsetForDrawingAtPoint(posX, posY) + + canvas?.withTranslation(posX + offset.x, posY + offset.y) { + // translate to the correct position and draw + draw(canvas) + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/XAxis.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/XAxis.kt similarity index 62% rename from MPChartLib/src/main/java/com/github/mikephil/charting/components/XAxis.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/components/XAxis.kt index 42ec5a8792..3353501e47 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/XAxis.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/XAxis.kt @@ -1,7 +1,6 @@ +package com.github.mikephil.charting.components -package com.github.mikephil.charting.components; - -import com.github.mikephil.charting.utils.Utils; +import com.github.mikephil.charting.utils.Utils /** * Class representing the x-axis labels settings. Only use the setter methods to @@ -10,79 +9,66 @@ * * @author Philipp Jahoda */ -public class XAxis extends AxisBase { - +class XAxis : AxisBase() { /** * width of the x-axis labels in pixels - this is automatically * calculated by the computeSize() methods in the renderers */ - public int mLabelWidth = 1; + var mLabelWidth: Int = 1 /** * height of the x-axis labels in pixels - this is automatically * calculated by the computeSize() methods in the renderers */ - public int mLabelHeight = 1; + var mLabelHeight: Int = 1 /** - * This is the angle for drawing the X axis labels (in degrees) + * returns the angle for drawing the X axis labels (in degrees) */ - protected float mLabelRotationAngle = 0f; - /** - * if set to true, the chart will avoid that the first and last label entry - * in the chart "clip" off the edge of the chart + * sets the angle for drawing the X axis labels (in degrees) + * + * @param angle the angle in degrees */ - private boolean mAvoidFirstLastClipping = false; - /** - * the position of the x-labels relative to the chart + * This is the angle for drawing the X axis labels (in degrees) */ - private XAxisPosition mPosition = XAxisPosition.TOP; + var labelRotationAngle: Float = 0f /** - * enum for the position of the x-labels relative to the chart + * returns true if avoid-first-lastclipping is enabled, false if not + * + * @return */ - public enum XAxisPosition { - TOP, BOTTOM, BOTH_SIDED, TOP_INSIDE, BOTTOM_INSIDE - } - - public XAxis() { - super(); - - mYOffset = Utils.convertDpToPixel(4.f); // -3 - } + /** + * if set to true, the chart will avoid that the first and last label entry + * in the chart "clip" off the edge of the chart + */ + var isAvoidFirstLastClippingEnabled: Boolean = false + private set /** * returns the position of the x-labels */ - public XAxisPosition getPosition() { - return mPosition; - } - /** * sets the position of the x-labels * * @param pos */ - public void setPosition(XAxisPosition pos) { - mPosition = pos; - } - /** - * returns the angle for drawing the X axis labels (in degrees) + * the position of the x-labels relative to the chart */ - public float getLabelRotationAngle() { - return mLabelRotationAngle; - } + var position: XAxisPosition? = XAxisPosition.TOP /** - * sets the angle for drawing the X axis labels (in degrees) - * - * @param angle the angle in degrees + * enum for the position of the x-labels relative to the chart */ - public void setLabelRotationAngle(float angle) { - mLabelRotationAngle = angle; + enum class XAxisPosition { + TOP, BOTTOM, BOTH_SIDED, TOP_INSIDE, BOTTOM_INSIDE + } + + init { + mYOffset = Utils.convertDpToPixel(4f) // -3 } /** @@ -91,16 +77,7 @@ public void setLabelRotationAngle(float angle) { * * @param enabled */ - public void setAvoidFirstLastClipping(boolean enabled) { - mAvoidFirstLastClipping = enabled; - } - - /** - * returns true if avoid-first-lastclipping is enabled, false if not - * - * @return - */ - public boolean isAvoidFirstLastClippingEnabled() { - return mAvoidFirstLastClipping; + fun setAvoidFirstLastClipping(enabled: Boolean) { + this.isAvoidFirstLastClippingEnabled = enabled } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.java b/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.java deleted file mode 100644 index 872bb721a9..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.java +++ /dev/null @@ -1,467 +0,0 @@ -package com.github.mikephil.charting.components; - -import android.graphics.Color; -import android.graphics.Paint; - -import com.github.mikephil.charting.utils.Utils; - -/** - * Class representing the y-axis labels settings and its entries. Only use the setter methods to - * modify it. Do not - * access public variables directly. Be aware that not all features the YLabels class provides - * are suitable for the - * RadarChart. Customizations that affect the value range of the axis need to be applied before - * setting data for the - * chart. - * - * @author Philipp Jahoda - */ -public class YAxis extends AxisBase { - - /** - * indicates if the bottom y-label entry is drawn or not - */ - private boolean mDrawBottomYLabelEntry = true; - - /** - * indicates if the top y-label entry is drawn or not - */ - private boolean mDrawTopYLabelEntry = true; - - /** - * flag that indicates if the axis is inverted or not - */ - protected boolean mInverted = false; - - /** - * flag that indicates if the zero-line should be drawn regardless of other grid lines - */ - protected boolean mDrawZeroLine = false; - - /** - * flag indicating that auto scale min restriction should be used - */ - private boolean mUseAutoScaleRestrictionMin = false; - - /** - * flag indicating that auto scale max restriction should be used - */ - private boolean mUseAutoScaleRestrictionMax = false; - - /** - * Color of the zero line - */ - protected int mZeroLineColor = Color.GRAY; - - /** - * Width of the zero line in pixels - */ - protected float mZeroLineWidth = 1f; - - /** - * axis space from the largest value to the top in percent of the total axis range - */ - protected float mSpacePercentTop = 10f; - - /** - * axis space from the smallest value to the bottom in percent of the total axis range - */ - protected float mSpacePercentBottom = 10f; - - /** - * the position of the y-labels relative to the chart - */ - private YAxisLabelPosition mPosition = YAxisLabelPosition.OUTSIDE_CHART; - - /** - * the horizontal offset of the y-label - */ - private float mXLabelOffset = 0.0f; - - /** - * enum for the position of the y-labels relative to the chart - */ - public enum YAxisLabelPosition { - OUTSIDE_CHART, INSIDE_CHART - } - - /** - * the side this axis object represents - */ - private AxisDependency mAxisDependency; - - /** - * the minimum width that the axis should take (in dp). - *

- * default: 0.0 - */ - protected float mMinWidth = 0.f; - - /** - * the maximum width that the axis can take (in dp). - * use Inifinity for disabling the maximum - * default: Float.POSITIVE_INFINITY (no maximum specified) - */ - protected float mMaxWidth = Float.POSITIVE_INFINITY; - - /** - * Enum that specifies the axis a DataSet should be plotted against, either LEFT or RIGHT. - * - * @author Philipp Jahoda - */ - public enum AxisDependency { - LEFT, RIGHT - } - - public YAxis() { - super(); - - // default left - this.mAxisDependency = AxisDependency.LEFT; - this.mYOffset = 0f; - } - - public YAxis(AxisDependency position) { - super(); - this.mAxisDependency = position; - this.mYOffset = 0f; - } - - public AxisDependency getAxisDependency() { - return mAxisDependency; - } - - /** - * @return the minimum width that the axis should take (in dp). - */ - public float getMinWidth() { - return mMinWidth; - } - - /** - * Sets the minimum width that the axis should take (in dp). - * - * @param minWidth - */ - public void setMinWidth(float minWidth) { - mMinWidth = minWidth; - } - - /** - * @return the maximum width that the axis can take (in dp). - */ - public float getMaxWidth() { - return mMaxWidth; - } - - /** - * Sets the maximum width that the axis can take (in dp). - * - * @param maxWidth - */ - public void setMaxWidth(float maxWidth) { - mMaxWidth = maxWidth; - } - - /** - * returns the position of the y-labels - */ - public YAxisLabelPosition getLabelPosition() { - return mPosition; - } - - /** - * sets the position of the y-labels - * - * @param pos - */ - public void setPosition(YAxisLabelPosition pos) { - mPosition = pos; - } - - /** - * returns the horizontal offset of the y-label - */ - public float getLabelXOffset() { - return mXLabelOffset; - } - - /** - * sets the horizontal offset of the y-label - * - * @param xOffset - */ - public void setLabelXOffset(float xOffset) { - mXLabelOffset = xOffset; - } - - /** - * returns true if drawing the top y-axis label entry is enabled - * - * @return - */ - public boolean isDrawTopYLabelEntryEnabled() { - return mDrawTopYLabelEntry; - } - - /** - * returns true if drawing the bottom y-axis label entry is enabled - * - * @return - */ - public boolean isDrawBottomYLabelEntryEnabled() { - return mDrawBottomYLabelEntry; - } - - /** - * set this to true to enable drawing the top y-label entry. Disabling this can be helpful - * when the top y-label and - * left x-label interfere with each other. default: true - * - * @param enabled - */ - public void setDrawTopYLabelEntry(boolean enabled) { - mDrawTopYLabelEntry = enabled; - } - - /** - * If this is set to true, the y-axis is inverted which means that low values are on top of - * the chart, high values - * on bottom. - * - * @param enabled - */ - public void setInverted(boolean enabled) { - mInverted = enabled; - } - - /** - * If this returns true, the y-axis is inverted. - * - * @return - */ - public boolean isInverted() { - return mInverted; - } - - /** - * This method is deprecated. - * Use setAxisMinimum(...) / setAxisMaximum(...) instead. - * - * @param startAtZero - */ - @Deprecated - public void setStartAtZero(boolean startAtZero) { - if (startAtZero) - setAxisMinimum(0f); - else - resetAxisMinimum(); - } - - /** - * Sets the top axis space in percent of the full range. Default 10f - * - * @param percent - */ - public void setSpaceTop(float percent) { - mSpacePercentTop = percent; - } - - /** - * Returns the top axis space in percent of the full range. Default 10f - * - * @return - */ - public float getSpaceTop() { - return mSpacePercentTop; - } - - /** - * Sets the bottom axis space in percent of the full range. Default 10f - * - * @param percent - */ - public void setSpaceBottom(float percent) { - mSpacePercentBottom = percent; - } - - /** - * Returns the bottom axis space in percent of the full range. Default 10f - * - * @return - */ - public float getSpaceBottom() { - return mSpacePercentBottom; - } - - public boolean isDrawZeroLineEnabled() { - return mDrawZeroLine; - } - - /** - * Set this to true to draw the zero-line regardless of weather other - * grid-lines are enabled or not. Default: false - * - * @param mDrawZeroLine - */ - public void setDrawZeroLine(boolean mDrawZeroLine) { - this.mDrawZeroLine = mDrawZeroLine; - } - - public int getZeroLineColor() { - return mZeroLineColor; - } - - /** - * Sets the color of the zero line - * - * @param color - */ - public void setZeroLineColor(int color) { - mZeroLineColor = color; - } - - public float getZeroLineWidth() { - return mZeroLineWidth; - } - - /** - * Sets the width of the zero line in dp - * - * @param width - */ - public void setZeroLineWidth(float width) { - this.mZeroLineWidth = Utils.convertDpToPixel(width); - } - - /** - * This is for normal (not horizontal) charts horizontal spacing. - * - * @param p - * @return - */ - public float getRequiredWidthSpace(Paint p) { - - p.setTextSize(mTextSize); - - String label = getLongestLabel(p); - float width = (float) Utils.calcTextWidth(p, label) + getXOffset() * 2f; - - float minWidth = getMinWidth(); - float maxWidth = getMaxWidth(); - - if (minWidth > 0.f) - minWidth = Utils.convertDpToPixel(minWidth); - - if (maxWidth > 0.f && maxWidth != Float.POSITIVE_INFINITY) - maxWidth = Utils.convertDpToPixel(maxWidth); - - width = Math.max(minWidth, Math.min(width, maxWidth > 0.0 ? maxWidth : width)); - - return width; - } - - /** - * This is for HorizontalBarChart vertical spacing. - * - * @param p - * @return - */ - public float getRequiredHeightSpace(Paint p) { - - p.setTextSize(mTextSize); - - String label = getLongestLabel(p); - return (float) Utils.calcTextHeight(p, label) + getYOffset() * 2f; - } - - /** - * Returns true if this axis needs horizontal offset, false if no offset is needed. - * - * @return - */ - public boolean needsOffset() { - if (isEnabled() && isDrawLabelsEnabled() && getLabelPosition() == YAxisLabelPosition - .OUTSIDE_CHART) - return true; - else - return false; - } - - /** - * Returns true if autoscale restriction for axis min value is enabled - */ - @Deprecated - public boolean isUseAutoScaleMinRestriction( ) { - return mUseAutoScaleRestrictionMin; - } - - /** - * Sets autoscale restriction for axis min value as enabled/disabled - */ - @Deprecated - public void setUseAutoScaleMinRestriction( boolean isEnabled ) { - mUseAutoScaleRestrictionMin = isEnabled; - } - - /** - * Returns true if autoscale restriction for axis max value is enabled - */ - @Deprecated - public boolean isUseAutoScaleMaxRestriction() { - return mUseAutoScaleRestrictionMax; - } - - /** - * Sets autoscale restriction for axis max value as enabled/disabled - */ - @Deprecated - public void setUseAutoScaleMaxRestriction( boolean isEnabled ) { - mUseAutoScaleRestrictionMax = isEnabled; - } - - - @Override - public void calculate(float dataMin, float dataMax) { - - float min = dataMin; - float max = dataMax; - - // Make sure max is greater than min - // Discussion: https://github.com/danielgindi/Charts/pull/3650#discussion_r221409991 - if (min > max) - { - if (mCustomAxisMax && mCustomAxisMin) - { - float t = min; - min = max; - max = t; - } - else if (mCustomAxisMax) - { - min = max < 0f ? max * 1.5f : max * 0.5f; - } - else if (mCustomAxisMin) - { - max = min < 0f ? min * 0.5f : min * 1.5f; - } - } - - float range = Math.abs(max - min); - - // in case all values are equal - if (range == 0f) { - max = max + 1f; - min = min - 1f; - } - - // recalculate - range = Math.abs(max - min); - - // calc extra spacing - this.mAxisMinimum = mCustomAxisMin ? this.mAxisMinimum : min - (range / 100f) * getSpaceBottom(); - this.mAxisMaximum = mCustomAxisMax ? this.mAxisMaximum : max + (range / 100f) * getSpaceTop(); - - this.mAxisRange = Math.abs(this.mAxisMinimum - this.mAxisMaximum); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.kt new file mode 100644 index 0000000000..76531625a6 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/components/YAxis.kt @@ -0,0 +1,357 @@ +package com.github.mikephil.charting.components + +import android.graphics.Color +import android.graphics.Paint +import com.github.mikephil.charting.utils.Utils +import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min + +/** + * Class representing the y-axis labels settings and its entries. Only use the setter methods to + * modify it. Do not + * access public variables directly. Be aware that not all features the YLabels class provides + * are suitable for the + * RadarChart. Customizations that affect the value range of the axis need to be applied before + * setting data for the + * chart. + * + * @author Philipp Jahoda + */ +open class YAxis : AxisBase { + /** + * returns true if drawing the bottom y-axis label entry is enabled + * + * @return + */ + /** + * indicates if the bottom y-label entry is drawn or not + */ + val isDrawBottomYLabelEntryEnabled: Boolean = true + + /** + * returns true if drawing the top y-axis label entry is enabled + * + * @return + */ + /** + * indicates if the top y-label entry is drawn or not + */ + var isDrawTopYLabelEntryEnabled: Boolean = true + private set + + /** + * If this returns true, the y-axis is inverted. + * + * @return + */ + /** + * If this is set to true, the y-axis is inverted which means that low values are on top of + * the chart, high values + * on bottom. + * + * @param enabled + */ + /** + * flag that indicates if the axis is inverted or not + */ + var isInverted: Boolean = false + + /** + * flag that indicates if the zero-line should be drawn regardless of other grid lines + */ + var isDrawZeroLineEnabled: Boolean = false + protected set + + /** + * Returns true if autoscale restriction for axis min value is enabled + */ + /** + * Sets autoscale restriction for axis min value as enabled/disabled + */ + /** + * flag indicating that auto scale min restriction should be used + */ + @get:Deprecated("") + @set:Deprecated("") + var isUseAutoScaleMinRestriction: Boolean = false + + /** + * Returns true if autoscale restriction for axis max value is enabled + */ + /** + * Sets autoscale restriction for axis max value as enabled/disabled + */ + /** + * flag indicating that auto scale max restriction should be used + */ + @get:Deprecated("") + @set:Deprecated("") + var isUseAutoScaleMaxRestriction: Boolean = false + + /** + * Sets the color of the zero line + * + * @param color + */ + /** + * Color of the zero line + */ + var zeroLineColor: Int = Color.GRAY + + /** + * Width of the zero line in pixels + */ + protected var mZeroLineWidth: Float = 1f + + /** + * Returns the top axis space in percent of the full range. Default 10f + * + * @return + */ + /** + * Sets the top axis space in percent of the full range. Default 10f + * + * @param percent + */ + /** + * axis space from the largest value to the top in percent of the total axis range + */ + var spaceTop: Float = 10f + + /** + * Returns the bottom axis space in percent of the full range. Default 10f + * + * @return + */ + /** + * Sets the bottom axis space in percent of the full range. Default 10f + * + * @param percent + */ + /** + * axis space from the smallest value to the bottom in percent of the total axis range + */ + var spaceBottom: Float = 10f + + /** + * returns the position of the y-labels + */ + /** + * the position of the y-labels relative to the chart + */ + var labelPosition: YAxisLabelPosition = YAxisLabelPosition.OUTSIDE_CHART + private set + + /** + * returns the horizontal offset of the y-label + */ + /** + * sets the horizontal offset of the y-label + * + * @param xOffset + */ + /** + * the horizontal offset of the y-label + */ + var labelXOffset: Float = 0.0f + + /** + * enum for the position of the y-labels relative to the chart + */ + enum class YAxisLabelPosition { + OUTSIDE_CHART, INSIDE_CHART + } + + /** + * the side this axis object represents + */ + val axisDependency: AxisDependency + + /** + * @return the minimum width that the axis should take (in dp). + */ + /** + * Sets the minimum width that the axis should take (in dp). + * + * @param minWidth + */ + /** + * the minimum width that the axis should take (in dp). + * + * + * default: 0.0 + */ + var minWidth: Float = 0f + + /** + * @return the maximum width that the axis can take (in dp). + */ + /** + * Sets the maximum width that the axis can take (in dp). + * + * @param maxWidth + */ + /** + * the maximum width that the axis can take (in dp). + * use Inifinity for disabling the maximum + * default: Float.POSITIVE_INFINITY (no maximum specified) + */ + var maxWidth: Float = Float.Companion.POSITIVE_INFINITY + + /** + * Enum that specifies the axis a DataSet should be plotted against, either LEFT or RIGHT. + * + * @author Philipp Jahoda + */ + enum class AxisDependency { + LEFT, RIGHT + } + + constructor() : super() { + // default left + this.axisDependency = AxisDependency.LEFT + this.mYOffset = 0f + } + + constructor(position: AxisDependency) : super() { + this.axisDependency = position + this.mYOffset = 0f + } + + /** + * sets the position of the y-labels + * + * @param pos + */ + fun setPosition(pos: YAxisLabelPosition) { + this.labelPosition = pos + } + + /** + * set this to true to enable drawing the top y-label entry. Disabling this can be helpful + * when the top y-label and + * left x-label interfere with each other. default: true + * + * @param enabled + */ + fun setDrawTopYLabelEntry(enabled: Boolean) { + this.isDrawTopYLabelEntryEnabled = enabled + } + + /** + * This method is deprecated. + * Use setAxisMinimum(...) / setAxisMaximum(...) instead. + * + * @param startAtZero + */ + @Deprecated("") + fun setStartAtZero(startAtZero: Boolean) { + if (startAtZero) axisMinimum = 0f + else resetAxisMinimum() + } + + /** + * Set this to true to draw the zero-line regardless of weather other + * grid-lines are enabled or not. Default: false + * + * @param mDrawZeroLine + */ + fun setDrawZeroLine(mDrawZeroLine: Boolean) { + this.isDrawZeroLineEnabled = mDrawZeroLine + } + + var zeroLineWidth: Float + get() = mZeroLineWidth + /** + * Sets the width of the zero line in dp + * + * @param width + */ + set(width) { + this.mZeroLineWidth = Utils.convertDpToPixel(width) + } + + /** + * This is for normal (not horizontal) charts horizontal spacing. + * + * @param p + * @return + */ + fun getRequiredWidthSpace(p: Paint): Float { + p.textSize = mTextSize + + val label = getLongestLabel(p) + var width = Utils.calcTextWidth(p, label).toFloat() + xOffset * 2f + + var minWidth = this.minWidth + var maxWidth = this.maxWidth + + if (minWidth > 0f) minWidth = Utils.convertDpToPixel(minWidth) + + if (maxWidth > 0f && maxWidth != Float.Companion.POSITIVE_INFINITY) maxWidth = Utils.convertDpToPixel(maxWidth) + + width = max(minWidth, min(width, if (maxWidth > 0.0) maxWidth else width)) + + return width + } + + /** + * This is for HorizontalBarChart vertical spacing. + * + * @param p + * @return + */ + fun getRequiredHeightSpace(p: Paint): Float { + p.textSize = mTextSize + + val label = getLongestLabel(p) + return Utils.calcTextHeight(p, label).toFloat() + yOffset * 2f + } + + /** + * Returns true if this axis needs horizontal offset, false if no offset is needed. + * + * @return + */ + fun needsOffset(): Boolean { + return isEnabled && isDrawLabelsEnabled && this.labelPosition == YAxisLabelPosition.OUTSIDE_CHART + } + + + override fun calculate(dataMin: Float, dataMax: Float) { + var min = dataMin + var max = dataMax + + // Make sure max is greater than min + // Discussion: https://github.com/danielgindi/Charts/pull/3650#discussion_r221409991 + if (min > max) { + if (isAxisMaxCustom && isAxisMinCustom) { + val t = min + min = max + max = t + } else if (isAxisMaxCustom) { + min = if (max < 0f) max * 1.5f else max * 0.5f + } else if (isAxisMinCustom) { + max = if (min < 0f) min * 0.5f else min * 1.5f + } + } + + var range = abs(max - min) + + // in case all values are equal + if (range == 0f) { + max = max + 1f + min = min - 1f + } + + // recalculate + range = abs(max - min) + + // calc extra spacing + this.mAxisMinimum = if (isAxisMinCustom) this.mAxisMinimum else min - (range / 100f) * this.spaceBottom + this.mAxisMaximum = if (isAxisMaxCustom) this.mAxisMaximum else max + (range / 100f) * this.spaceTop + + this.mAxisRange = abs(this.mAxisMinimum - this.mAxisMaximum) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.java deleted file mode 100644 index 16d60f6f9c..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.java +++ /dev/null @@ -1,119 +0,0 @@ - -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; - -import java.util.List; - -/** - * Data object that represents all data for the BarChart. - * - * @author Philipp Jahoda - */ -public class BarData extends BarLineScatterCandleBubbleData { - - /** - * the width of the bars on the x-axis, in values (not pixels) - */ - private float mBarWidth = 0.85f; - - public BarData() { - super(); - } - - public BarData(IBarDataSet... dataSets) { - super(dataSets); - } - - public BarData(List dataSets) { - super(dataSets); - } - - /** - * Sets the width each bar should have on the x-axis (in values, not pixels). - * Default 0.85f - * - * @param mBarWidth - */ - public void setBarWidth(float mBarWidth) { - this.mBarWidth = mBarWidth; - } - - public float getBarWidth() { - return mBarWidth; - } - - /** - * Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries. - * Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified - * by the parameters. - * Do not forget to call notifyDataSetChanged() on your BarChart object after calling this method. - * - * @param fromX the starting point on the x-axis where the grouping should begin - * @param groupSpace the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f - * @param barSpace the space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f - */ - public void groupBars(float fromX, float groupSpace, float barSpace) { - - int setCount = mDataSets.size(); - if (setCount <= 1) { - throw new RuntimeException("BarData needs to hold at least 2 BarDataSets to allow grouping."); - } - - IBarDataSet max = getMaxEntryCountSet(); - int maxEntryCount = max.getEntryCount(); - - float groupSpaceWidthHalf = groupSpace / 2f; - float barSpaceHalf = barSpace / 2f; - float barWidthHalf = mBarWidth / 2f; - - float interval = getGroupWidth(groupSpace, barSpace); - - for (int i = 0; i < maxEntryCount; i++) { - - float start = fromX; - fromX += groupSpaceWidthHalf; - - for (IBarDataSet set : mDataSets) { - - fromX += barSpaceHalf; - fromX += barWidthHalf; - - if (i < set.getEntryCount()) { - - BarEntry entry = set.getEntryForIndex(i); - - if (entry != null) { - entry.setX(fromX); - } - } - - fromX += barWidthHalf; - fromX += barSpaceHalf; - } - - fromX += groupSpaceWidthHalf; - float end = fromX; - float innerInterval = end - start; - float diff = interval - innerInterval; - - // correct rounding errors - if (diff > 0 || diff < 0) { - fromX += diff; - } - } - - notifyDataChanged(); - } - - /** - * In case of grouped bars, this method returns the space an individual group of bar needs on the x-axis. - * - * @param groupSpace - * @param barSpace - * @return - */ - public float getGroupWidth(float groupSpace, float barSpace) { - return mDataSets.size() * (mBarWidth + barSpace) + groupSpace; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.kt new file mode 100644 index 0000000000..7e401c9e65 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.kt @@ -0,0 +1,96 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet + +/** + * Data object that represents all data for the BarChart. + * + * @author Philipp Jahoda + */ +class BarData : BarLineScatterCandleBubbleData { + /** + * Sets the width each bar should have on the x-axis (in values, not pixels). + * Default 0.85f + * + * @param this.barWidth + */ + /** + * the width of the bars on the x-axis, in values (not pixels) + */ + var barWidth: Float = 0.85f + + constructor() : super() + + constructor(vararg dataSets: IBarDataSet) : super(*dataSets) + + constructor(dataSets: MutableList) : super(dataSets) + + /** + * Groups all BarDataSet objects this data object holds together by modifying the x-value of their entries. + * Previously set x-values of entries will be overwritten. Leaves space between bars and groups as specified + * by the parameters. + * Do not forget to call notifyDataSetChanged() on your BarChart object after calling this method. + * + * @param fromX the starting point on the x-axis where the grouping should begin + * @param groupSpace the space between groups of bars in values (not pixels) e.g. 0.8f for bar width 1f + * @param barSpace the space between individual bars in values (not pixels) e.g. 0.1f for bar width 1f + */ + fun groupBars(fromX: Float, groupSpace: Float, barSpace: Float) { + var fromX = fromX + val setCount = dataSets.size + if (setCount <= 1) { + throw RuntimeException("BarData needs to hold at least 2 BarDataSets to allow grouping.") + } + + val max = maxEntryCountSet ?: return + val maxEntryCount = max.entryCount + + val groupSpaceWidthHalf = groupSpace / 2f + val barSpaceHalf = barSpace / 2f + val barWidthHalf = this.barWidth / 2f + + val interval = getGroupWidth(groupSpace, barSpace) + + for (i in 0.. 0 || diff < 0) { + fromX += diff + } + } + + notifyDataChanged() + } + + /** + * In case of grouped bars, this method returns the space an individual group of bar needs on the x-axis. + * + * @param groupSpace + * @param barSpace + * @return + */ + fun getGroupWidth(groupSpace: Float, barSpace: Float): Float { + return dataSets.size * (this.barWidth + barSpace) + groupSpace + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.java deleted file mode 100644 index 3f2c66dbe3..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.java +++ /dev/null @@ -1,299 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.graphics.Color; - -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.utils.Fill; - -import java.util.ArrayList; -import java.util.List; - -public class BarDataSet extends BarLineScatterCandleBubbleDataSet implements IBarDataSet { - - /** - * the maximum number of bars that are stacked upon each other, this value - * is calculated from the Entries that are added to the DataSet - */ - private int mStackSize = 1; - - /** - * the color used for drawing the bar shadows - */ - private int mBarShadowColor = Color.rgb(215, 215, 215); - - private float mBarBorderWidth = 0.0f; - - private int mBarBorderColor = Color.BLACK; - - /** - * the alpha value used to draw the highlight indicator bar - */ - private int mHighLightAlpha = 120; - - /** - * the overall entry count, including counting each stack-value individually - */ - private int mEntryCountStacks = 0; - - /** - * array of labels used to describe the different values of the stacked bars - */ - private String[] mStackLabels = new String[]{}; - - protected List mFills = null; - - public BarDataSet(List yVals, String label) { - super(yVals, label); - - mHighLightColor = Color.rgb(0, 0, 0); - - calcStackSize(yVals); - calcEntryCountIncludingStacks(yVals); - } - - @Override - public DataSet copy() { - List entries = new ArrayList(); - for (int i = 0; i < mEntries.size(); i++) { - entries.add(mEntries.get(i).copy()); - } - BarDataSet copied = new BarDataSet(entries, getLabel()); - copy(copied); - return copied; - } - - protected void copy(BarDataSet barDataSet) { - super.copy((BaseDataSet) barDataSet); - barDataSet.mStackSize = mStackSize; - barDataSet.mBarShadowColor = mBarShadowColor; - barDataSet.mBarBorderWidth = mBarBorderWidth; - barDataSet.mStackLabels = mStackLabels; - barDataSet.mHighLightAlpha = mHighLightAlpha; - } - - @Override - public List getFills() { - return mFills; - } - - @Override - public Fill getFill(int index) { - return mFills.get(index % mFills.size()); - } - - /** - * This method is deprecated. - * Use getFills() instead. - */ - @Deprecated - public List getGradients() { - return mFills; - } - - /** - * This method is deprecated. - * Use getFill(...) instead. - * - * @param index - */ - @Deprecated - public Fill getGradient(int index) { - return getFill(index); - } - - /** - * Sets the start and end color for gradient color, ONLY color that should be used for this DataSet. - * - * @param startColor - * @param endColor - */ - public void setGradientColor(int startColor, int endColor) { - mFills.clear(); - mFills.add(new Fill(startColor, endColor)); - } - - /** - * This method is deprecated. - * Use setFills(...) instead. - * - * @param gradientColors - */ - @Deprecated - public void setGradientColors(List gradientColors) { - this.mFills = gradientColors; - } - - /** - * Sets the fills for the bars in this dataset. - * - * @param fills - */ - public void setFills(List fills) { - this.mFills = fills; - } - - /** - * Calculates the total number of entries this DataSet represents, including - * stacks. All values belonging to a stack are calculated separately. - */ - private void calcEntryCountIncludingStacks(List yVals) { - - mEntryCountStacks = 0; - - for (int i = 0; i < yVals.size(); i++) { - - float[] vals = yVals.get(i).getYVals(); - - if (vals == null) - mEntryCountStacks++; - else - mEntryCountStacks += vals.length; - } - } - - /** - * calculates the maximum stacksize that occurs in the Entries array of this - * DataSet - */ - private void calcStackSize(List yVals) { - - for (int i = 0; i < yVals.size(); i++) { - - float[] vals = yVals.get(i).getYVals(); - - if (vals != null && vals.length > mStackSize) - mStackSize = vals.length; - } - } - - @Override - protected void calcMinMax(BarEntry e) { - - if (e != null && !Float.isNaN(e.getY())) { - - if (e.getYVals() == null) { - - if (e.getY() < mYMin) - mYMin = e.getY(); - - if (e.getY() > mYMax) - mYMax = e.getY(); - } else { - - if (-e.getNegativeSum() < mYMin) - mYMin = -e.getNegativeSum(); - - if (e.getPositiveSum() > mYMax) - mYMax = e.getPositiveSum(); - } - - calcMinMaxX(e); - } - } - - @Override - public int getStackSize() { - return mStackSize; - } - - @Override - public boolean isStacked() { - return mStackSize > 1 ? true : false; - } - - /** - * returns the overall entry count, including counting each stack-value - * individually - * - * @return - */ - public int getEntryCountStacks() { - return mEntryCountStacks; - } - - /** - * Sets the color used for drawing the bar-shadows. The bar shadows is a - * surface behind the bar that indicates the maximum value. Don't for get to - * use getResources().getColor(...) to set this. Or Color.rgb(...). - * - * @param color - */ - public void setBarShadowColor(int color) { - mBarShadowColor = color; - } - - @Override - public int getBarShadowColor() { - return mBarShadowColor; - } - - /** - * Sets the width used for drawing borders around the bars. - * If borderWidth == 0, no border will be drawn. - * - * @return - */ - public void setBarBorderWidth(float width) { - mBarBorderWidth = width; - } - - /** - * Returns the width used for drawing borders around the bars. - * If borderWidth == 0, no border will be drawn. - * - * @return - */ - @Override - public float getBarBorderWidth() { - return mBarBorderWidth; - } - - /** - * Sets the color drawing borders around the bars. - * - * @return - */ - public void setBarBorderColor(int color) { - mBarBorderColor = color; - } - - /** - * Returns the color drawing borders around the bars. - * - * @return - */ - @Override - public int getBarBorderColor() { - return mBarBorderColor; - } - - /** - * Set the alpha value (transparency) that is used for drawing the highlight - * indicator bar. min = 0 (fully transparent), max = 255 (fully opaque) - * - * @param alpha - */ - public void setHighLightAlpha(int alpha) { - mHighLightAlpha = alpha; - } - - @Override - public int getHighLightAlpha() { - return mHighLightAlpha; - } - - /** - * Sets labels for different values of bar-stacks, in case there are one. - * - * @param labels - */ - public void setStackLabels(String[] labels) { - mStackLabels = labels; - } - - @Override - public String[] getStackLabels() { - return mStackLabels; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.kt new file mode 100644 index 0000000000..aa96c560cc --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarDataSet.kt @@ -0,0 +1,258 @@ +package com.github.mikephil.charting.data + +import android.graphics.Color +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.utils.Fill +import kotlin.Array +import kotlin.Boolean +import kotlin.Deprecated +import kotlin.Int +import kotlin.String +import kotlin.arrayOf + +open class BarDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), IBarDataSet { + /** + * the maximum number of bars that are stacked upon each other, this value + * is calculated from the Entries that are added to the DataSet + */ + private var mStackSize = 1 + + /** + * the color used for drawing the bar shadows + */ + private var mBarShadowColor = Color.rgb(215, 215, 215) + + private var mBarBorderWidth = 0.0f + + private var mBarBorderColor = Color.BLACK + + /** + * the alpha value used to draw the highlight indicator bar + */ + private var mHighLightAlpha = 120 + + /** + * returns the overall entry count, including counting each stack-value + * individually + * + * @return + */ + /** + * the overall entry count, including counting each stack-value individually + */ + var entryCountStacks: Int = 0 + private set + + /** + * array of labels used to describe the different values of the stacked bars + */ + private var mStackLabels: Array? = arrayOf() + + /** + * This method is deprecated. + * Use getFills() instead. + */ + @get:Deprecated("") + val gradients: MutableList = mutableListOf() + + init { + highLightColor = Color.rgb(0, 0, 0) + + calcStackSize(yVals) + calcEntryCountIncludingStacks(yVals) + } + + override fun copy(): DataSet { + val entries: MutableList = ArrayList() + for (i in mEntries.indices) { + entries.add(mEntries[i].copy()) + } + val copied = BarDataSet(entries, label) + copy(copied) + return copied + } + + protected fun copy(barDataSet: BarDataSet) { + super.copy(barDataSet) + barDataSet.mStackSize = mStackSize + barDataSet.mBarShadowColor = mBarShadowColor + barDataSet.mBarBorderWidth = mBarBorderWidth + barDataSet.mStackLabels = mStackLabels + barDataSet.mHighLightAlpha = mHighLightAlpha + } + + override val fills: MutableList + get() = gradients + + override fun getFill(index: Int): Fill { + return gradients[index % gradients.size] + } + + /** + * This method is deprecated. + * Use getFill(...) instead. + * + * @param index + */ + @Deprecated("") + fun getGradient(index: Int): Fill { + return getFill(index) + } + + /** + * Sets the start and end color for gradient color, ONLY color that should be used for this DataSet. + * + * @param startColor + * @param endColor + */ + fun setGradientColor(startColor: Int, endColor: Int) { + gradients.clear() + gradients.add(Fill(startColor, endColor)) + } + + /** + * This method is deprecated. + * Use setFills(...) instead. + * + * @param gradientColors + */ + @Deprecated("") + fun setGradientColors(gradientColors: MutableList) { + this.gradients.clear() + this.gradients.addAll(gradientColors) + } + + /** + * Sets the fills for the bars in this dataset. + * + * @param fills + */ + fun setFills(fills: MutableList) { + this.gradients.clear() + this.gradients.addAll(fills) + } + + /** + * Calculates the total number of entries this DataSet represents, including + * stacks. All values belonging to a stack are calculated separately. + */ + private fun calcEntryCountIncludingStacks(yVals: MutableList) { + this.entryCountStacks = 0 + + for (i in yVals.indices) { + val vals = yVals[i].yVals + + if (vals == null) this.entryCountStacks++ + else this.entryCountStacks += vals.size + } + } + + /** + * calculates the maximum stacksize that occurs in the Entries array of this + * DataSet + */ + private fun calcStackSize(yVals: MutableList) { + for (i in yVals.indices) { + val vals = yVals[i].yVals + + if (vals != null && vals.size > mStackSize) mStackSize = vals.size + } + } + + override fun calcMinMax(e: BarEntry) { + if (!e.y.isNaN()) { + if (e.yVals == null) { + if (e.y < mYMin) mYMin = e.y + + if (e.y > mYMax) mYMax = e.y + } else { + if (-e.negativeSum < mYMin) mYMin = -e.negativeSum + + if (e.positiveSum > mYMax) mYMax = e.positiveSum + } + + calcMinMaxX(e) + } + } + + override val stackSize: Int + get() = mStackSize + + override val isStacked: Boolean + get() = mStackSize > 1 + + /** + * Sets the color used for drawing the bar-shadows. The bar shadows is a + * surface behind the bar that indicates the maximum value. Don't for get to + * use getResources().getColor(...) to set this. Or Color.rgb(...). + * + * @param color + */ + fun setBarShadowColor(color: Int) { + mBarShadowColor = color + } + + override val barShadowColor: Int + get() = mBarShadowColor + + /** + * Sets the width used for drawing borders around the bars. + * If borderWidth == 0, no border will be drawn. + * + * @return + */ + fun setBarBorderWidth(width: Float) { + mBarBorderWidth = width + } + + /** + * Returns the width used for drawing borders around the bars. + * If borderWidth == 0, no border will be drawn. + * + * @return + */ + override val barBorderWidth: Float + get() = mBarBorderWidth + + /** + * Sets the color drawing borders around the bars. + * + * @return + */ + fun setBarBorderColor(color: Int) { + mBarBorderColor = color + } + + /** + * Returns the color drawing borders around the bars. + * + * @return + */ + override val barBorderColor: Int + get() = mBarBorderColor + + /** + * Set the alpha value (transparency) that is used for drawing the highlight + * indicator bar. min = 0 (fully transparent), max = 255 (fully opaque) + * + * @param alpha + */ + fun setHighLightAlpha(alpha: Int) { + mHighLightAlpha = alpha + } + + override val highLightAlpha: Int + get() = mHighLightAlpha + + /** + * Sets labels for different values of bar-stacks, in case there are one. + * + * @param labels + */ + fun setStackLabels(labels: Array?) { + mStackLabels = labels + } + + override val stackLabels: Array? + get() = mStackLabels +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarEntry.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarEntry.java deleted file mode 100644 index 365ef51a2d..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarEntry.java +++ /dev/null @@ -1,310 +0,0 @@ -package com.github.mikephil.charting.data; - -import android.annotation.SuppressLint; -import android.graphics.drawable.Drawable; - -import com.github.mikephil.charting.highlight.Range; - -/** - * Entry class for the BarChart. (especially stacked bars) - * - * @author Philipp Jahoda - */ -@SuppressLint("ParcelCreator") -public class BarEntry extends Entry { - - /** - * the values the stacked barchart holds - */ - private float[] mYVals; - - /** - * the ranges for the individual stack values - automatically calculated - */ - private Range[] mRanges; - - /** - * the sum of all negative values this entry (if stacked) contains - */ - private float mNegativeSum; - - /** - * the sum of all positive values this entry (if stacked) contains - */ - private float mPositiveSum; - - /** - * Constructor for normal bars (not stacked). - * - * @param x - * @param y - */ - public BarEntry(float x, float y) { - super(x, y); - } - - /** - * Constructor for normal bars (not stacked). - * - * @param x - * @param y - * @param data - Spot for additional data this Entry represents. - */ - public BarEntry(float x, float y, Object data) { - super(x, y, data); - } - - /** - * Constructor for normal bars (not stacked). - * - * @param x - * @param y - * @param icon - icon image - */ - public BarEntry(float x, float y, Drawable icon) { - super(x, y, icon); - } - - /** - * Constructor for normal bars (not stacked). - * - * @param x - * @param y - * @param icon - icon image - * @param data - Spot for additional data this Entry represents. - */ - public BarEntry(float x, float y, Drawable icon, Object data) { - super(x, y, icon, data); - } - - /** - * Constructor for stacked bar entries. One data object for whole stack - * - * @param x - * @param vals - the stack values, use at least 2 - */ - public BarEntry(float x, float[] vals) { - super(x, calcSum(vals)); - - this.mYVals = vals; - calcPosNegSum(); - calcRanges(); - } - - /** - * Constructor for stacked bar entries. One data object for whole stack - * - * @param x - * @param vals - the stack values, use at least 2 - * @param data - Spot for additional data this Entry represents. - */ - public BarEntry(float x, float[] vals, Object data) { - super(x, calcSum(vals), data); - - this.mYVals = vals; - calcPosNegSum(); - calcRanges(); - } - - /** - * Constructor for stacked bar entries. One data object for whole stack - * - * @param x - * @param vals - the stack values, use at least 2 - * @param icon - icon image - */ - public BarEntry(float x, float[] vals, Drawable icon) { - super(x, calcSum(vals), icon); - - this.mYVals = vals; - calcPosNegSum(); - calcRanges(); - } - - /** - * Constructor for stacked bar entries. One data object for whole stack - * - * @param x - * @param vals - the stack values, use at least 2 - * @param icon - icon image - * @param data - Spot for additional data this Entry represents. - */ - public BarEntry(float x, float[] vals, Drawable icon, Object data) { - super(x, calcSum(vals), icon, data); - - this.mYVals = vals; - calcPosNegSum(); - calcRanges(); - } - - /** - * Returns an exact copy of the BarEntry. - */ - public BarEntry copy() { - - BarEntry copied = new BarEntry(getX(), getY(), getData()); - copied.setVals(mYVals); - return copied; - } - - /** - * Returns the stacked values this BarEntry represents, or null, if only a single value is represented (then, use - * getY()). - * - * @return - */ - public float[] getYVals() { - return mYVals; - } - - /** - * Set the array of values this BarEntry should represent. - * - * @param vals - */ - public void setVals(float[] vals) { - setY(calcSum(vals)); - mYVals = vals; - calcPosNegSum(); - calcRanges(); - } - - /** - * Returns the value of this BarEntry. If the entry is stacked, it returns the positive sum of all values. - * - * @return - */ - @Override - public float getY() { - return super.getY(); - } - - /** - * Returns the ranges of the individual stack-entries. Will return null if this entry is not stacked. - * - * @return - */ - public Range[] getRanges() { - return mRanges; - } - - /** - * Returns true if this BarEntry is stacked (has a values array), false if not. - * - * @return - */ - public boolean isStacked() { - return mYVals != null; - } - - /** - * Use `getSumBelow(stackIndex)` instead. - */ - @Deprecated - public float getBelowSum(int stackIndex) { - return getSumBelow(stackIndex); - } - - public float getSumBelow(int stackIndex) { - - if (mYVals == null) - return 0; - - float remainder = 0f; - int index = mYVals.length - 1; - - while (index > stackIndex && index >= 0) { - remainder += mYVals[index]; - index--; - } - - return remainder; - } - - /** - * Reuturns the sum of all positive values this entry (if stacked) contains. - * - * @return - */ - public float getPositiveSum() { - return mPositiveSum; - } - - /** - * Returns the sum of all negative values this entry (if stacked) contains. (this is a positive number) - * - * @return - */ - public float getNegativeSum() { - return mNegativeSum; - } - - private void calcPosNegSum() { - - if (mYVals == null) { - mNegativeSum = 0; - mPositiveSum = 0; - return; - } - - float sumNeg = 0f; - float sumPos = 0f; - - for (float f : mYVals) { - if (f <= 0f) - sumNeg += Math.abs(f); - else - sumPos += f; - } - - mNegativeSum = sumNeg; - mPositiveSum = sumPos; - } - - /** - * Calculates the sum across all values of the given stack. - * - * @param vals - * @return - */ - private static float calcSum(float[] vals) { - - if (vals == null) - return 0f; - - float sum = 0f; - - for (float f : vals) - sum += f; - - return sum; - } - - protected void calcRanges() { - - float[] values = getYVals(); - - if (values == null || values.length == 0) - return; - - mRanges = new Range[values.length]; - - float negRemain = -getNegativeSum(); - float posRemain = 0f; - - for (int i = 0; i < mRanges.length; i++) { - - float value = values[i]; - - if (value < 0) { - mRanges[i] = new Range(negRemain, negRemain - value); - negRemain -= value; - } else { - mRanges[i] = new Range(posRemain, posRemain + value); - posRemain += value; - } - } - } -} - - diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarEntry.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarEntry.kt new file mode 100644 index 0000000000..996584c2d9 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarEntry.kt @@ -0,0 +1,260 @@ +package com.github.mikephil.charting.data + +import android.annotation.SuppressLint +import android.graphics.drawable.Drawable +import com.github.mikephil.charting.highlight.Range +import kotlin.math.abs + +/** + * Entry class for the BarChart. (especially stacked bars) + * + * @author Philipp Jahoda + */ +@SuppressLint("ParcelCreator") +open class BarEntry : Entry { + /** + * Returns the stacked values this BarEntry represents, or null, if only a single value is represented (then, use + * getY()). + * + * @return + */ + /** + * the values the stacked barchart holds + */ + var yVals: FloatArray? = null + private set + + /** + * Returns the ranges of the individual stack-entries. Will return null if this entry is not stacked. + * + * @return + */ + /** + * the ranges for the individual stack values - automatically calculated + */ + var ranges: Array = arrayOf() + private set + + /** + * Returns the sum of all negative values this entry (if stacked) contains. (this is a positive number) + * + * @return + */ + /** + * the sum of all negative values this entry (if stacked) contains + */ + var negativeSum: Float = 0f + private set + + /** + * Reuturns the sum of all positive values this entry (if stacked) contains. + * + * @return + */ + /** + * the sum of all positive values this entry (if stacked) contains + */ + var positiveSum: Float = 0f + private set + + /** + * Constructor for normal bars (not stacked). + * + * @param x + * @param y + */ + constructor(x: Float, y: Float) : super(x, y) + + /** + * Constructor for normal bars (not stacked). + * + * @param x + * @param y + * @param data - Spot for additional data this Entry represents. + */ + constructor(x: Float, y: Float, data: Any?) : super(x, y, data) + + /** + * Constructor for normal bars (not stacked). + * + * @param x + * @param y + * @param icon - icon image + */ + constructor(x: Float, y: Float, icon: Drawable?) : super(x, y, icon) + + /** + * Constructor for normal bars (not stacked). + * + * @param x + * @param y + * @param icon - icon image + * @param data - Spot for additional data this Entry represents. + */ + constructor(x: Float, y: Float, icon: Drawable?, data: Any?) : super(x, y, icon, data) + + /** + * Constructor for stacked bar entries. One data object for whole stack + * + * @param x + * @param vals - the stack values, use at least 2 + */ + constructor(x: Float, vals: FloatArray?) : super(x, calcSum(vals)) { + this.yVals = vals + calcPosNegSum() + calcRanges() + } + + /** + * Constructor for stacked bar entries. One data object for whole stack + * + * @param x + * @param vals - the stack values, use at least 2 + * @param data - Spot for additional data this Entry represents. + */ + constructor(x: Float, vals: FloatArray?, data: Any?) : super(x, calcSum(vals), data) { + this.yVals = vals + calcPosNegSum() + calcRanges() + } + + /** + * Constructor for stacked bar entries. One data object for whole stack + * + * @param x + * @param vals - the stack values, use at least 2 + * @param icon - icon image + */ + constructor(x: Float, vals: FloatArray?, icon: Drawable?) : super(x, calcSum(vals), icon) { + this.yVals = vals + calcPosNegSum() + calcRanges() + } + + /** + * Constructor for stacked bar entries. One data object for whole stack + * + * @param x + * @param vals - the stack values, use at least 2 + * @param icon - icon image + * @param data - Spot for additional data this Entry represents. + */ + constructor(x: Float, vals: FloatArray?, icon: Drawable?, data: Any?) : super(x, calcSum(vals), icon, data) { + this.yVals = vals + calcPosNegSum() + calcRanges() + } + + /** + * Returns an exact copy of the BarEntry. + */ + override fun copy(): BarEntry { + val copied = BarEntry(x, y, data) + copied.setVals(this.yVals) + return copied + } + + /** + * Set the array of values this BarEntry should represent. + * + * @param vals + */ + fun setVals(vals: FloatArray?) { + y = calcSum(vals) + this.yVals = vals + calcPosNegSum() + calcRanges() + } + + val isStacked: Boolean + /** + * Returns true if this BarEntry is stacked (has a values array), false if not. + * + * @return + */ + get() = this.yVals != null + + /** + * Use `getSumBelow(stackIndex)` instead. + */ + @Deprecated("") + fun getBelowSum(stackIndex: Int): Float { + return getSumBelow(stackIndex) + } + + fun getSumBelow(stackIndex: Int): Float { + if (this.yVals == null) return 0f + + var remainder = 0f + var index = yVals!!.size - 1 + + while (index > stackIndex && index >= 0) { + remainder += this.yVals!![index] + index-- + } + + return remainder + } + + private fun calcPosNegSum() { + if (this.yVals == null) { + this.negativeSum = 0f + this.positiveSum = 0f + return + } + + var sumNeg = 0f + var sumPos = 0f + + for (f in this.yVals) { + if (f <= 0f) sumNeg += abs(f) + else sumPos += f + } + + this.negativeSum = sumNeg + this.positiveSum = sumPos + } + + protected fun calcRanges() { + val values = this.yVals + + if (values == null || values.isEmpty()) return + + var negRemain = -this.negativeSum + var posRemain = 0f + + this.ranges = Array(values.size) { i -> + val value = values[i] + + if (value < 0) { + Range(negRemain, negRemain - value).also { + negRemain -= value + } + } else { + Range(posRemain, posRemain + value).also { + posRemain += value + } + } + } + } + + companion object { + /** + * Calculates the sum across all values of the given stack. + * + * @param vals + * @return + */ + private fun calcSum(vals: FloatArray?): Float { + if (vals == null) return 0f + + var sum = 0f + + for (f in vals) sum += f + + return sum + } + } +} + + diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.java deleted file mode 100644 index c09eadec9b..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.java +++ /dev/null @@ -1,27 +0,0 @@ - -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet; - -import java.util.List; - -/** - * Baseclass for all Line, Bar, Scatter, Candle and Bubble data. - * - * @author Philipp Jahoda - */ -public abstract class BarLineScatterCandleBubbleData> - extends ChartData { - - public BarLineScatterCandleBubbleData() { - super(); - } - - public BarLineScatterCandleBubbleData(T... sets) { - super(sets); - } - - public BarLineScatterCandleBubbleData(List sets) { - super(sets); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.kt new file mode 100644 index 0000000000..f6cc95ba1b --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.kt @@ -0,0 +1,17 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet + +/** + * Baseclass for all Line, Bar, Scatter, Candle and Bubble data. + * + * @author Philipp Jahoda + */ +abstract class BarLineScatterCandleBubbleData> + : ChartData { + constructor() : super() + + constructor(vararg sets: T) : super(*sets) + + constructor(sets: MutableList) : super(sets) +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleDataSet.java deleted file mode 100644 index fc8cc36e06..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleDataSet.java +++ /dev/null @@ -1,48 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.graphics.Color; - -import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet; - -import java.util.List; - -/** - * Baseclass of all DataSets for Bar-, Line-, Scatter- and CandleStickChart. - * - * @author Philipp Jahoda - */ -public abstract class BarLineScatterCandleBubbleDataSet - extends DataSet - implements IBarLineScatterCandleBubbleDataSet { - - /** - * default highlight color - */ - protected int mHighLightColor = Color.rgb(255, 187, 115); - - public BarLineScatterCandleBubbleDataSet(List yVals, String label) { - super(yVals, label); - } - - /** - * Sets the color that is used for drawing the highlight indicators. Dont - * forget to resolve the color using getResources().getColor(...) or - * Color.rgb(...). - * - * @param color - */ - public void setHighLightColor(int color) { - mHighLightColor = color; - } - - @Override - public int getHighLightColor() { - return mHighLightColor; - } - - protected void copy(BarLineScatterCandleBubbleDataSet barLineScatterCandleBubbleDataSet) { - super.copy((BaseDataSet) barLineScatterCandleBubbleDataSet); - barLineScatterCandleBubbleDataSet.mHighLightColor = mHighLightColor; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleDataSet.kt new file mode 100644 index 0000000000..0f16389189 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleDataSet.kt @@ -0,0 +1,29 @@ +package com.github.mikephil.charting.data + +import android.graphics.Color +import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet + +/** + * Baseclass of all DataSets for Bar-, Line-, Scatter- and CandleStickChart. + * + * @author Philipp Jahoda + */ +abstract class BarLineScatterCandleBubbleDataSet + (yVals: MutableList, label: String) : DataSet(yVals, label), IBarLineScatterCandleBubbleDataSet { + /** + * Sets the color that is used for drawing the highlight indicators. Dont + * forget to resolve the color using getResources().getColor(...) or + * Color.rgb(...). + * + * @param color + */ + /** + * default highlight color + */ + override var highLightColor: Int = Color.rgb(255, 187, 115) + + protected fun copy(barLineScatterCandleBubbleDataSet: BarLineScatterCandleBubbleDataSet<*>) { + super.copy(barLineScatterCandleBubbleDataSet) + barLineScatterCandleBubbleDataSet.highLightColor = this.highLightColor + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BaseDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BaseDataSet.kt index dd37ddb55c..b66d4abd6a 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BaseDataSet.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BaseDataSet.kt @@ -17,7 +17,7 @@ import com.github.mikephil.charting.utils.Utils * This is the base dataset of all DataSets. It's purpose is to implement critical methods * provided by the IDataSet interface. */ -abstract class BaseDataSet() : IDataSet { +abstract class BaseDataSet() : IDataSet { /** * List representing all colors that are used for this DataSet */ @@ -28,62 +28,11 @@ abstract class BaseDataSet() : IDataSet { */ protected var mValueColors: MutableList - /** - * label that describes the DataSet or the data the DataSet represents - */ - private var mLabel = "DataSet" - /** * this specifies which axis this DataSet should be plotted against */ protected var mAxisDependency: AxisDependency = AxisDependency.LEFT - /** - * if true, value highlightning is enabled - */ - protected var mHighlightEnabled: Boolean = true - - /** - * custom formatter that is used instead of the auto-formatter if set - */ - @Transient - protected var mValueFormatter: IValueFormatter? = null - - /** - * the typeface used for the value text - */ - protected var mValueTypeface: Typeface? = null - - private var mForm = LegendForm.DEFAULT - private var mFormSize = Float.NaN - private var mFormLineWidth = Float.NaN - private var mFormLineDashEffect: DashPathEffect? = null - - /** - * if true, y-values are drawn on the chart - */ - protected var mDrawValues: Boolean = true - - /** - * if true, y-icons are drawn on the chart - */ - protected var mDrawIcons: Boolean = true - - /** - * the offset for drawing icons (in dp) - */ - protected var mIconsOffset: MPPointF = MPPointF() - - /** - * the size of the value-text labels - */ - protected var mValueTextSize: Float = 17f - - /** - * flag that indicates if the DataSet is visible or not - */ - protected var mVisible: Boolean = true - /** * Default constructor. */ @@ -102,7 +51,7 @@ abstract class BaseDataSet() : IDataSet { * @param label */ constructor(label: String) : this() { - this.mLabel = label + this.label = label } /** @@ -112,16 +61,14 @@ abstract class BaseDataSet() : IDataSet { calcMinMax() } - override fun getColors(): List { - return mColors - } + override val colors: MutableList + get() = mColors val valueColors: List get() = mValueColors - override fun getColor(): Int { - return mColors[0] - } + override val color: Int + get() = mColors[0] override fun getColor(index: Int): Int { return mColors[index % mColors.size] @@ -227,149 +174,59 @@ abstract class BaseDataSet() : IDataSet { /** * ###### ###### OTHER STYLING RELATED METHODS ##### ###### */ - override fun setLabel(label: String) { - mLabel = label - } - - override fun getLabel(): String { - return mLabel - } - - override fun setHighlightEnabled(enabled: Boolean) { - mHighlightEnabled = enabled - } + override var label: String = "DataSet" - override fun isHighlightEnabled(): Boolean { - return mHighlightEnabled - } - - override fun setValueFormatter(f: IValueFormatter?) { - if (f == null) return - else mValueFormatter = f - } - - override fun getValueFormatter(): IValueFormatter { - if (needsFormatter()) return Utils.getDefaultValueFormatter() - return mValueFormatter!! - } + override var isHighlightEnabled: Boolean = true - override fun needsFormatter(): Boolean { - return mValueFormatter == null - } + @Transient + override var valueFormatter: IValueFormatter = Utils.defaultValueFormatter - override fun setValueTextColor(color: Int) { - mValueColors.clear() - mValueColors.add(color) - } + override var valueTextColor: Int + get() = mValueColors[0] + set(value) { + mValueColors.clear() + mValueColors.add(value) + } override fun setValueTextColors(colors: MutableList) { mValueColors = colors } - override fun setValueTypeface(tf: Typeface?) { - mValueTypeface = tf - } + override var valueTypeface: Typeface? = null - override fun setValueTextSize(size: Float) { - mValueTextSize = Utils.convertDpToPixel(size) - } - - override fun getValueTextColor(): Int { - return mValueColors[0] - } + override var valueTextSize: Float = 17f + set(value) { + field = Utils.convertDpToPixel(value) + } override fun getValueTextColor(index: Int): Int { return mValueColors[index % mValueColors.size] } - override fun getValueTypeface(): Typeface? { - return mValueTypeface - } + override var form: LegendForm? = LegendForm.DEFAULT - override fun getValueTextSize(): Float { - return mValueTextSize - } + override var formSize: Float = Float.NaN - fun setForm(form: LegendForm) { - mForm = form - } - - override fun getForm(): LegendForm { - return mForm - } - - fun setFormSize(formSize: Float) { - mFormSize = formSize - } + override var formLineWidth: Float = Float.NaN - override fun getFormSize(): Float { - return mFormSize - } + override var formLineDashEffect: DashPathEffect? = null - fun setFormLineWidth(formLineWidth: Float) { - mFormLineWidth = formLineWidth - } + override var isDrawValuesEnabled: Boolean = true - override fun getFormLineWidth(): Float { - return mFormLineWidth - } + override var isDrawIconsEnabled: Boolean = true - fun setFormLineDashEffect(dashPathEffect: DashPathEffect?) { - mFormLineDashEffect = dashPathEffect - } + override var iconsOffset: MPPointF = MPPointF() - override fun getFormLineDashEffect(): DashPathEffect? { - return mFormLineDashEffect - } - - override fun setDrawValues(enabled: Boolean) { - this.mDrawValues = enabled - } - - override fun isDrawValuesEnabled(): Boolean { - return mDrawValues - } - - override fun setDrawIcons(enabled: Boolean) { - mDrawIcons = enabled - } - - override fun isDrawIconsEnabled(): Boolean { - return mDrawIcons - } - - override fun setIconsOffset(offsetDp: MPPointF) { - mIconsOffset.x = offsetDp.x - mIconsOffset.y = offsetDp.y - } - - override fun getIconsOffset(): MPPointF { - return mIconsOffset - } - - override fun setVisible(visible: Boolean) { - mVisible = visible - } - - override fun isVisible(): Boolean { - return mVisible - } - - override fun getAxisDependency(): AxisDependency { - return mAxisDependency - } - - override fun setAxisDependency(dependency: AxisDependency) { - mAxisDependency = dependency - } + override var isVisible: Boolean = true + override var axisDependency: AxisDependency? = AxisDependency.LEFT /** * ###### ###### DATA RELATED METHODS ###### ###### */ override fun getIndexInEntries(xIndex: Int): Int { for (i in 0..() : IDataSet { return removeEntry(e) } - override fun contains(e: T): Boolean { + override fun contains(entry: T?): Boolean { for (i in 0..() : IDataSet { protected fun copy(baseDataSet: BaseDataSet<*>) { baseDataSet.mAxisDependency = mAxisDependency baseDataSet.mColors = mColors - baseDataSet.mDrawIcons = mDrawIcons - baseDataSet.mDrawValues = mDrawValues - baseDataSet.mForm = mForm - baseDataSet.mFormLineDashEffect = mFormLineDashEffect - baseDataSet.mFormLineWidth = mFormLineWidth - baseDataSet.mFormSize = mFormSize - baseDataSet.mHighlightEnabled = mHighlightEnabled - baseDataSet.mIconsOffset = mIconsOffset + baseDataSet.isDrawIconsEnabled = isDrawIconsEnabled + baseDataSet.isDrawValuesEnabled = isDrawValuesEnabled + baseDataSet.form = form + baseDataSet.formLineDashEffect = formLineDashEffect + baseDataSet.formLineWidth = formLineWidth + baseDataSet.formSize = formSize + baseDataSet.isHighlightEnabled = isHighlightEnabled + baseDataSet.iconsOffset = iconsOffset baseDataSet.mValueColors = mValueColors - baseDataSet.mValueFormatter = mValueFormatter - baseDataSet.mValueTextSize = mValueTextSize - baseDataSet.mVisible = mVisible + baseDataSet.valueFormatter = valueFormatter + baseDataSet.valueTextSize = valueTextSize + baseDataSet.isVisible = isVisible } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.java deleted file mode 100644 index 8fb72f17c5..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.java +++ /dev/null @@ -1,34 +0,0 @@ - -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet; - -import java.util.List; - -public class BubbleData extends BarLineScatterCandleBubbleData { - - public BubbleData() { - super(); - } - - public BubbleData(IBubbleDataSet... dataSets) { - super(dataSets); - } - - public BubbleData(List dataSets) { - super(dataSets); - } - - - /** - * Sets the width of the circle that surrounds the bubble when highlighted - * for all DataSet objects this data object contains, in dp. - * - * @param width - */ - public void setHighlightCircleWidth(float width) { - for (IBubbleDataSet set : mDataSets) { - set.setHighlightCircleWidth(width); - } - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.kt new file mode 100644 index 0000000000..b07124bf74 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.kt @@ -0,0 +1,24 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet + +class BubbleData : BarLineScatterCandleBubbleData { + constructor() : super() + + constructor(vararg dataSets: IBubbleDataSet) : super(*dataSets) + + constructor(dataSets: MutableList) : super(dataSets) + + + /** + * Sets the width of the circle that surrounds the bubble when highlighted + * for all DataSet objects this data object contains, in dp. + * + * @param width + */ + fun setHighlightCircleWidth(width: Float) { + for (set in dataSets) { + set.highlightCircleWidth = width + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.java deleted file mode 100644 index 9ef87fb2d5..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.java +++ /dev/null @@ -1,71 +0,0 @@ - -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet; -import com.github.mikephil.charting.utils.Utils; - -import java.util.ArrayList; -import java.util.List; - -public class BubbleDataSet extends BarLineScatterCandleBubbleDataSet implements IBubbleDataSet { - - protected float mMaxSize; - protected boolean mNormalizeSize = true; - - private float mHighlightCircleWidth = 2.5f; - - public BubbleDataSet(List yVals, String label) { - super(yVals, label); - } - - @Override - public void setHighlightCircleWidth(float width) { - mHighlightCircleWidth = Utils.convertDpToPixel(width); - } - - @Override - public float getHighlightCircleWidth() { - return mHighlightCircleWidth; - } - - @Override - protected void calcMinMax(BubbleEntry e) { - super.calcMinMax(e); - - final float size = e.getSize(); - - if (size > mMaxSize) { - mMaxSize = size; - } - } - - @Override - public DataSet copy() { - List entries = new ArrayList(); - for (int i = 0; i < mEntries.size(); i++) { - entries.add(mEntries.get(i).copy()); - } - BubbleDataSet copied = new BubbleDataSet(entries, getLabel()); - copy(copied); - return copied; - } - - protected void copy(BubbleDataSet bubbleDataSet) { - bubbleDataSet.mHighlightCircleWidth = mHighlightCircleWidth; - bubbleDataSet.mNormalizeSize = mNormalizeSize; - } - - @Override - public float getMaxSize() { - return mMaxSize; - } - - @Override - public boolean isNormalizeSizeEnabled() { - return mNormalizeSize; - } - - public void setNormalizeSizeEnabled(boolean normalizeSize) { - mNormalizeSize = normalizeSize; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.kt new file mode 100644 index 0000000000..58d176263c --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleDataSet.kt @@ -0,0 +1,45 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet + +open class BubbleDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), IBubbleDataSet { + protected var mMaxSize: Float = 0f + protected var mNormalizeSize: Boolean = true + + override var highlightCircleWidth: Float = 2.5f + + override fun calcMinMax(e: BubbleEntry) { + super.calcMinMax(e) + + val size = e.size + + if (size > mMaxSize) { + mMaxSize = size + } + } + + override fun copy(): DataSet { + val entries: MutableList = ArrayList() + for (i in mEntries.indices) { + entries.add(mEntries[i].copy()) + } + val copied = BubbleDataSet(entries, label) + copy(copied) + return copied + } + + protected fun copy(bubbleDataSet: BubbleDataSet) { + bubbleDataSet.highlightCircleWidth = highlightCircleWidth + bubbleDataSet.mNormalizeSize = mNormalizeSize + } + + override val maxSize: Float + get() = mMaxSize + + override val isNormalizeSizeEnabled: Boolean + get() = mNormalizeSize + + fun setNormalizeSizeEnabled(normalizeSize: Boolean) { + mNormalizeSize = normalizeSize + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleEntry.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleEntry.kt similarity index 56% rename from MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleEntry.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleEntry.kt index 35d8330aaa..19ac343b2f 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleEntry.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleEntry.kt @@ -1,8 +1,7 @@ +package com.github.mikephil.charting.data -package com.github.mikephil.charting.data; - -import android.annotation.SuppressLint; -import android.graphics.drawable.Drawable; +import android.annotation.SuppressLint +import android.graphics.drawable.Drawable /** * Subclass of Entry that holds a value for one entry in a BubbleChart. Bubble @@ -12,10 +11,14 @@ * @author Philipp Jahoda */ @SuppressLint("ParcelCreator") -public class BubbleEntry extends Entry { - - /** size value */ - private float mSize = 0f; +class BubbleEntry : Entry { + /** + * Returns the size of this entry (the size of the bubble). + * + * @return + */ + /** size value */ + var size: Float = 0f /** * Constructor. @@ -24,9 +27,8 @@ public class BubbleEntry extends Entry { * @param y The value on the y-axis. * @param size The size of the bubble. */ - public BubbleEntry(float x, float y, float size) { - super(x, y); - this.mSize = size; + constructor(x: Float, y: Float, size: Float) : super(x, y) { + this.size = size } /** @@ -37,9 +39,8 @@ public BubbleEntry(float x, float y, float size) { * @param size The size of the bubble. * @param data Spot for additional data this Entry represents. */ - public BubbleEntry(float x, float y, float size, Object data) { - super(x, y, data); - this.mSize = size; + constructor(x: Float, y: Float, size: Float, data: Any?) : super(x, y, data) { + this.size = size } /** @@ -50,9 +51,8 @@ public BubbleEntry(float x, float y, float size, Object data) { * @param size The size of the bubble. * @param icon Icon image */ - public BubbleEntry(float x, float y, float size, Drawable icon) { - super(x, y, icon); - this.mSize = size; + constructor(x: Float, y: Float, size: Float, icon: Drawable?) : super(x, y, icon) { + this.size = size } /** @@ -64,28 +64,12 @@ public BubbleEntry(float x, float y, float size, Drawable icon) { * @param icon Icon image * @param data Spot for additional data this Entry represents. */ - public BubbleEntry(float x, float y, float size, Drawable icon, Object data) { - super(x, y, icon, data); - this.mSize = size; - } - - public BubbleEntry copy() { - - BubbleEntry c = new BubbleEntry(getX(), getY(), mSize, getData()); - return c; - } - - /** - * Returns the size of this entry (the size of the bubble). - * - * @return - */ - public float getSize() { - return mSize; + constructor(x: Float, y: Float, size: Float, icon: Drawable?, data: Any?) : super(x, y, icon, data) { + this.size = size } - public void setSize(float size) { - this.mSize = size; + override fun copy(): BubbleEntry { + val c = BubbleEntry(x, y, this.size, data) + return c } - } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleData.java deleted file mode 100644 index 1e90db2ba5..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleData.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet; - -import java.util.ArrayList; -import java.util.List; - -public class CandleData extends BarLineScatterCandleBubbleData { - - public CandleData() { - super(); - } - - public CandleData(List dataSets) { - super(dataSets); - } - - public CandleData(ICandleDataSet... dataSets) { - super(dataSets); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleData.kt new file mode 100644 index 0000000000..ce6197da3d --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleData.kt @@ -0,0 +1,11 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet + +class CandleData : BarLineScatterCandleBubbleData { + constructor() : super() + + constructor(dataSets: MutableList) : super(dataSets) + + constructor(vararg dataSets: ICandleDataSet) : super(*dataSets) +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.java deleted file mode 100644 index a9a016bcad..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.java +++ /dev/null @@ -1,295 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.graphics.Paint; - -import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.Utils; - -import java.util.ArrayList; -import java.util.List; - -/** - * DataSet for the CandleStickChart. - * - * @author Philipp Jahoda - */ -public class CandleDataSet extends LineScatterCandleRadarDataSet implements ICandleDataSet { - - /** - * the width of the shadow of the candle - */ - private float mShadowWidth = 3f; - - /** - * should the candle bars show? - * when false, only "ticks" will show - *

- * - default: true - */ - private boolean mShowCandleBar = true; - - /** - * the space between the candle entries, default 0.1f (10%) - */ - private float mBarSpace = 0.1f; - - /** - * use candle color for the shadow - */ - private boolean mShadowColorSameAsCandle = false; - - /** - * paint style when open < close - * increasing candlesticks are traditionally hollow - */ - protected Paint.Style mIncreasingPaintStyle = Paint.Style.STROKE; - - /** - * paint style when open > close - * descreasing candlesticks are traditionally filled - */ - protected Paint.Style mDecreasingPaintStyle = Paint.Style.FILL; - - /** - * color for open == close - */ - protected int mNeutralColor = ColorTemplate.COLOR_SKIP; - - /** - * color for open < close - */ - protected int mIncreasingColor = ColorTemplate.COLOR_SKIP; - - /** - * color for open > close - */ - protected int mDecreasingColor = ColorTemplate.COLOR_SKIP; - - /** - * shadow line color, set -1 for backward compatibility and uses default - * color - */ - protected int mShadowColor = ColorTemplate.COLOR_SKIP; - - public CandleDataSet(List yVals, String label) { - super(yVals, label); - } - - @Override - public DataSet copy() { - List entries = new ArrayList(); - for (int i = 0; i < mEntries.size(); i++) { - entries.add(mEntries.get(i).copy()); - } - CandleDataSet copied = new CandleDataSet(entries, getLabel()); - copy(copied); - return copied; - } - - protected void copy(CandleDataSet candleDataSet) { - super.copy((BaseDataSet) candleDataSet); - candleDataSet.mShadowWidth = mShadowWidth; - candleDataSet.mShowCandleBar = mShowCandleBar; - candleDataSet.mBarSpace = mBarSpace; - candleDataSet.mShadowColorSameAsCandle = mShadowColorSameAsCandle; - candleDataSet.mHighLightColor = mHighLightColor; - candleDataSet.mIncreasingPaintStyle = mIncreasingPaintStyle; - candleDataSet.mDecreasingPaintStyle = mDecreasingPaintStyle; - candleDataSet.mNeutralColor = mNeutralColor; - candleDataSet.mIncreasingColor = mIncreasingColor; - candleDataSet.mDecreasingColor = mDecreasingColor; - candleDataSet.mShadowColor = mShadowColor; - } - - @Override - protected void calcMinMax(CandleEntry e) { - - if (e.getLow() < mYMin) - mYMin = e.getLow(); - - if (e.getHigh() > mYMax) - mYMax = e.getHigh(); - - calcMinMaxX(e); - } - - @Override - protected void calcMinMaxY(CandleEntry e) { - - if (e.getHigh() < mYMin) - mYMin = e.getHigh(); - - if (e.getHigh() > mYMax) - mYMax = e.getHigh(); - - if (e.getLow() < mYMin) - mYMin = e.getLow(); - - if (e.getLow() > mYMax) - mYMax = e.getLow(); - } - - /** - * Sets the space that is left out on the left and right side of each - * candle, default 0.1f (10%), max 0.45f, min 0f - * - * @param space - */ - public void setBarSpace(float space) { - - if (space < 0f) - space = 0f; - if (space > 0.45f) - space = 0.45f; - - mBarSpace = space; - } - - @Override - public float getBarSpace() { - return mBarSpace; - } - - /** - * Sets the width of the candle-shadow-line in pixels. Default 3f. - * - * @param width - */ - public void setShadowWidth(float width) { - mShadowWidth = Utils.convertDpToPixel(width); - } - - @Override - public float getShadowWidth() { - return mShadowWidth; - } - - /** - * Sets whether the candle bars should show? - * - * @param showCandleBar - */ - public void setShowCandleBar(boolean showCandleBar) { - mShowCandleBar = showCandleBar; - } - - @Override - public boolean getShowCandleBar() { - return mShowCandleBar; - } - - // TODO - /** - * It is necessary to implement ColorsList class that will encapsulate - * colors list functionality, because It's wrong to copy paste setColor, - * addColor, ... resetColors for each time when we want to add a coloring - * options for one of objects - * - * @author Mesrop - */ - - /** BELOW THIS COLOR HANDLING */ - - /** - * Sets the one and ONLY color that should be used for this DataSet when - * open == close. - * - * @param color - */ - public void setNeutralColor(int color) { - mNeutralColor = color; - } - - @Override - public int getNeutralColor() { - return mNeutralColor; - } - - /** - * Sets the one and ONLY color that should be used for this DataSet when - * open <= close. - * - * @param color - */ - public void setIncreasingColor(int color) { - mIncreasingColor = color; - } - - @Override - public int getIncreasingColor() { - return mIncreasingColor; - } - - /** - * Sets the one and ONLY color that should be used for this DataSet when - * open > close. - * - * @param color - */ - public void setDecreasingColor(int color) { - mDecreasingColor = color; - } - - @Override - public int getDecreasingColor() { - return mDecreasingColor; - } - - @Override - public Paint.Style getIncreasingPaintStyle() { - return mIncreasingPaintStyle; - } - - /** - * Sets paint style when open < close - * - * @param paintStyle - */ - public void setIncreasingPaintStyle(Paint.Style paintStyle) { - this.mIncreasingPaintStyle = paintStyle; - } - - @Override - public Paint.Style getDecreasingPaintStyle() { - return mDecreasingPaintStyle; - } - - /** - * Sets paint style when open > close - * - * @param decreasingPaintStyle - */ - public void setDecreasingPaintStyle(Paint.Style decreasingPaintStyle) { - this.mDecreasingPaintStyle = decreasingPaintStyle; - } - - @Override - public int getShadowColor() { - return mShadowColor; - } - - /** - * Sets shadow color for all entries - * - * @param shadowColor - */ - public void setShadowColor(int shadowColor) { - this.mShadowColor = shadowColor; - } - - @Override - public boolean getShadowColorSameAsCandle() { - return mShadowColorSameAsCandle; - } - - /** - * Sets shadow color to be the same color as the candle color - * - * @param shadowColorSameAsCandle - */ - public void setShadowColorSameAsCandle(boolean shadowColorSameAsCandle) { - this.mShadowColorSameAsCandle = shadowColorSameAsCandle; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.kt new file mode 100644 index 0000000000..e665116b82 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleDataSet.kt @@ -0,0 +1,251 @@ +package com.github.mikephil.charting.data + +import android.graphics.Paint +import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.Utils + +/** + * DataSet for the CandleStickChart. + * + * @author Philipp Jahoda + */ +open class CandleDataSet(yVals: MutableList, label: String) : LineScatterCandleRadarDataSet(yVals, label), ICandleDataSet { + /** + * the width of the shadow of the candle + */ + private var mShadowWidth = 3f + + /** + * should the candle bars show? + * when false, only "ticks" will show + * + * + * - default: true + */ + private var mShowCandleBar = true + + /** + * the space between the candle entries, default 0.1f (10%) + */ + private var mBarSpace = 0.1f + + /** + * use candle color for the shadow + */ + private var mShadowColorSameAsCandle = false + + /** + * paint style when open < close + * increasing candlesticks are traditionally hollow + */ + protected var mIncreasingPaintStyle: Paint.Style? = Paint.Style.STROKE + + /** + * paint style when open > close + * descreasing candlesticks are traditionally filled + */ + protected var mDecreasingPaintStyle: Paint.Style? = Paint.Style.FILL + + /** + * color for open == close + */ + protected var mNeutralColor: Int = ColorTemplate.COLOR_SKIP + + /** + * color for open < close + */ + protected var mIncreasingColor: Int = ColorTemplate.COLOR_SKIP + + /** + * color for open > close + */ + protected var mDecreasingColor: Int = ColorTemplate.COLOR_SKIP + + /** + * shadow line color, set -1 for backward compatibility and uses default + * color + */ + protected var mShadowColor: Int = ColorTemplate.COLOR_SKIP + + override fun copy(): DataSet { + val entries: MutableList = ArrayList() + for (i in mEntries.indices) { + entries.add(mEntries[i].copy()) + } + val copied = CandleDataSet(entries, label) + copy(copied) + return copied + } + + protected fun copy(candleDataSet: CandleDataSet) { + super.copy((candleDataSet as BaseDataSet<*>?)!!) + candleDataSet.mShadowWidth = mShadowWidth + candleDataSet.mShowCandleBar = mShowCandleBar + candleDataSet.mBarSpace = mBarSpace + candleDataSet.mShadowColorSameAsCandle = mShadowColorSameAsCandle + candleDataSet.highLightColor = highLightColor + candleDataSet.mIncreasingPaintStyle = mIncreasingPaintStyle + candleDataSet.mDecreasingPaintStyle = mDecreasingPaintStyle + candleDataSet.mNeutralColor = mNeutralColor + candleDataSet.mIncreasingColor = mIncreasingColor + candleDataSet.mDecreasingColor = mDecreasingColor + candleDataSet.mShadowColor = mShadowColor + } + + override fun calcMinMax(e: CandleEntry) { + if (e.low < mYMin) mYMin = e.low + + if (e.high > mYMax) mYMax = e.high + + calcMinMaxX(e) + } + + override fun calcMinMaxY(e: CandleEntry) { + if (e.high < mYMin) mYMin = e.high + + if (e.high > mYMax) mYMax = e.high + + if (e.low < mYMin) mYMin = e.low + + if (e.low > mYMax) mYMax = e.low + } + + /** + * Sets the space that is left out on the left and right side of each + * candle, default 0.1f (10%), max 0.45f, min 0f + * + * @param space + */ + fun setBarSpace(space: Float) { + var space = space + if (space < 0f) space = 0f + if (space > 0.45f) space = 0.45f + + mBarSpace = space + } + + override val barSpace: Float + get() = mBarSpace + + /** + * Sets the width of the candle-shadow-line in pixels. Default 3f. + * + * @param width + */ + fun setShadowWidth(width: Float) { + mShadowWidth = Utils.convertDpToPixel(width) + } + + override val shadowWidth: Float + get() = mShadowWidth + + /** + * Sets whether the candle bars should show? + * + * @param showCandleBar + */ + fun setShowCandleBar(showCandleBar: Boolean) { + mShowCandleBar = showCandleBar + } + + override val showCandleBar: Boolean + get() = mShowCandleBar + + // TODO + /** + * It is necessary to implement ColorsList class that will encapsulate + * colors list functionality, because It's wrong to copy paste setColor, + * addColor, ... resetColors for each time when we want to add a coloring + * options for one of objects + * + * @author Mesrop + */ + /** BELOW THIS COLOR HANDLING */ + /** + * Sets the one and ONLY color that should be used for this DataSet when + * open == close. + * + * @param color + */ + fun setNeutralColor(color: Int) { + mNeutralColor = color + } + + override val neutralColor: Int + get() = mNeutralColor + + /** + * Sets the one and ONLY color that should be used for this DataSet when + * open <= close. + * + * @param color + */ + fun setIncreasingColor(color: Int) { + mIncreasingColor = color + } + + override val increasingColor: Int + get() = mIncreasingColor + + /** + * Sets the one and ONLY color that should be used for this DataSet when + * open > close. + * + * @param color + */ + fun setDecreasingColor(color: Int) { + mDecreasingColor = color + } + + override val decreasingColor: Int + get() = mDecreasingColor + + override val increasingPaintStyle: Paint.Style? + get() = mIncreasingPaintStyle + + /** + * Sets paint style when open < close + * + * @param paintStyle + */ + fun setIncreasingPaintStyle(paintStyle: Paint.Style?) { + this.mIncreasingPaintStyle = paintStyle + } + + override val decreasingPaintStyle: Paint.Style? + get() = mDecreasingPaintStyle + + /** + * Sets paint style when open > close + * + * @param decreasingPaintStyle + */ + fun setDecreasingPaintStyle(decreasingPaintStyle: Paint.Style?) { + this.mDecreasingPaintStyle = decreasingPaintStyle + } + + override val shadowColor: Int + get() = mShadowColor + + /** + * Sets shadow color for all entries + * + * @param shadowColor + */ + fun setShadowColor(shadowColor: Int) { + this.mShadowColor = shadowColor + } + + override val shadowColorSameAsCandle: Boolean + get() = mShadowColorSameAsCandle + + /** + * Sets shadow color to be the same color as the candle color + * + * @param shadowColorSameAsCandle + */ + fun setShadowColorSameAsCandle(shadowColorSameAsCandle: Boolean) { + this.mShadowColorSameAsCandle = shadowColorSameAsCandle + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleEntry.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleEntry.java deleted file mode 100644 index 5a049957a2..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleEntry.java +++ /dev/null @@ -1,193 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.annotation.SuppressLint; -import android.graphics.drawable.Drawable; - -/** - * Subclass of Entry that holds all values for one entry in a CandleStickChart. - * - * @author Philipp Jahoda - */ -@SuppressLint("ParcelCreator") -public class CandleEntry extends Entry { - - /** shadow-high value */ - private float mShadowHigh = 0f; - - /** shadow-low value */ - private float mShadowLow = 0f; - - /** close value */ - private float mClose = 0f; - - /** open value */ - private float mOpen = 0f; - - /** - * Constructor. - * - * @param x The value on the x-axis - * @param shadowH The (shadow) high value - * @param shadowL The (shadow) low value - * @param open The open value - * @param close The close value - */ - public CandleEntry(float x, float shadowH, float shadowL, float open, float close) { - super(x, (shadowH + shadowL) / 2f); - - this.mShadowHigh = shadowH; - this.mShadowLow = shadowL; - this.mOpen = open; - this.mClose = close; - } - - /** - * Constructor. - * - * @param x The value on the x-axis - * @param shadowH The (shadow) high value - * @param shadowL The (shadow) low value - * @param open - * @param close - * @param data Spot for additional data this Entry represents - */ - public CandleEntry(float x, float shadowH, float shadowL, float open, float close, - Object data) { - super(x, (shadowH + shadowL) / 2f, data); - - this.mShadowHigh = shadowH; - this.mShadowLow = shadowL; - this.mOpen = open; - this.mClose = close; - } - - /** - * Constructor. - * - * @param x The value on the x-axis - * @param shadowH The (shadow) high value - * @param shadowL The (shadow) low value - * @param open - * @param close - * @param icon Icon image - */ - public CandleEntry(float x, float shadowH, float shadowL, float open, float close, - Drawable icon) { - super(x, (shadowH + shadowL) / 2f, icon); - - this.mShadowHigh = shadowH; - this.mShadowLow = shadowL; - this.mOpen = open; - this.mClose = close; - } - - /** - * Constructor. - * - * @param x The value on the x-axis - * @param shadowH The (shadow) high value - * @param shadowL The (shadow) low value - * @param open - * @param close - * @param icon Icon image - * @param data Spot for additional data this Entry represents - */ - public CandleEntry(float x, float shadowH, float shadowL, float open, float close, - Drawable icon, Object data) { - super(x, (shadowH + shadowL) / 2f, icon, data); - - this.mShadowHigh = shadowH; - this.mShadowLow = shadowL; - this.mOpen = open; - this.mClose = close; - } - - /** - * Returns the overall range (difference) between shadow-high and - * shadow-low. - * - * @return - */ - public float getShadowRange() { - return Math.abs(mShadowHigh - mShadowLow); - } - - /** - * Returns the body size (difference between open and close). - * - * @return - */ - public float getBodyRange() { - return Math.abs(mOpen - mClose); - } - - /** - * Returns the center value of the candle. (Middle value between high and - * low) - */ - @Override - public float getY() { - return super.getY(); - } - - public CandleEntry copy() { - - CandleEntry c = new CandleEntry(getX(), mShadowHigh, mShadowLow, mOpen, - mClose, getData()); - - return c; - } - - /** - * Returns the upper shadows highest value. - * - * @return - */ - public float getHigh() { - return mShadowHigh; - } - - public void setHigh(float mShadowHigh) { - this.mShadowHigh = mShadowHigh; - } - - /** - * Returns the lower shadows lowest value. - * - * @return - */ - public float getLow() { - return mShadowLow; - } - - public void setLow(float mShadowLow) { - this.mShadowLow = mShadowLow; - } - - /** - * Returns the bodys close value. - * - * @return - */ - public float getClose() { - return mClose; - } - - public void setClose(float mClose) { - this.mClose = mClose; - } - - /** - * Returns the bodys open value. - * - * @return - */ - public float getOpen() { - return mOpen; - } - - public void setOpen(float mOpen) { - this.mOpen = mOpen; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleEntry.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleEntry.kt new file mode 100644 index 0000000000..2a5e38e751 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CandleEntry.kt @@ -0,0 +1,148 @@ +package com.github.mikephil.charting.data + +import android.annotation.SuppressLint +import android.graphics.drawable.Drawable +import kotlin.math.abs + +/** + * Subclass of Entry that holds all values for one entry in a CandleStickChart. + * + * @author Philipp Jahoda + */ +@SuppressLint("ParcelCreator") +class CandleEntry : Entry { + /** + * Returns the upper shadows highest value. + * + * @return + */ + /** shadow-high value */ + var high: Float = 0f + + /** + * Returns the lower shadows lowest value. + * + * @return + */ + /** shadow-low value */ + var low: Float = 0f + + /** + * Returns the bodys close value. + * + * @return + */ + /** close value */ + var close: Float = 0f + + /** + * Returns the bodys open value. + * + * @return + */ + /** open value */ + var open: Float = 0f + + /** + * Constructor. + * + * @param x The value on the x-axis + * @param shadowH The (shadow) high value + * @param shadowL The (shadow) low value + * @param open The open value + * @param close The close value + */ + constructor(x: Float, shadowH: Float, shadowL: Float, open: Float, close: Float) : super(x, (shadowH + shadowL) / 2f) { + this.high = shadowH + this.low = shadowL + this.open = open + this.close = close + } + + /** + * Constructor. + * + * @param x The value on the x-axis + * @param shadowH The (shadow) high value + * @param shadowL The (shadow) low value + * @param open + * @param close + * @param data Spot for additional data this Entry represents + */ + constructor( + x: Float, shadowH: Float, shadowL: Float, open: Float, close: Float, + data: Any? + ) : super(x, (shadowH + shadowL) / 2f, data) { + this.high = shadowH + this.low = shadowL + this.open = open + this.close = close + } + + /** + * Constructor. + * + * @param x The value on the x-axis + * @param shadowH The (shadow) high value + * @param shadowL The (shadow) low value + * @param open + * @param close + * @param icon Icon image + */ + constructor( + x: Float, shadowH: Float, shadowL: Float, open: Float, close: Float, + icon: Drawable? + ) : super(x, (shadowH + shadowL) / 2f, icon) { + this.high = shadowH + this.low = shadowL + this.open = open + this.close = close + } + + /** + * Constructor. + * + * @param x The value on the x-axis + * @param shadowH The (shadow) high value + * @param shadowL The (shadow) low value + * @param open + * @param close + * @param icon Icon image + * @param data Spot for additional data this Entry represents + */ + constructor( + x: Float, shadowH: Float, shadowL: Float, open: Float, close: Float, + icon: Drawable?, data: Any? + ) : super(x, (shadowH + shadowL) / 2f, icon, data) { + this.high = shadowH + this.low = shadowL + this.open = open + this.close = close + } + + val shadowRange: Float + /** + * Returns the overall range (difference) between shadow-high and + * shadow-low. + * + * @return + */ + get() = abs(this.high - this.low) + + val bodyRange: Float + /** + * Returns the body size (difference between open and close). + * + * @return + */ + get() = abs(this.open - this.close) + + override fun copy(): CandleEntry { + val c = CandleEntry( + x, this.high, this.low, this.open, + this.close, data + ) + + return c + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java deleted file mode 100644 index fa6dc27573..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java +++ /dev/null @@ -1,822 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.graphics.Typeface; -import android.util.Log; - -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -/** - * Class that holds all relevant data that represents the chart. That involves - * at least one (or more) DataSets, and an array of x-values. - * - * @author Philipp Jahoda - */ -public abstract class ChartData> implements Serializable { - - /** - * maximum y-value in the value array across all axes - */ - protected float mYMax = -Float.MAX_VALUE; - - /** - * the minimum y-value in the value array across all axes - */ - protected float mYMin = Float.MAX_VALUE; - - /** - * maximum x-value in the value array - */ - protected float mXMax = -Float.MAX_VALUE; - - /** - * minimum x-value in the value array - */ - protected float mXMin = Float.MAX_VALUE; - - - protected float mLeftAxisMax = -Float.MAX_VALUE; - - protected float mLeftAxisMin = Float.MAX_VALUE; - - protected float mRightAxisMax = -Float.MAX_VALUE; - - protected float mRightAxisMin = Float.MAX_VALUE; - - /** - * array that holds all DataSets the ChartData object represents - */ - protected List mDataSets; - - /** - * Default constructor. - */ - public ChartData() { - mDataSets = new ArrayList(); - } - - /** - * Constructor taking single or multiple DataSet objects. - * - * @param dataSets - */ - public ChartData(T... dataSets) { - mDataSets = arrayToList(dataSets); - notifyDataChanged(); - } - - /** - * Created because Arrays.asList(...) does not support modification. - * - * @param array - * @return - */ - private List arrayToList(T[] array) { - - List list = new ArrayList<>(); - - for (T set : array) { - list.add(set); - } - - return list; - } - - /** - * constructor for chart data - * - * @param sets the dataset array - */ - public ChartData(List sets) { - this.mDataSets = sets; - notifyDataChanged(); - } - - /** - * Call this method to let the ChartData know that the underlying data has - * changed. Calling this performs all necessary recalculations needed when - * the contained data has changed. - */ - public void notifyDataChanged() { - calcMinMax(); - } - - /** - * Calc minimum and maximum y-values over all DataSets. - * Tell DataSets to recalculate their min and max y-values, this is only needed for autoScaleMinMax. - * - * @param fromX the x-value to start the calculation from - * @param toX the x-value to which the calculation should be performed - */ - public void calcMinMaxY(float fromX, float toX) { - - for (T set : mDataSets) { - set.calcMinMaxY(fromX, toX); - } - - // apply the new data - calcMinMax(); - } - - /** - * Calc minimum and maximum values (both x and y) over all DataSets. - */ - protected void calcMinMax() { - - if (mDataSets == null) - return; - - mYMax = -Float.MAX_VALUE; - mYMin = Float.MAX_VALUE; - mXMax = -Float.MAX_VALUE; - mXMin = Float.MAX_VALUE; - - for (T set : mDataSets) { - calcMinMax(set); - } - - mLeftAxisMax = -Float.MAX_VALUE; - mLeftAxisMin = Float.MAX_VALUE; - mRightAxisMax = -Float.MAX_VALUE; - mRightAxisMin = Float.MAX_VALUE; - - // left axis - T firstLeft = getFirstLeft(mDataSets); - - if (firstLeft != null) { - - mLeftAxisMax = firstLeft.getYMax(); - mLeftAxisMin = firstLeft.getYMin(); - - for (T dataSet : mDataSets) { - if (dataSet.getAxisDependency() == AxisDependency.LEFT) { - if (dataSet.getYMin() < mLeftAxisMin) - mLeftAxisMin = dataSet.getYMin(); - - if (dataSet.getYMax() > mLeftAxisMax) - mLeftAxisMax = dataSet.getYMax(); - } - } - } - - // right axis - T firstRight = getFirstRight(mDataSets); - - if (firstRight != null) { - - mRightAxisMax = firstRight.getYMax(); - mRightAxisMin = firstRight.getYMin(); - - for (T dataSet : mDataSets) { - if (dataSet.getAxisDependency() == AxisDependency.RIGHT) { - if (dataSet.getYMin() < mRightAxisMin) - mRightAxisMin = dataSet.getYMin(); - - if (dataSet.getYMax() > mRightAxisMax) - mRightAxisMax = dataSet.getYMax(); - } - } - } - } - - /** ONLY GETTERS AND SETTERS BELOW THIS */ - - /** - * returns the number of LineDataSets this object contains - * - * @return - */ - public int getDataSetCount() { - if (mDataSets == null) - return 0; - return mDataSets.size(); - } - - /** - * Returns the smallest y-value the data object contains. - * - * @return - */ - public float getYMin() { - return mYMin; - } - - /** - * Returns the minimum y-value for the specified axis. - * - * @param axis - * @return - */ - public float getYMin(AxisDependency axis) { - if (axis == AxisDependency.LEFT) { - - if (mLeftAxisMin == Float.MAX_VALUE) { - return mRightAxisMin; - } else - return mLeftAxisMin; - } else { - if (mRightAxisMin == Float.MAX_VALUE) { - return mLeftAxisMin; - } else - return mRightAxisMin; - } - } - - /** - * Returns the greatest y-value the data object contains. - * - * @return - */ - public float getYMax() { - return mYMax; - } - - /** - * Returns the maximum y-value for the specified axis. - * - * @param axis - * @return - */ - public float getYMax(AxisDependency axis) { - if (axis == AxisDependency.LEFT) { - - if (mLeftAxisMax == -Float.MAX_VALUE) { - return mRightAxisMax; - } else - return mLeftAxisMax; - } else { - if (mRightAxisMax == -Float.MAX_VALUE) { - return mLeftAxisMax; - } else - return mRightAxisMax; - } - } - - /** - * Returns the minimum x-value this data object contains. - * - * @return - */ - public float getXMin() { - return mXMin; - } - - /** - * Returns the maximum x-value this data object contains. - * - * @return - */ - public float getXMax() { - return mXMax; - } - - /** - * Returns all DataSet objects this ChartData object holds. - * - * @return - */ - public List getDataSets() { - return mDataSets; - } - - /** - * Retrieve the index of a DataSet with a specific label from the ChartData. - * Search can be case sensitive or not. IMPORTANT: This method does - * calculations at runtime, do not over-use in performance critical - * situations. - * - * @param dataSets the DataSet array to search - * @param label - * @param ignorecase if true, the search is not case-sensitive - * @return - */ - protected int getDataSetIndexByLabel(List dataSets, String label, - boolean ignorecase) { - - if (ignorecase) { - for (int i = 0; i < dataSets.size(); i++) - if (label.equalsIgnoreCase(dataSets.get(i).getLabel())) - return i; - } else { - for (int i = 0; i < dataSets.size(); i++) - if (label.equals(dataSets.get(i).getLabel())) - return i; - } - - return -1; - } - - /** - * Returns the labels of all DataSets as a string array. - * - * @return - */ - public String[] getDataSetLabels() { - - String[] types = new String[mDataSets.size()]; - - for (int i = 0; i < mDataSets.size(); i++) { - types[i] = mDataSets.get(i).getLabel(); - } - - return types; - } - - /** - * Get the Entry for a corresponding highlight object - * - * @param highlight - * @return the entry that is highlighted - */ - public Entry getEntryForHighlight(Highlight highlight) { - if (highlight.getDataSetIndex() >= mDataSets.size()) - return null; - else { - return mDataSets.get(highlight.getDataSetIndex()).getEntryForXValue(highlight.getX(), highlight.getY()); - } - } - - /** - * Returns the DataSet object with the given label. Search can be case - * sensitive or not. IMPORTANT: This method does calculations at runtime. - * Use with care in performance critical situations. - * - * @param label - * @param ignorecase - * @return - */ - public T getDataSetByLabel(String label, boolean ignorecase) { - - int index = getDataSetIndexByLabel(mDataSets, label, ignorecase); - - if (index < 0 || index >= mDataSets.size()) - return null; - else - return mDataSets.get(index); - } - - public T getDataSetByIndex(int index) { - - if (mDataSets == null || index < 0 || index >= mDataSets.size()) - return null; - - return mDataSets.get(index); - } - - /** - * Adds a DataSet dynamically. - * - * @param d - */ - public void addDataSet(T d) { - - if (d == null) - return; - - calcMinMax(d); - - mDataSets.add(d); - } - - /** - * Removes the given DataSet from this data object. Also recalculates all - * minimum and maximum values. Returns true if a DataSet was removed, false - * if no DataSet could be removed. - * - * @param d - */ - public boolean removeDataSet(T d) { - - if (d == null) - return false; - - boolean removed = mDataSets.remove(d); - - // if a DataSet was removed - if (removed) { - notifyDataChanged(); - } - - return removed; - } - - /** - * Removes the DataSet at the given index in the DataSet array from the data - * object. Also recalculates all minimum and maximum values. Returns true if - * a DataSet was removed, false if no DataSet could be removed. - * - * @param index - */ - public boolean removeDataSet(int index) { - - if (index >= mDataSets.size() || index < 0) - return false; - - T set = mDataSets.get(index); - return removeDataSet(set); - } - - /** - * Adds an Entry to the DataSet at the specified index. - * Entries are added to the end of the list. - * - * @param e - * @param dataSetIndex - */ - public void addEntry(Entry e, int dataSetIndex) { - - if (mDataSets.size() > dataSetIndex && dataSetIndex >= 0) { - - IDataSet set = mDataSets.get(dataSetIndex); - // add the entry to the dataset - if (!set.addEntry(e)) - return; - - calcMinMax(e, set.getAxisDependency()); - - } else { - Log.e("addEntry", "Cannot add Entry because dataSetIndex too high or too low."); - } - } - - /** - * Adjusts the current minimum and maximum values based on the provided Entry object. - * - * @param e - * @param axis - */ - protected void calcMinMax(Entry e, AxisDependency axis) { - - if (mYMax < e.getY()) - mYMax = e.getY(); - if (mYMin > e.getY()) - mYMin = e.getY(); - - if (mXMax < e.getX()) - mXMax = e.getX(); - if (mXMin > e.getX()) - mXMin = e.getX(); - - if (axis == AxisDependency.LEFT) { - - if (mLeftAxisMax < e.getY()) - mLeftAxisMax = e.getY(); - if (mLeftAxisMin > e.getY()) - mLeftAxisMin = e.getY(); - } else { - if (mRightAxisMax < e.getY()) - mRightAxisMax = e.getY(); - if (mRightAxisMin > e.getY()) - mRightAxisMin = e.getY(); - } - } - - /** - * Adjusts the minimum and maximum values based on the given DataSet. - * - * @param d - */ - protected void calcMinMax(T d) { - - if (mYMax < d.getYMax()) - mYMax = d.getYMax(); - if (mYMin > d.getYMin()) - mYMin = d.getYMin(); - - if (mXMax < d.getXMax()) - mXMax = d.getXMax(); - if (mXMin > d.getXMin()) - mXMin = d.getXMin(); - - if (d.getAxisDependency() == AxisDependency.LEFT) { - - if (mLeftAxisMax < d.getYMax()) - mLeftAxisMax = d.getYMax(); - if (mLeftAxisMin > d.getYMin()) - mLeftAxisMin = d.getYMin(); - } else { - if (mRightAxisMax < d.getYMax()) - mRightAxisMax = d.getYMax(); - if (mRightAxisMin > d.getYMin()) - mRightAxisMin = d.getYMin(); - } - } - - /** - * Removes the given Entry object from the DataSet at the specified index. - * - * @param e - * @param dataSetIndex - */ - public boolean removeEntry(Entry e, int dataSetIndex) { - - // entry null, outofbounds - if (e == null || dataSetIndex >= mDataSets.size()) - return false; - - IDataSet set = mDataSets.get(dataSetIndex); - - if (set != null) { - // remove the entry from the dataset - boolean removed = set.removeEntry(e); - - if (removed) { - notifyDataChanged(); - } - - return removed; - } else - return false; - } - - /** - * Removes the Entry object closest to the given DataSet at the - * specified index. Returns true if an Entry was removed, false if no Entry - * was found that meets the specified requirements. - * - * @param xValue - * @param dataSetIndex - * @return - */ - public boolean removeEntry(float xValue, int dataSetIndex) { - - if (dataSetIndex >= mDataSets.size()) - return false; - - IDataSet dataSet = mDataSets.get(dataSetIndex); - Entry e = dataSet.getEntryForXValue(xValue, Float.NaN); - - if (e == null) - return false; - - return removeEntry(e, dataSetIndex); - } - - /** - * Returns the DataSet that contains the provided Entry, or null, if no - * DataSet contains this Entry. - * - * @param e - * @return - */ - public T getDataSetForEntry(Entry e) { - - if (e == null) - return null; - - for (int i = 0; i < mDataSets.size(); i++) { - - T set = mDataSets.get(i); - - for (int j = 0; j < set.getEntryCount(); j++) { - if (e.equalTo(set.getEntryForXValue(e.getX(), e.getY()))) - return set; - } - } - - return null; - } - - /** - * Returns all colors used across all DataSet objects this object - * represents. - * - * @return - */ - public int[] getColors() { - - if (mDataSets == null) - return null; - - int clrcnt = 0; - - for (int i = 0; i < mDataSets.size(); i++) { - clrcnt += mDataSets.get(i).getColors().size(); - } - - int[] colors = new int[clrcnt]; - int cnt = 0; - - for (int i = 0; i < mDataSets.size(); i++) { - - List clrs = mDataSets.get(i).getColors(); - - for (Integer clr : clrs) { - colors[cnt] = clr; - cnt++; - } - } - - return colors; - } - - /** - * Returns the index of the provided DataSet in the DataSet array of this data object, or -1 if it does not exist. - * - * @param dataSet - * @return - */ - public int getIndexOfDataSet(T dataSet) { - return mDataSets.indexOf(dataSet); - } - - /** - * Returns the first DataSet from the datasets-array that has it's dependency on the left axis. - * Returns null if no DataSet with left dependency could be found. - * - * @return - */ - protected T getFirstLeft(List sets) { - for (T dataSet : sets) { - if (dataSet.getAxisDependency() == AxisDependency.LEFT) - return dataSet; - } - return null; - } - - /** - * Returns the first DataSet from the datasets-array that has it's dependency on the right axis. - * Returns null if no DataSet with right dependency could be found. - * - * @return - */ - public T getFirstRight(List sets) { - for (T dataSet : sets) { - if (dataSet.getAxisDependency() == AxisDependency.RIGHT) - return dataSet; - } - return null; - } - - /** - * Sets a custom IValueFormatter for all DataSets this data object contains. - * - * @param f - */ - public void setValueFormatter(IValueFormatter f) { - if (f == null) - return; - else { - for (IDataSet set : mDataSets) { - set.setValueFormatter(f); - } - } - } - - /** - * Sets the color of the value-text (color in which the value-labels are - * drawn) for all DataSets this data object contains. - * - * @param color - */ - public void setValueTextColor(int color) { - for (IDataSet set : mDataSets) { - set.setValueTextColor(color); - } - } - - /** - * Sets the same list of value-colors for all DataSets this - * data object contains. - * - * @param colors - */ - public void setValueTextColors(List colors) { - for (IDataSet set : mDataSets) { - set.setValueTextColors(colors); - } - } - - /** - * Sets the Typeface for all value-labels for all DataSets this data object - * contains. - * - * @param tf - */ - public void setValueTypeface(Typeface tf) { - for (IDataSet set : mDataSets) { - set.setValueTypeface(tf); - } - } - - /** - * Sets the size (in dp) of the value-text for all DataSets this data object - * contains. - * - * @param size - */ - public void setValueTextSize(float size) { - for (IDataSet set : mDataSets) { - set.setValueTextSize(size); - } - } - - /** - * Enables / disables drawing values (value-text) for all DataSets this data - * object contains. - * - * @param enabled - */ - public void setDrawValues(boolean enabled) { - for (IDataSet set : mDataSets) { - set.setDrawValues(enabled); - } - } - - /** - * Enables / disables highlighting values for all DataSets this data object - * contains. If set to true, this means that values can - * be highlighted programmatically or by touch gesture. - */ - public void setHighlightEnabled(boolean enabled) { - for (IDataSet set : mDataSets) { - set.setHighlightEnabled(enabled); - } - } - - /** - * Returns true if highlighting of all underlying values is enabled, false - * if not. - * - * @return - */ - public boolean isHighlightEnabled() { - for (IDataSet set : mDataSets) { - if (!set.isHighlightEnabled()) - return false; - } - return true; - } - - /** - * Clears this data object from all DataSets and removes all Entries. Don't - * forget to invalidate the chart after this. - */ - public void clearValues() { - if (mDataSets != null) { - mDataSets.clear(); - } - notifyDataChanged(); - } - - /** - * Checks if this data object contains the specified DataSet. Returns true - * if so, false if not. - * - * @param dataSet - * @return - */ - public boolean contains(T dataSet) { - - for (T set : mDataSets) { - if (set.equals(dataSet)) - return true; - } - - return false; - } - - /** - * Returns the total entry count across all DataSet objects this data object contains. - * - * @return - */ - public int getEntryCount() { - - int count = 0; - - for (T set : mDataSets) { - count += set.getEntryCount(); - } - - return count; - } - - /** - * Returns the DataSet object with the maximum number of entries or null if there are no DataSets. - * - * @return - */ - public T getMaxEntryCountSet() { - - if (mDataSets == null || mDataSets.isEmpty()) - return null; - - T max = mDataSets.get(0); - - for (T set : mDataSets) { - - if (set.getEntryCount() > max.getEntryCount()) - max = set; - } - - return max; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.kt new file mode 100644 index 0000000000..4f677919b8 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.kt @@ -0,0 +1,703 @@ +package com.github.mikephil.charting.data + +import android.graphics.Typeface +import android.util.Log +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.formatter.IValueFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IDataSet +import java.io.Serializable + +/** + * Class that holds all relevant data that represents the chart. That involves + * at least one (or more) DataSets, and an array of x-values. + * + * @author Philipp Jahoda + */ +abstract class ChartData>( + /** + * Returns all DataSet objects this ChartData object holds. + * + * @return + */ + /** + * array that holds all DataSets the ChartData object represents + */ + open val dataSets: MutableList = arrayListOf(), +) : Serializable { + /** + * Returns the greatest y-value the data object contains. + * + * @return + */ + /** + * maximum y-value in the value array across all axes + */ + var yMax: Float = -Float.Companion.MAX_VALUE + protected set + + /** + * Returns the smallest y-value the data object contains. + * + * @return + */ + /** + * the minimum y-value in the value array across all axes + */ + var yMin: Float = Float.Companion.MAX_VALUE + protected set + + /** + * Returns the maximum x-value this data object contains. + * + * @return + */ + /** + * maximum x-value in the value array + */ + var xMax: Float = -Float.Companion.MAX_VALUE + protected set + + /** + * Returns the minimum x-value this data object contains. + * + * @return + */ + /** + * minimum x-value in the value array + */ + var xMin: Float = Float.Companion.MAX_VALUE + protected set + + + protected var mLeftAxisMax: Float = -Float.Companion.MAX_VALUE + + protected var mLeftAxisMin: Float = Float.Companion.MAX_VALUE + + protected var mRightAxisMax: Float = -Float.Companion.MAX_VALUE + + protected var mRightAxisMin: Float = Float.Companion.MAX_VALUE + + /** + * Constructor taking single or multiple DataSet objects. + * + * @param dataSets + */ + constructor(vararg dataSets: T): this(dataSets = dataSets.toMutableList()) + + init { + if (dataSets.isNotEmpty()) { + notifyDataChanged() + } + } + + /** + * Created because Arrays.asList(...) does not support modification. + * + * @param array + * @return + */ + private fun arrayToList(array: Array): MutableList { + val list: MutableList = ArrayList() + + for (set in array) { + list.add(set) + } + + return list + } + + /** + * Call this method to let the ChartData know that the underlying data has + * changed. Calling this performs all necessary recalculations needed when + * the contained data has changed. + */ + open fun notifyDataChanged() { + calcMinMax() + } + + /** + * Calc minimum and maximum y-values over all DataSets. + * Tell DataSets to recalculate their min and max y-values, this is only needed for autoScaleMinMax. + * + * @param fromX the x-value to start the calculation from + * @param toX the x-value to which the calculation should be performed + */ + fun calcMinMaxY(fromX: Float, toX: Float) { + for (set in this.dataSets) { + set.calcMinMaxY(fromX, toX) + } + + // apply the new data + calcMinMax() + } + + /** + * Calc minimum and maximum values (both x and y) over all DataSets. + */ + open fun calcMinMax() { + this.yMax = -Float.Companion.MAX_VALUE + this.yMin = Float.Companion.MAX_VALUE + this.xMax = -Float.Companion.MAX_VALUE + this.xMin = Float.Companion.MAX_VALUE + + for (set in this.dataSets) { + calcMinMax(set) + } + + mLeftAxisMax = -Float.Companion.MAX_VALUE + mLeftAxisMin = Float.Companion.MAX_VALUE + mRightAxisMax = -Float.Companion.MAX_VALUE + mRightAxisMin = Float.Companion.MAX_VALUE + + // left axis + val firstLeft = getFirstLeft(this.dataSets) + + if (firstLeft != null) { + mLeftAxisMax = firstLeft.yMax + mLeftAxisMin = firstLeft.yMin + + for (dataSet in this.dataSets) { + if (dataSet.axisDependency == AxisDependency.LEFT) { + if (dataSet.yMin < mLeftAxisMin) mLeftAxisMin = dataSet.yMin + + if (dataSet.yMax > mLeftAxisMax) mLeftAxisMax = dataSet.yMax + } + } + } + + // right axis + val firstRight = getFirstRight(this.dataSets) + + if (firstRight != null) { + mRightAxisMax = firstRight.yMax + mRightAxisMin = firstRight.yMin + + for (dataSet in this.dataSets) { + if (dataSet.axisDependency == AxisDependency.RIGHT) { + if (dataSet.yMin < mRightAxisMin) mRightAxisMin = dataSet.yMin + + if (dataSet.yMax > mRightAxisMax) mRightAxisMax = dataSet.yMax + } + } + } + } + + /** ONLY GETTERS AND SETTERS BELOW THIS */ + val dataSetCount: Int + /** + * returns the number of LineDataSets this object contains + * + * @return + */ + get() { + return dataSets.size + } + + /** + * Returns the minimum y-value for the specified axis. + * + * @param axis + * @return + */ + fun getYMin(axis: AxisDependency?): Float { + return if (axis == AxisDependency.LEFT) { + if (mLeftAxisMin == Float.Companion.MAX_VALUE) { + mRightAxisMin + } else mLeftAxisMin + } else { + if (mRightAxisMin == Float.Companion.MAX_VALUE) { + mLeftAxisMin + } else mRightAxisMin + } + } + + /** + * Returns the maximum y-value for the specified axis. + * + * @param axis + * @return + */ + fun getYMax(axis: AxisDependency?): Float { + return if (axis == AxisDependency.LEFT) { + if (mLeftAxisMax == -Float.Companion.MAX_VALUE) { + mRightAxisMax + } else mLeftAxisMax + } else { + if (mRightAxisMax == -Float.Companion.MAX_VALUE) { + mLeftAxisMax + } else mRightAxisMax + } + } + + /** + * Retrieve the index of a DataSet with a specific label from the ChartData. + * Search can be case sensitive or not. IMPORTANT: This method does + * calculations at runtime, do not over-use in performance critical + * situations. + * + * @param dataSets the DataSet array to search + * @param label + * @param ignorecase if true, the search is not case-sensitive + * @return + */ + protected fun getDataSetIndexByLabel( + dataSets: MutableList, label: String, + ignorecase: Boolean + ): Int { + if (ignorecase) { + for (i in dataSets.indices) if (label.equals(dataSets[i].label, ignoreCase = true)) return i + } else { + for (i in dataSets.indices) if (label == dataSets[i].label) return i + } + + return -1 + } + + val dataSetLabels: Array + /** + * Returns the labels of all DataSets as a string array. + * + * @return + */ + get() { + val types = arrayOfNulls(dataSets.size) + + for (i in dataSets.indices) { + types[i] = dataSets[i].label + } + + return types + } + + /** + * Get the Entry for a corresponding highlight object + * + * @param highlight + * @return the entry that is highlighted + */ + open fun getEntryForHighlight(highlight: Highlight): E? { + return if (highlight.dataSetIndex >= dataSets.size) null + else { + dataSets[highlight.dataSetIndex].getEntryForXValue(highlight.x, highlight.y) + } + } + + /** + * Returns the DataSet object with the given label. Search can be case + * sensitive or not. IMPORTANT: This method does calculations at runtime. + * Use with care in performance critical situations. + * + * @param label + * @param ignorecase + * @return + */ + open fun getDataSetByLabel(label: String, ignorecase: Boolean): T? { + val index = getDataSetIndexByLabel(this.dataSets, label, ignorecase) + + return if (index < 0 || index >= dataSets.size) null + else dataSets[index] + } + + open fun getDataSetByIndex(index: Int): T { + if (index < 0 || index >= dataSets.size) throw ArrayIndexOutOfBoundsException(index) + + return dataSets[index] + } + + /** + * Adds a DataSet dynamically. + * + * @param d + */ + fun addDataSet(d: T?) { + if (d == null) return + + calcMinMax(d) + + dataSets.add(d) + } + + /** + * Removes the given DataSet from this data object. Also recalculates all + * minimum and maximum values. Returns true if a DataSet was removed, false + * if no DataSet could be removed. + * + * @param d + */ + open fun removeDataSet(d: IDataSet?): Boolean { + if (d == null) return false + + val removed = dataSets.removeAll { it == d } + + // if a DataSet was removed + if (removed) { + notifyDataChanged() + } + + return removed + } + + /** + * Removes the DataSet at the given index in the DataSet array from the data + * object. Also recalculates all minimum and maximum values. Returns true if + * a DataSet was removed, false if no DataSet could be removed. + * + * @param index + */ + open fun removeDataSet(index: Int): Boolean { + if (index >= dataSets.size || index < 0) return false + + dataSets.removeAt(index) + notifyDataChanged() + + return true + } + + /** + * Adds an Entry to the DataSet at the specified index. + * Entries are added to the end of the list. + * + * @param e + * @param dataSetIndex + */ + fun addEntry(e: E, dataSetIndex: Int) { + if (dataSets.size > dataSetIndex && dataSetIndex >= 0) { + val set = dataSets[dataSetIndex] + // add the entry to the dataset + if (!set.addEntry(e)) return + + calcMinMax(e, set.axisDependency) + } else { + Log.e("addEntry", "Cannot add Entry because dataSetIndex too high or too low.") + } + } + + /** + * Adjusts the current minimum and maximum values based on the provided Entry object. + * + * @param e + * @param axis + */ + protected fun calcMinMax(e: E, axis: AxisDependency?) { + if (this.yMax < e.y) this.yMax = e.y + if (this.yMin > e.y) this.yMin = e.y + + if (this.xMax < e.x) this.xMax = e.x + if (this.xMin > e.x) this.xMin = e.x + + if (axis == AxisDependency.LEFT) { + if (mLeftAxisMax < e.y) mLeftAxisMax = e.y + if (mLeftAxisMin > e.y) mLeftAxisMin = e.y + } else { + if (mRightAxisMax < e.y) mRightAxisMax = e.y + if (mRightAxisMin > e.y) mRightAxisMin = e.y + } + } + + /** + * Adjusts the minimum and maximum values based on the given DataSet. + * + * @param d + */ + protected fun calcMinMax(d: T) { + if (this.yMax < d.yMax) this.yMax = d.yMax + if (this.yMin > d.yMin) this.yMin = d.yMin + + if (this.xMax < d.xMax) this.xMax = d.xMax + if (this.xMin > d.xMin) this.xMin = d.xMin + + if (d.axisDependency == AxisDependency.LEFT) { + if (mLeftAxisMax < d.yMax) mLeftAxisMax = d.yMax + if (mLeftAxisMin > d.yMin) mLeftAxisMin = d.yMin + } else { + if (mRightAxisMax < d.yMax) mRightAxisMax = d.yMax + if (mRightAxisMin > d.yMin) mRightAxisMin = d.yMin + } + } + + /** + * Removes the given Entry object from the DataSet at the specified index. + * + * @param e + * @param dataSetIndex + */ + open fun removeEntry(e: E?, dataSetIndex: Int): Boolean { + // entry null, outofbounds + + if (e == null || dataSetIndex >= dataSets.size) return false + + val set = dataSets[dataSetIndex] + + // remove the entry from the dataset + val removed: Boolean = set.removeEntry(e) + + if (removed) { + notifyDataChanged() + } + + return removed + } + + /** + * Removes the Entry object closest to the given DataSet at the + * specified index. Returns true if an Entry was removed, false if no Entry + * was found that meets the specified requirements. + * + * @param xValue + * @param dataSetIndex + * @return + */ + open fun removeEntry(xValue: Float, dataSetIndex: Int): Boolean { + if (dataSetIndex >= dataSets.size) return false + + val dataSet = dataSets[dataSetIndex] + val e = dataSet.getEntryForXValue(xValue, Float.Companion.NaN) + + if (e == null) return false + + return removeEntry(e, dataSetIndex) + } + + /** + * Returns the DataSet that contains the provided Entry, or null, if no + * DataSet contains this Entry. + * + * @param e + * @return + */ + fun getDataSetForEntry(e: E?): T? { + if (e == null) return null + + for (i in dataSets.indices) { + val set = dataSets[i] + + repeat(set.entryCount + 1) { + if (e.equalTo(set.getEntryForXValue(e.x, e.y))) return set + } + } + + return null + } + + val colors: IntArray? + /** + * Returns all colors used across all DataSet objects this object + * represents. + * + * @return + */ + get() { + var clrcnt = 0 + + for (i in dataSets.indices) { + clrcnt += dataSets[i].colors.size + } + + val colors = IntArray(clrcnt) + var cnt = 0 + + for (i in dataSets.indices) { + val clrs = dataSets[i].colors + + for (clr in clrs) { + colors[cnt] = clr + cnt++ + } + } + + return colors + } + + /** + * Returns the index of the provided DataSet in the DataSet array of this data object, or -1 if it does not exist. + * + * @param dataSet + * @return + */ + fun getIndexOfDataSet(dataSet: T?): Int { + return dataSets.indexOf(dataSet) + } + + /** + * Returns the first DataSet from the datasets-array that has it's dependency on the left axis. + * Returns null if no DataSet with left dependency could be found. + * + * @return + */ + protected fun getFirstLeft(sets: MutableList): T? { + for (dataSet in sets) { + if (dataSet.axisDependency == AxisDependency.LEFT) return dataSet + } + return null + } + + /** + * Returns the first DataSet from the datasets-array that has it's dependency on the right axis. + * Returns null if no DataSet with right dependency could be found. + * + * @return + */ + fun getFirstRight(sets: MutableList): T? { + for (dataSet in sets) { + if (dataSet.axisDependency == AxisDependency.RIGHT) return dataSet + } + return null + } + + /** + * Sets a custom IValueFormatter for all DataSets this data object contains. + * + * @param f + */ + fun setValueFormatter(f: IValueFormatter?) { + if (f == null) return + else { + for (set in this.dataSets) { + set.valueFormatter = f + } + } + } + + /** + * Sets the color of the value-text (color in which the value-labels are + * drawn) for all DataSets this data object contains. + * + * @param color + */ + fun setValueTextColor(color: Int) { + for (set in this.dataSets) { + set.valueTextColor = color + } + } + + /** + * Sets the same list of value-colors for all DataSets this + * data object contains. + * + * @param colors + */ + fun setValueTextColors(colors: MutableList) { + for (set in this.dataSets) { + set.setValueTextColors(colors) + } + } + + /** + * Sets the Typeface for all value-labels for all DataSets this data object + * contains. + * + * @param tf + */ + fun setValueTypeface(tf: Typeface?) { + for (set in this.dataSets) { + set.valueTypeface = tf + } + } + + /** + * Sets the size (in dp) of the value-text for all DataSets this data object + * contains. + * + * @param size + */ + fun setValueTextSize(size: Float) { + for (set in this.dataSets) { + set.valueTextSize = size + } + } + + /** + * Enables / disables drawing values (value-text) for all DataSets this data + * object contains. + * + * @param enabled + */ + fun setDrawValues(enabled: Boolean) { + for (set in this.dataSets) { + set.isDrawValuesEnabled = enabled + } + } + + var isHighlightEnabled: Boolean + /** + * Returns true if highlighting of all underlying values is enabled, false + * if not. + * + * @return + */ + get() { + for (set in this.dataSets) { + if (!set.isHighlightEnabled) return false + } + return true + } + /** + * Enables / disables highlighting values for all DataSets this data object + * contains. If set to true, this means that values can + * be highlighted programmatically or by touch gesture. + */ + set(enabled) { + for (set in this.dataSets) { + set.isHighlightEnabled = enabled + } + } + + /** + * Clears this data object from all DataSets and removes all Entries. Don't + * forget to invalidate the chart after this. + */ + fun clearValues() { + dataSets.clear() + notifyDataChanged() + } + + /** + * Checks if this data object contains the specified DataSet. Returns true + * if so, false if not. + * + * @param dataSet + * @return + */ + fun contains(dataSet: T?): Boolean { + for (set in this.dataSets) { + if (set == dataSet) return true + } + + return false + } + + val entryCount: Int + /** + * Returns the total entry count across all DataSet objects this data object contains. + * + * @return + */ + get() { + var count = 0 + + for (set in this.dataSets) { + count += set.entryCount + } + + return count + } + + val maxEntryCountSet: T? + /** + * Returns the DataSet object with the maximum number of entries or null if there are no DataSets. + * + * @return + */ + get() { + if (dataSets.isEmpty()) return null + + var max = dataSets[0] + + for (set in this.dataSets) { + if (set.entryCount > max.entryCount) max = set + } + + return max + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java deleted file mode 100644 index 0b36aa3bef..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java +++ /dev/null @@ -1,272 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.util.Log; - -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet; - -import java.util.ArrayList; -import java.util.List; - -/** - * Data object that allows the combination of Line-, Bar-, Scatter-, Bubble- and - * CandleData. Used in the CombinedChart class. - * - * @author Philipp Jahoda - */ -public class CombinedData extends BarLineScatterCandleBubbleData> { - - private LineData mLineData; - private BarData mBarData; - private ScatterData mScatterData; - private CandleData mCandleData; - private BubbleData mBubbleData; - - public CombinedData() { - super(); - } - - public void setData(LineData data) { - mLineData = data; - notifyDataChanged(); - } - - public void setData(BarData data) { - mBarData = data; - notifyDataChanged(); - } - - public void setData(ScatterData data) { - mScatterData = data; - notifyDataChanged(); - } - - public void setData(CandleData data) { - mCandleData = data; - notifyDataChanged(); - } - - public void setData(BubbleData data) { - mBubbleData = data; - notifyDataChanged(); - } - - @Override - public void calcMinMax() { - - if(mDataSets == null){ - mDataSets = new ArrayList<>(); - } - mDataSets.clear(); - - mYMax = -Float.MAX_VALUE; - mYMin = Float.MAX_VALUE; - mXMax = -Float.MAX_VALUE; - mXMin = Float.MAX_VALUE; - - mLeftAxisMax = -Float.MAX_VALUE; - mLeftAxisMin = Float.MAX_VALUE; - mRightAxisMax = -Float.MAX_VALUE; - mRightAxisMin = Float.MAX_VALUE; - - List allData = getAllData(); - - for (ChartData data : allData) { - - data.calcMinMax(); - - List> sets = data.getDataSets(); - mDataSets.addAll(sets); - - if (data.getYMax() > mYMax) - mYMax = data.getYMax(); - - if (data.getYMin() < mYMin) - mYMin = data.getYMin(); - - if (data.getXMax() > mXMax) - mXMax = data.getXMax(); - - if (data.getXMin() < mXMin) - mXMin = data.getXMin(); - - for (IBarLineScatterCandleBubbleDataSet dataset : sets) { - if (dataset.getAxisDependency() == YAxis.AxisDependency.LEFT) { - if (dataset.getYMax() > mLeftAxisMax) { - mLeftAxisMax = dataset.getYMax(); - } - - if (dataset.getYMin() < mLeftAxisMin) { - mLeftAxisMin = dataset.getYMin(); - } - } - else { - if (dataset.getYMax() > mRightAxisMax) { - mRightAxisMax = dataset.getYMax(); - } - - if (dataset.getYMin() < mRightAxisMin) { - mRightAxisMin = dataset.getYMin(); - } - } - } - } - } - - public BubbleData getBubbleData() { - return mBubbleData; - } - - public LineData getLineData() { - return mLineData; - } - - public BarData getBarData() { - return mBarData; - } - - public ScatterData getScatterData() { - return mScatterData; - } - - public CandleData getCandleData() { - return mCandleData; - } - - /** - * Returns all data objects in row: line-bar-scatter-candle-bubble if not null. - * - * @return - */ - public List getAllData() { - - List data = new ArrayList(); - if (mLineData != null) - data.add(mLineData); - if (mBarData != null) - data.add(mBarData); - if (mScatterData != null) - data.add(mScatterData); - if (mCandleData != null) - data.add(mCandleData); - if (mBubbleData != null) - data.add(mBubbleData); - - return data; - } - - public BarLineScatterCandleBubbleData getDataByIndex(int index) { - return getAllData().get(index); - } - - @Override - public void notifyDataChanged() { - if (mLineData != null) - mLineData.notifyDataChanged(); - if (mBarData != null) - mBarData.notifyDataChanged(); - if (mCandleData != null) - mCandleData.notifyDataChanged(); - if (mScatterData != null) - mScatterData.notifyDataChanged(); - if (mBubbleData != null) - mBubbleData.notifyDataChanged(); - - calcMinMax(); // recalculate everything - } - - /** - * Get the Entry for a corresponding highlight object - * - * @param highlight - * @return the entry that is highlighted - */ - @Override - public Entry getEntryForHighlight(Highlight highlight) { - - if (highlight.getDataIndex() >= getAllData().size()) - return null; - - ChartData data = getDataByIndex(highlight.getDataIndex()); - - if (highlight.getDataSetIndex() >= data.getDataSetCount()) - return null; - - // The value of the highlighted entry could be NaN - - // if we are not interested in highlighting a specific value. - - List entries = data.getDataSetByIndex(highlight.getDataSetIndex()) - .getEntriesForXValue(highlight.getX()); - for (Entry entry : entries) - if (entry.getY() == highlight.getY() || - Float.isNaN(highlight.getY())) - return entry; - - return null; - } - - /** - * Get dataset for highlight - * - * @param highlight current highlight - * @return dataset related to highlight - */ - public IBarLineScatterCandleBubbleDataSet getDataSetByHighlight(Highlight highlight) { - if (highlight.getDataIndex() >= getAllData().size()) - return null; - - BarLineScatterCandleBubbleData data = getDataByIndex(highlight.getDataIndex()); - - if (highlight.getDataSetIndex() >= data.getDataSetCount()) - return null; - - return (IBarLineScatterCandleBubbleDataSet) - data.getDataSets().get(highlight.getDataSetIndex()); - } - - public int getDataIndex(ChartData data) { - return getAllData().indexOf(data); - } - - @Override - public boolean removeDataSet(IBarLineScatterCandleBubbleDataSet d) { - - List datas = getAllData(); - - boolean success = false; - - for (ChartData data : datas) { - - success = data.removeDataSet(d); - - if (success) { - break; - } - } - - return success; - } - - @Deprecated - @Override - public boolean removeDataSet(int index) { - Log.e("MPAndroidChart", "removeDataSet(int index) not supported for CombinedData"); - return false; - } - - @Deprecated - @Override - public boolean removeEntry(Entry e, int dataSetIndex) { - Log.e("MPAndroidChart", "removeEntry(...) not supported for CombinedData"); - return false; - } - - @Deprecated - @Override - public boolean removeEntry(float xValue, int dataSetIndex) { - Log.e("MPAndroidChart", "removeEntry(...) not supported for CombinedData"); - return false; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.kt new file mode 100644 index 0000000000..4636bd4d58 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.kt @@ -0,0 +1,209 @@ +package com.github.mikephil.charting.data + +import android.util.Log +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet +import com.github.mikephil.charting.interfaces.datasets.IDataSet +import java.lang.Float +import kotlin.Boolean +import kotlin.Deprecated +import kotlin.Int + +/** + * Data object that allows the combination of Line-, Bar-, Scatter-, Bubble- and + * CandleData. Used in the CombinedChart class. + * + * @author Philipp Jahoda + */ +class CombinedData : BarLineScatterCandleBubbleData>() { + var lineData: LineData? = null + private set + var barData: BarData? = null + private set + var scatterData: ScatterData? = null + private set + var candleData: CandleData? = null + private set + var bubbleData: BubbleData? = null + private set + + fun setData(data: LineData?) { + this.lineData = data + notifyDataChanged() + } + + fun setData(data: BarData?) { + this.barData = data + notifyDataChanged() + } + + fun setData(data: ScatterData?) { + this.scatterData = data + notifyDataChanged() + } + + fun setData(data: CandleData?) { + this.candleData = data + notifyDataChanged() + } + + fun setData(data: BubbleData?) { + this.bubbleData = data + notifyDataChanged() + } + + override fun calcMinMax() { + dataSets.clear() + + yMax = -Float.MAX_VALUE + yMin = Float.MAX_VALUE + xMax = -Float.MAX_VALUE + xMin = Float.MAX_VALUE + + mLeftAxisMax = -Float.MAX_VALUE + mLeftAxisMin = Float.MAX_VALUE + mRightAxisMax = -Float.MAX_VALUE + mRightAxisMin = Float.MAX_VALUE + + val allData = this.allData + + for (data in allData) { + data.calcMinMax() + + @Suppress("UNCHECKED_CAST") + dataSets.addAll(data.dataSets as Collection>) + + if (data.yMax > yMax) yMax = data.yMax + + if (data.yMin < yMin) yMin = data.yMin + + if (data.xMax > xMax) xMax = data.xMax + + if (data.xMin < xMin) xMin = data.xMin + + for (dataset in dataSets) { + if (dataset.axisDependency == AxisDependency.LEFT) { + if (dataset.yMax > mLeftAxisMax) { + mLeftAxisMax = dataset.yMax + } + + if (dataset.yMin < mLeftAxisMin) { + mLeftAxisMin = dataset.yMin + } + } else { + if (dataset.yMax > mRightAxisMax) { + mRightAxisMax = dataset.yMax + } + + if (dataset.yMin < mRightAxisMin) { + mRightAxisMin = dataset.yMin + } + } + } + } + } + + val allData + /** + * Returns all data objects in row: line-bar-scatter-candle-bubble if not null. + * + * @return + */ + get() = listOfNotNull( + this.lineData, + this.barData, + this.scatterData, + this.candleData, + this.bubbleData, + ) + + fun getDataByIndex(index: Int) = this.allData[index] + + override fun notifyDataChanged() { + lineData?.notifyDataChanged() + barData?.notifyDataChanged() + candleData?.notifyDataChanged() + scatterData?.notifyDataChanged() + bubbleData?.notifyDataChanged() + + calcMinMax() // recalculate everything + } + + /** + * Get the Entry for a corresponding highlight object + * + * @param highlight + * @return the entry that is highlighted + */ + override fun getEntryForHighlight(highlight: Highlight): Entry? { + if (highlight.dataIndex >= this.allData.size) return null + + val data: ChartData<*, *> = getDataByIndex(highlight.dataIndex) + + if (highlight.dataSetIndex >= data.dataSetCount) return null + + // The value of the highlighted entry could be NaN - + // if we are not interested in highlighting a specific value. + val entries = data.getDataSetByIndex(highlight.dataSetIndex).getEntriesForXValue(highlight.x) + for (entry in entries) if (entry.y == highlight.y || + Float.isNaN(highlight.y) + ) return entry + + return null + } + + /** + * Get dataset for highlight + * + * @param highlight current highlight + * @return dataset related to highlight + */ + fun getDataSetByHighlight(highlight: Highlight): IBarLineScatterCandleBubbleDataSet? { + if (highlight.dataIndex >= this.allData.size) return null + + val data = getDataByIndex(highlight.dataIndex) + + if (highlight.dataSetIndex >= data.dataSetCount) return null + + return data.dataSets[highlight.dataSetIndex] + } + + fun getDataIndex(data: BarLineScatterCandleBubbleData>): Int { + return this.allData.indexOf(data) + } + + override fun removeDataSet(d: IDataSet?): Boolean { + val datas = this.allData + + var success = false + + for (data in datas) { + success = data.removeDataSet(d) + + if (success) { + break + } + } + + return success + } + + @Deprecated("") + override fun removeDataSet(index: Int): Boolean { + Log.e("MPAndroidChart", "removeDataSet(int index) not supported for CombinedData") + return false + } + + @Deprecated("") + override fun removeEntry(e: Entry?, dataSetIndex: Int): Boolean { + Log.e("MPAndroidChart", "removeEntry(...) not supported for CombinedData") + return false + } + + @Deprecated("") + override fun removeEntry(xValue: kotlin.Float, dataSetIndex: Int): Boolean { + Log.e("MPAndroidChart", "removeEntry(...) not supported for CombinedData") + return false + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.java deleted file mode 100644 index 56fb0d924f..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.java +++ /dev/null @@ -1,475 +0,0 @@ - -package com.github.mikephil.charting.data; - -import java.io.Serializable; -import java.util.ArrayList; -import java.util.List; - -/** - * The DataSet class represents one group or type of entries (Entry) in the - * Chart that belong together. It is designed to logically separate different - * groups of values inside the Chart (e.g. the values for a specific line in the - * LineChart, or the values of a specific group of bars in the BarChart). - * - * @author Philipp Jahoda - */ -public abstract class DataSet extends BaseDataSet implements Serializable { - - /** - * the entries that this DataSet represents / holds together - */ - protected List mEntries; - - /** - * maximum y-value in the value array - */ - protected float mYMax = -Float.MAX_VALUE; - - /** - * minimum y-value in the value array - */ - protected float mYMin = Float.MAX_VALUE; - - /** - * maximum x-value in the value array - */ - protected float mXMax = -Float.MAX_VALUE; - - /** - * minimum x-value in the value array - */ - protected float mXMin = Float.MAX_VALUE; - - - /** - * Creates a new DataSet object with the given values (entries) it represents. Also, a - * label that describes the DataSet can be specified. The label can also be - * used to retrieve the DataSet from a ChartData object. - * - * @param entries - * @param label - */ - public DataSet(List entries, String label) { - super(label); - this.mEntries = entries; - - if (mEntries == null) - mEntries = new ArrayList(); - - calcMinMax(); - } - - @Override - public void calcMinMax() { - - mYMax = -Float.MAX_VALUE; - mYMin = Float.MAX_VALUE; - mXMax = -Float.MAX_VALUE; - mXMin = Float.MAX_VALUE; - - if (mEntries == null || mEntries.isEmpty()) - return; - - for (T e : mEntries) { - calcMinMax(e); - } - } - - @Override - public void calcMinMaxY(float fromX, float toX) { - mYMax = -Float.MAX_VALUE; - mYMin = Float.MAX_VALUE; - - if (mEntries == null || mEntries.isEmpty()) - return; - - int indexFrom = getEntryIndex(fromX, Float.NaN, Rounding.DOWN); - int indexTo = getEntryIndex(toX, Float.NaN, Rounding.UP); - - if (indexTo < indexFrom) return; - - for (int i = indexFrom; i <= indexTo; i++) { - - // only recalculate y - calcMinMaxY(mEntries.get(i)); - } - } - - /** - * Updates the min and max x and y value of this DataSet based on the given Entry. - * - * @param e - */ - protected void calcMinMax(T e) { - - if (e == null) - return; - - calcMinMaxX(e); - - calcMinMaxY(e); - } - - protected void calcMinMaxX(T e) { - - if (e.getX() < mXMin) - mXMin = e.getX(); - - if (e.getX() > mXMax) - mXMax = e.getX(); - } - - protected void calcMinMaxY(T e) { - - if (e.getY() < mYMin) - mYMin = e.getY(); - - if (e.getY() > mYMax) - mYMax = e.getY(); - } - - @Override - public int getEntryCount() { - return mEntries.size(); - } - - /** - * This method is deprecated. - * Use getEntries() instead. - * - * @return - */ - @Deprecated - public List getValues() { - return mEntries; - } - - /** - * Returns the array of entries that this DataSet represents. - * - * @return - */ - public List getEntries() { - return mEntries; - } - - /** - * This method is deprecated. - * Use setEntries(...) instead. - * - * @param values - */ - @Deprecated - public void setValues(List values) { - setEntries(values); - } - - /** - * Sets the array of entries that this DataSet represents, and calls notifyDataSetChanged() - * - * @return - */ - public void setEntries(List entries) { - mEntries = entries; - notifyDataSetChanged(); - } - - /** - * Provides an exact copy of the DataSet this method is used on. - * - * @return - */ - public abstract DataSet copy(); - - /** - * @param dataSet - */ - protected void copy(DataSet dataSet) { - super.copy(dataSet); - } - - @Override - public String toString() { - StringBuffer buffer = new StringBuffer(); - buffer.append(toSimpleString()); - for (int i = 0; i < mEntries.size(); i++) { - buffer.append(mEntries.get(i).toString() + " "); - } - return buffer.toString(); - } - - /** - * Returns a simple string representation of the DataSet with the type and - * the number of Entries. - * - * @return - */ - public String toSimpleString() { - StringBuffer buffer = new StringBuffer(); - buffer.append("DataSet, label: " + (getLabel() == null ? "" : getLabel()) + ", entries: " + mEntries.size() + - "\n"); - return buffer.toString(); - } - - @Override - public float getYMin() { - return mYMin; - } - - @Override - public float getYMax() { - return mYMax; - } - - @Override - public float getXMin() { - return mXMin; - } - - @Override - public float getXMax() { - return mXMax; - } - - @Override - public void addEntryOrdered(T e) { - - if (e == null) - return; - - if (mEntries == null) { - mEntries = new ArrayList(); - } - - calcMinMax(e); - - if (mEntries.size() > 0 && mEntries.get(mEntries.size() - 1).getX() > e.getX()) { - int closestIndex = getEntryIndex(e.getX(), e.getY(), Rounding.UP); - mEntries.add(closestIndex, e); - } else { - mEntries.add(e); - } - } - - @Override - public void clear() { - mEntries.clear(); - notifyDataSetChanged(); - } - - @Override - public boolean addEntry(T e) { - - if (e == null) - return false; - - List values = getEntries(); - if (values == null) { - values = new ArrayList<>(); - } - - calcMinMax(e); - - // add the entry - return values.add(e); - } - - @Override - public boolean removeEntry(T e) { - - if (e == null) - return false; - - if (mEntries == null) - return false; - - // remove the entry - boolean removed = mEntries.remove(e); - - if (removed) { - calcMinMax(); - } - - return removed; - } - - @Override - public int getEntryIndex(Entry e) { - return mEntries.indexOf(e); - } - - @Override - public T getEntryForXValue(float xValue, float closestToY, Rounding rounding) { - - int index = getEntryIndex(xValue, closestToY, rounding); - if (index > -1) - return mEntries.get(index); - return null; - } - - @Override - public T getEntryForXValue(float xValue, float closestToY) { - return getEntryForXValue(xValue, closestToY, Rounding.CLOSEST); - } - - @Override - public T getEntryForIndex(int index) { - if (index < 0) - return null; - if (index >= mEntries.size()) - return null; - return mEntries.get(index); - } - - @Override - public int getEntryIndex(float xValue, float closestToY, Rounding rounding) { - - if (mEntries == null || mEntries.isEmpty()) - return -1; - - int low = 0; - int high = mEntries.size() - 1; - int closest = high; - - while (low < high) { - int m = low + (high - low) / 2; - - Entry currentEntry = mEntries.get(m); - - if (currentEntry != null) { - Entry nextEntry = mEntries.get(m + 1); - - if (nextEntry != null) { - final float d1 = currentEntry.getX() - xValue, - d2 = nextEntry.getX() - xValue, - ad1 = Math.abs(d1), - ad2 = Math.abs(d2); - - if (ad2 < ad1) { - // [m + 1] is closer to xValue - // Search in an higher place - low = m + 1; - } else if (ad1 < ad2) { - // [m] is closer to xValue - // Search in a lower place - high = m; - } else { - // We have multiple sequential x-value with same distance - - if (d1 >= 0.0) { - // Search in a lower place - high = m; - } else if (d1 < 0.0) { - // Search in an higher place - low = m + 1; - } - } - - closest = high; - } - } - } - - if (closest != -1) { - Entry closestEntry = mEntries.get(closest); - if (closestEntry != null) { - float closestXValue = closestEntry.getX(); - if (rounding == Rounding.UP) { - // If rounding up, and found x-value is lower than specified x, and we can go upper... - if (closestXValue < xValue && closest < mEntries.size() - 1) { - ++closest; - } - } else if (rounding == Rounding.DOWN) { - // If rounding down, and found x-value is upper than specified x, and we can go lower... - if (closestXValue > xValue && closest > 0) { - --closest; - } - } - - // Search by closest to y-value - if (!Float.isNaN(closestToY)) { - while (closest > 0 && mEntries.get(closest - 1).getX() == closestXValue) - closest -= 1; - - float closestYValue = closestEntry.getY(); - int closestYIndex = closest; - - while (true) { - closest += 1; - if (closest >= mEntries.size()) - break; - - final Entry value = mEntries.get(closest); - - if (value == null) { - continue; - } - - if (value.getX() != closestXValue) - break; - - if (Math.abs(value.getY() - closestToY) <= Math.abs(closestYValue - closestToY)) { - closestYValue = closestToY; - closestYIndex = closest; - } - } - - closest = closestYIndex; - } - } - } - return closest; - } - - @Override - public List getEntriesForXValue(float xValue) { - - List entries = new ArrayList(); - - int low = 0; - int high = mEntries.size() - 1; - - while (low <= high) { - int m = (high + low) / 2; - T entry = mEntries.get(m); - - // if we have a match - if (xValue == entry.getX()) { - while (m > 0 && mEntries.get(m - 1).getX() == xValue) - m--; - - high = mEntries.size(); - - // loop over all "equal" entries - for (; m < high; m++) { - entry = mEntries.get(m); - if (entry.getX() == xValue) { - entries.add(entry); - } else { - break; - } - } - - break; - } else { - if (xValue > entry.getX()) - low = m + 1; - else - high = m - 1; - } - } - - return entries; - } - - /** - * Determines how to round DataSet index values for - * {@link DataSet#getEntryIndex(float, float, Rounding)} DataSet.getEntryIndex()} - * when an exact x-index is not found. - */ - public enum Rounding { - UP, - DOWN, - CLOSEST, - } -} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.kt new file mode 100644 index 0000000000..8039b0c376 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/DataSet.kt @@ -0,0 +1,395 @@ +package com.github.mikephil.charting.data + +import java.io.Serializable +import kotlin.math.abs + +/** + * The DataSet class represents one group or type of entries (Entry) in the + * Chart that belong together. It is designed to logically separate different + * groups of values inside the Chart (e.g. the values for a specific line in the + * LineChart, or the values of a specific group of bars in the BarChart). + * + * @author Philipp Jahoda + */ +abstract class DataSet( + /** + * the entries that this DataSet represents / holds together + */ + protected var mEntries: MutableList, label: String +) : BaseDataSet(label), Serializable { + /** + * maximum y-value in the value array + */ + protected var mYMax: Float = -Float.MAX_VALUE + + /** + * minimum y-value in the value array + */ + protected var mYMin: Float = Float.MAX_VALUE + + /** + * maximum x-value in the value array + */ + protected var mXMax: Float = -Float.Companion.MAX_VALUE + + /** + * minimum x-value in the value array + */ + protected var mXMin: Float = Float.Companion.MAX_VALUE + + + /** + * Creates a new DataSet object with the given values (entries) it represents. Also, a + * label that describes the DataSet can be specified. The label can also be + * used to retrieve the DataSet from a ChartData object. + * + * @param mEntries + * @param label + */ + init { + calcMinMax() + } + + override fun calcMinMax() { + mYMax = -Float.Companion.MAX_VALUE + mYMin = Float.Companion.MAX_VALUE + mXMax = -Float.Companion.MAX_VALUE + mXMin = Float.Companion.MAX_VALUE + + if (mEntries.isEmpty()) return + + for (e in mEntries) { + calcMinMax(e) + } + } + + override fun calcMinMaxY(fromX: Float, toX: Float) { + mYMax = -Float.Companion.MAX_VALUE + mYMin = Float.Companion.MAX_VALUE + + if (mEntries.isEmpty()) return + + val indexFrom = getEntryIndex(fromX, Float.Companion.NaN, Rounding.DOWN) + val indexTo = getEntryIndex(toX, Float.Companion.NaN, Rounding.UP) + + if (indexTo < indexFrom) return + + for (i in indexFrom..indexTo) { + // only recalculate y + + calcMinMaxY(mEntries[i]) + } + } + + /** + * Updates the min and max x and y value of this DataSet based on the given Entry. + * + * @param e + */ + protected open fun calcMinMax(e: T) { + calcMinMaxX(e) + + calcMinMaxY(e) + } + + protected fun calcMinMaxX(e: T) { + if (e.x < mXMin) mXMin = e.x + + if (e.x > mXMax) mXMax = e.x + } + + protected open fun calcMinMaxY(e: T) { + if (e.y < mYMin) mYMin = e.y + + if (e.y > mYMax) mYMax = e.y + } + + override val entryCount: Int + get() = mEntries.size + + @get:Deprecated("") + @set:Deprecated("") + var values: MutableList + /** + * This method is deprecated. + * Use getEntries() instead. + * + * @return + */ + get() = mEntries + /** + * This method is deprecated. + * Use setEntries(...) instead. + * + * @param values + */ + set(values) { + this.entries = values + } + + var entries: MutableList + /** + * Returns the array of entries that this DataSet represents. + * + * @return + */ + get() = mEntries + /** + * Sets the array of entries that this DataSet represents, and calls notifyDataSetChanged() + * + * @return + */ + set(entries) { + mEntries = entries + notifyDataSetChanged() + } + + /** + * Provides an exact copy of the DataSet this method is used on. + * + * @return + */ + abstract fun copy(): DataSet + + /** + * @param dataSet + */ + protected fun copy(dataSet: DataSet<*>) { + super.copy(dataSet) + } + + override fun toString(): String { + val buffer = StringBuffer() + buffer.append(toSimpleString()) + for (i in mEntries.indices) { + buffer.append(mEntries[i].toString() + " ") + } + return buffer.toString() + } + + /** + * Returns a simple string representation of the DataSet with the type and + * the number of Entries. + * + * @return + */ + fun toSimpleString(): String { + val buffer = StringBuffer() + buffer.append( + "DataSet, label: " + (label) + ", entries: " + mEntries.size + + "\n" + ) + return buffer.toString() + } + + override val yMin: Float + get() = mYMin + + override val yMax: Float + get() = mYMax + + override val xMin: Float + get() = mXMin + + override val xMax: Float + get() = mXMax + + override fun addEntryOrdered(e: T) { + calcMinMax(e) + + if (mEntries.isNotEmpty() && mEntries[mEntries.size - 1].x > e.x) { + val closestIndex = getEntryIndex(e.x, e.y, Rounding.UP) + mEntries.add(closestIndex, e) + } else { + mEntries.add(e) + } + } + + override fun clear() { + mEntries.clear() + notifyDataSetChanged() + } + + override fun addEntry(e: T): Boolean { + + val values = this.entries + + calcMinMax(e) + + // add the entry + return values.add(e) + } + + override fun removeEntry(e: T?): Boolean { + if (e == null) return false + + // remove the entry + val removed = mEntries.remove(e) + + if (removed) { + calcMinMax() + } + + return removed + } + + override fun getEntryIndex(e: Entry): Int { + return mEntries.indexOf(e) + } + + override fun getEntryForXValue(xValue: Float, closestToY: Float, rounding: Rounding?): T? { + val index = getEntryIndex(xValue, closestToY, rounding) + if (index > -1) return mEntries[index] + return null + } + + override fun getEntryForXValue(xValue: Float, closestToY: Float): T? { + return getEntryForXValue(xValue, closestToY, Rounding.CLOSEST) + } + + override fun getEntryForIndex(index: Int): T { + if (index < 0) throw ArrayIndexOutOfBoundsException(index) + if (index >= mEntries.size) throw ArrayIndexOutOfBoundsException(index) + return mEntries[index] + } + + override fun getEntryIndex(xValue: Float, closestToY: Float, rounding: Rounding?): Int { + if (mEntries.isEmpty()) return -1 + + var low = 0 + var high = mEntries.size - 1 + var closest = high + + while (low < high) { + val m = low + (high - low) / 2 + + val currentEntry: Entry? = mEntries[m] + + if (currentEntry != null) { + val nextEntry: Entry? = mEntries[m + 1] + + if (nextEntry != null) { + val d1 = currentEntry.x - xValue + val d2 = nextEntry.x - xValue + val ad1 = abs(d1) + val ad2 = abs(d2) + + if (ad2 < ad1) { + // [m + 1] is closer to xValue + // Search in an higher place + low = m + 1 + } else if (ad1 < ad2) { + // [m] is closer to xValue + // Search in a lower place + high = m + } else { + // We have multiple sequential x-value with same distance + + if (d1 >= 0.0) { + // Search in a lower place + high = m + } else if (d1 < 0.0) { + // Search in an higher place + low = m + 1 + } + } + + closest = high + } + } + } + + val closestEntry: Entry? = mEntries[closest] + if (closestEntry != null) { + val closestXValue = closestEntry.x + if (rounding == Rounding.UP) { + // If rounding up, and found x-value is lower than specified x, and we can go upper... + if (closestXValue < xValue && closest < mEntries.size - 1) { + ++closest + } + } else if (rounding == Rounding.DOWN) { + // If rounding down, and found x-value is upper than specified x, and we can go lower... + if (closestXValue > xValue && closest > 0) { + --closest + } + } + + // Search by closest to y-value + if (!closestToY.isNaN()) { + while (closest > 0 && mEntries[closest - 1].x == closestXValue) closest -= 1 + + var closestYValue = closestEntry.y + var closestYIndex = closest + + while (true) { + closest += 1 + if (closest >= mEntries.size) break + + val value: Entry? = mEntries[closest] + + if (value == null) { + continue + } + + if (value.x != closestXValue) break + + if (abs(value.y - closestToY) <= abs(closestYValue - closestToY)) { + closestYValue = closestToY + closestYIndex = closest + } + } + + closest = closestYIndex + } + } + return closest + } + + override fun getEntriesForXValue(xValue: Float): MutableList { + val entries: MutableList = ArrayList() + + var low = 0 + var high = mEntries.size - 1 + + while (low <= high) { + var m = (high + low) / 2 + var entry = mEntries[m] + + // if we have a match + if (xValue == entry.x) { + while (m > 0 && mEntries[m - 1].x == xValue) m-- + + high = mEntries.size + + // loop over all "equal" entries + while (m < high) { + entry = mEntries[m] + if (entry.x == xValue) { + entries.add(entry) + } else { + break + } + m++ + } + + break + } else { + if (xValue > entry.x) low = m + 1 + else high = m - 1 + } + } + + return entries + } + + /** + * Determines how to round DataSet index values for + * [DataSet.getEntryIndex] DataSet.getEntryIndex()} + * when an exact x-index is not found. + */ + enum class Rounding { + UP, + DOWN, + CLOSEST, + } +} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/Entry.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/Entry.kt index b02f4c10f5..482de2d8be 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/Entry.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/Entry.kt @@ -72,7 +72,7 @@ open class Entry : BaseEntry, Parcelable, Serializable { * * @return */ - open fun copy(): Entry? { + open fun copy(): Entry { val e = Entry(x, y, data) return e } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineData.java deleted file mode 100644 index 4cf544874b..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineData.java +++ /dev/null @@ -1,27 +0,0 @@ - -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; - -import java.util.ArrayList; -import java.util.List; - -/** - * Data object that encapsulates all data associated with a LineChart. - * - * @author Philipp Jahoda - */ -public class LineData extends BarLineScatterCandleBubbleData { - - public LineData() { - super(); - } - - public LineData(ILineDataSet... dataSets) { - super(dataSets); - } - - public LineData(List dataSets) { - super(dataSets); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineData.kt new file mode 100644 index 0000000000..afc96cb7d9 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineData.kt @@ -0,0 +1,16 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet + +/** + * Data object that encapsulates all data associated with a LineChart. + * + * @author Philipp Jahoda + */ +class LineData : BarLineScatterCandleBubbleData { + constructor() : super() + + constructor(vararg dataSets: ILineDataSet) : super(*dataSets) + + constructor(dataSets: MutableList) : super(dataSets) +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.java deleted file mode 100644 index 6590d5800e..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.java +++ /dev/null @@ -1,417 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.content.Context; -import android.graphics.Color; -import android.graphics.DashPathEffect; -import android.util.Log; - -import com.github.mikephil.charting.formatter.DefaultFillFormatter; -import com.github.mikephil.charting.formatter.IFillFormatter; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.Utils; - -import java.util.ArrayList; -import java.util.List; - -public class LineDataSet extends LineRadarDataSet implements ILineDataSet { - - /** - * Drawing mode for this line dataset - **/ - private LineDataSet.Mode mMode = Mode.LINEAR; - - /** - * List representing all colors that are used for the circles - */ - private List mCircleColors = null; - - /** - * the color of the inner circles - */ - private int mCircleHoleColor = Color.WHITE; - - /** - * the radius of the circle-shaped value indicators - */ - private float mCircleRadius = 8f; - - /** - * the hole radius of the circle-shaped value indicators - */ - private float mCircleHoleRadius = 4f; - - /** - * sets the intensity of the cubic lines - */ - private float mCubicIntensity = 0.2f; - - /** - * the path effect of this DataSet that makes dashed lines possible - */ - private DashPathEffect mDashPathEffect = null; - - /** - * formatter for customizing the position of the fill-line - */ - private IFillFormatter mFillFormatter = new DefaultFillFormatter(); - - /** - * if true, drawing circles is enabled - */ - private boolean mDrawCircles = true; - - private boolean mDrawCircleHole = true; - - - public LineDataSet(List yVals, String label) { - super(yVals, label); - - // mCircleRadius = Utils.convertDpToPixel(4f); - // mLineWidth = Utils.convertDpToPixel(1f); - - if (mCircleColors == null) { - mCircleColors = new ArrayList(); - } - mCircleColors.clear(); - - // default colors - // mColors.add(Color.rgb(192, 255, 140)); - // mColors.add(Color.rgb(255, 247, 140)); - mCircleColors.add(Color.rgb(140, 234, 255)); - } - - @Override - public DataSet copy() { - List entries = new ArrayList(); - for (int i = 0; i < mEntries.size(); i++) { - entries.add(mEntries.get(i).copy()); - } - LineDataSet copied = new LineDataSet(entries, getLabel()); - copy(copied); - return copied; - } - - protected void copy(LineDataSet lineDataSet) { - super.copy((BaseDataSet) lineDataSet); - lineDataSet.mCircleColors = mCircleColors; - lineDataSet.mCircleHoleColor = mCircleHoleColor; - lineDataSet.mCircleHoleRadius = mCircleHoleRadius; - lineDataSet.mCircleRadius = mCircleRadius; - lineDataSet.mCubicIntensity = mCubicIntensity; - lineDataSet.mDashPathEffect = mDashPathEffect; - lineDataSet.mDrawCircleHole = mDrawCircleHole; - lineDataSet.mDrawCircles = mDrawCircleHole; - lineDataSet.mFillFormatter = mFillFormatter; - lineDataSet.mMode = mMode; - } - - /** - * Returns the drawing mode for this line dataset - * - * @return - */ - @Override - public LineDataSet.Mode getMode() { - return mMode; - } - - /** - * Returns the drawing mode for this LineDataSet - * - * @return - */ - public void setMode(LineDataSet.Mode mode) { - mMode = mode; - } - - /** - * Sets the intensity for cubic lines (if enabled). Max = 1f = very cubic, - * Min = 0.05f = low cubic effect, Default: 0.2f - * - * @param intensity - */ - public void setCubicIntensity(float intensity) { - - if (intensity > 1f) - intensity = 1f; - if (intensity < 0.05f) - intensity = 0.05f; - - mCubicIntensity = intensity; - } - - @Override - public float getCubicIntensity() { - return mCubicIntensity; - } - - - /** - * Sets the radius of the drawn circles. - * Default radius = 4f, Min = 1f - * - * @param radius - */ - public void setCircleRadius(float radius) { - - if (radius >= 1f) { - mCircleRadius = Utils.convertDpToPixel(radius); - } else { - Log.e("LineDataSet", "Circle radius cannot be < 1"); - } - } - - @Override - public float getCircleRadius() { - return mCircleRadius; - } - - /** - * Sets the hole radius of the drawn circles. - * Default radius = 2f, Min = 0.5f - * - * @param holeRadius - */ - public void setCircleHoleRadius(float holeRadius) { - - if (holeRadius >= 0.5f) { - mCircleHoleRadius = Utils.convertDpToPixel(holeRadius); - } else { - Log.e("LineDataSet", "Circle radius cannot be < 0.5"); - } - } - - @Override - public float getCircleHoleRadius() { - return mCircleHoleRadius; - } - - /** - * sets the size (radius) of the circle shpaed value indicators, - * default size = 4f - *

- * This method is deprecated because of unclarity. Use setCircleRadius instead. - * - * @param size - */ - @Deprecated - public void setCircleSize(float size) { - setCircleRadius(size); - } - - /** - * This function is deprecated because of unclarity. Use getCircleRadius instead. - */ - @Deprecated - public float getCircleSize() { - return getCircleRadius(); - } - - /** - * Enables the line to be drawn in dashed mode, e.g. like this - * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. - * Keep in mind that hardware acceleration boosts performance. - * - * @param lineLength the length of the line pieces - * @param spaceLength the length of space in between the pieces - * @param phase offset, in degrees (normally, use 0) - */ - public void enableDashedLine(float lineLength, float spaceLength, float phase) { - mDashPathEffect = new DashPathEffect(new float[]{ - lineLength, spaceLength - }, phase); - } - - /** - * Disables the line to be drawn in dashed mode. - */ - public void disableDashedLine() { - mDashPathEffect = null; - } - - @Override - public boolean isDashedLineEnabled() { - return mDashPathEffect == null ? false : true; - } - - @Override - public DashPathEffect getDashPathEffect() { - return mDashPathEffect; - } - - /** - * set this to true to enable the drawing of circle indicators for this - * DataSet, default true - * - * @param enabled - */ - public void setDrawCircles(boolean enabled) { - this.mDrawCircles = enabled; - } - - @Override - public boolean isDrawCirclesEnabled() { - return mDrawCircles; - } - - @Deprecated - @Override - public boolean isDrawCubicEnabled() { - return mMode == Mode.CUBIC_BEZIER; - } - - @Deprecated - @Override - public boolean isDrawSteppedEnabled() { - return mMode == Mode.STEPPED; - } - - /** ALL CODE BELOW RELATED TO CIRCLE-COLORS */ - - /** - * returns all colors specified for the circles - * - * @return - */ - public List getCircleColors() { - return mCircleColors; - } - - @Override - public int getCircleColor(int index) { - return mCircleColors.get(index); - } - - @Override - public int getCircleColorCount() { - return mCircleColors.size(); - } - - /** - * Sets the colors that should be used for the circles of this DataSet. - * Colors are reused as soon as the number of Entries the DataSet represents - * is higher than the size of the colors array. Make sure that the colors - * are already prepared (by calling getResources().getColor(...)) before - * adding them to the DataSet. - * - * @param colors - */ - public void setCircleColors(List colors) { - mCircleColors = colors; - } - - /** - * Sets the colors that should be used for the circles of this DataSet. - * Colors are reused as soon as the number of Entries the DataSet represents - * is higher than the size of the colors array. Make sure that the colors - * are already prepared (by calling getResources().getColor(...)) before - * adding them to the DataSet. - * - * @param colors - */ - public void setCircleColors(int... colors) { - this.mCircleColors = ColorTemplate.createColors(colors); - } - - /** - * ets the colors that should be used for the circles of this DataSet. - * Colors are reused as soon as the number of Entries the DataSet represents - * is higher than the size of the colors array. You can use - * "new String[] { R.color.red, R.color.green, ... }" to provide colors for - * this method. Internally, the colors are resolved using - * getResources().getColor(...) - * - * @param colors - */ - public void setCircleColors(int[] colors, Context c) { - - List clrs = mCircleColors; - if (clrs == null) { - clrs = new ArrayList<>(); - } - clrs.clear(); - - for (int color : colors) { - clrs.add(c.getResources().getColor(color)); - } - - mCircleColors = clrs; - } - - /** - * Sets the one and ONLY color that should be used for this DataSet. - * Internally, this recreates the colors array and adds the specified color. - * - * @param color - */ - public void setCircleColor(int color) { - resetCircleColors(); - mCircleColors.add(color); - } - - /** - * resets the circle-colors array and creates a new one - */ - public void resetCircleColors() { - if (mCircleColors == null) { - mCircleColors = new ArrayList(); - } - mCircleColors.clear(); - } - - /** - * Sets the color of the inner circle of the line-circles. - * - * @param color - */ - public void setCircleHoleColor(int color) { - mCircleHoleColor = color; - } - - @Override - public int getCircleHoleColor() { - return mCircleHoleColor; - } - - /** - * Set this to true to allow drawing a hole in each data circle. - * - * @param enabled - */ - public void setDrawCircleHole(boolean enabled) { - mDrawCircleHole = enabled; - } - - @Override - public boolean isDrawCircleHoleEnabled() { - return mDrawCircleHole; - } - - /** - * Sets a custom IFillFormatter to the chart that handles the position of the - * filled-line for each DataSet. Set this to null to use the default logic. - * - * @param formatter - */ - public void setFillFormatter(IFillFormatter formatter) { - - if (formatter == null) - mFillFormatter = new DefaultFillFormatter(); - else - mFillFormatter = formatter; - } - - @Override - public IFillFormatter getFillFormatter() { - return mFillFormatter; - } - - public enum Mode { - LINEAR, - STEPPED, - CUBIC_BEZIER, - HORIZONTAL_BEZIER - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.kt new file mode 100644 index 0000000000..a514d24fbb --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineDataSet.kt @@ -0,0 +1,305 @@ +package com.github.mikephil.charting.data + +import android.content.Context +import android.graphics.Color +import android.graphics.DashPathEffect +import android.util.Log +import androidx.core.content.res.ResourcesCompat +import com.github.mikephil.charting.formatter.DefaultFillFormatter +import com.github.mikephil.charting.formatter.IFillFormatter +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.Utils + +open class LineDataSet(yVals: MutableList, label: String) : LineRadarDataSet(yVals, label), ILineDataSet { + /** + * Drawing mode for this line dataset + */ + private var mMode: Mode? = Mode.LINEAR + + /** + * returns all colors specified for the circles + * + * @return + */ + /** + * Sets the colors that should be used for the circles of this DataSet. + * Colors are reused as soon as the number of Entries the DataSet represents + * is higher than the size of the colors array. Make sure that the colors + * are already prepared (by calling getResources().getColor(...)) before + * adding them to the DataSet. + * + * @param colors + */ + /** + * List representing all colors that are used for the circles + */ + var circleColors: MutableList = mutableListOf() + + /** + * the color of the inner circles + */ + private var mCircleHoleColor = Color.WHITE + + /** + * the radius of the circle-shaped value indicators + */ + private var mCircleRadius = 8f + + /** + * the hole radius of the circle-shaped value indicators + */ + private var mCircleHoleRadius = 4f + + /** + * sets the intensity of the cubic lines + */ + private var mCubicIntensity = 0.2f + + /** + * the path effect of this DataSet that makes dashed lines possible + */ + private var mDashPathEffect: DashPathEffect? = null + + /** + * formatter for customizing the position of the fill-line + */ + private var mFillFormatter: IFillFormatter = DefaultFillFormatter() + + /** + * if true, drawing circles is enabled + */ + private var mDrawCircles = true + + private var mDrawCircleHole = true + + + init { + // mCircleRadius = Utils.convertDpToPixel(4f); + // mLineWidth = Utils.convertDpToPixel(1f); + circleColors.clear() + + // default colors + // mColors.add(Color.rgb(192, 255, 140)); + // mColors.add(Color.rgb(255, 247, 140)); + circleColors.add(Color.rgb(140, 234, 255)) + } + + override fun copy(): DataSet { + val entries: MutableList = ArrayList() + for (i in mEntries.indices) { + entries.add(mEntries[i].copy()) + } + val copied = LineDataSet(entries, label) + copy(copied) + return copied + } + + protected fun copy(lineDataSet: LineDataSet) { + super.copy(lineDataSet) + lineDataSet.circleColors = this.circleColors + lineDataSet.mCircleHoleColor = mCircleHoleColor + lineDataSet.mCircleHoleRadius = mCircleHoleRadius + lineDataSet.mCircleRadius = mCircleRadius + lineDataSet.mCubicIntensity = mCubicIntensity + lineDataSet.mDashPathEffect = mDashPathEffect + lineDataSet.mDrawCircleHole = mDrawCircleHole + lineDataSet.mDrawCircles = mDrawCircleHole + lineDataSet.mFillFormatter = mFillFormatter + lineDataSet.mMode = mMode + } + + /** + * Returns the drawing mode for this line dataset + * + * @return + */ + override var mode: Mode? + get() = mMode + set(value) { + mMode = value + } + + override var cubicIntensity: Float + get() = mCubicIntensity + set(value) { + var intensity = value + if (intensity > 1f) intensity = 1f + if (intensity < 0.05f) intensity = 0.05f + + mCubicIntensity = intensity + } + + override var circleRadius: Float + get() = mCircleRadius + set(value) { + if (value >= 1f) { + mCircleRadius = Utils.convertDpToPixel(value) + } else { + Log.e("LineDataSet", "Circle radius cannot be < 1") + } + } + + override var circleHoleRadius: Float + get() = mCircleHoleRadius + set(holeRadius) { + if (holeRadius >= 0.5f) { + mCircleHoleRadius = Utils.convertDpToPixel(holeRadius) + } else { + Log.e("LineDataSet", "Circle radius cannot be < 0.5") + } + } + + @get:Deprecated("") + @set:Deprecated("") + var circleSize: Float + /** + * This function is deprecated because of unclarity. Use getCircleRadius instead. + */ + get() = circleRadius + /** + * sets the size (radius) of the circle shpaed value indicators, + * default size = 4f + * + * + * This method is deprecated because of unclarity. Use setCircleRadius instead. + * + * @param size + */ + set(size) { + circleRadius = size + } + + /** + * Enables the line to be drawn in dashed mode, e.g. like this + * "- - - - - -". THIS ONLY WORKS IF HARDWARE-ACCELERATION IS TURNED OFF. + * Keep in mind that hardware acceleration boosts performance. + * + * @param lineLength the length of the line pieces + * @param spaceLength the length of space in between the pieces + * @param phase offset, in degrees (normally, use 0) + */ + fun enableDashedLine(lineLength: Float, spaceLength: Float, phase: Float) { + mDashPathEffect = DashPathEffect( + floatArrayOf( + lineLength, spaceLength + ), phase + ) + } + + /** + * Disables the line to be drawn in dashed mode. + */ + fun disableDashedLine() { + mDashPathEffect = null + } + + override val isDashedLineEnabled: Boolean + get() = mDashPathEffect != null + + override var dashPathEffect: DashPathEffect? + get() = mDashPathEffect + set(value) { + mDashPathEffect = value + } + + override var isDrawCirclesEnabled: Boolean + get() = mDrawCircles + set(value) { + mDrawCircles = value + } + + @Deprecated("") + override val isDrawCubicEnabled: Boolean + get() = mMode == Mode.CUBIC_BEZIER + + @Deprecated("") + override val isDrawSteppedEnabled: Boolean + get() = mMode == Mode.STEPPED + + /** ALL CODE BELOW RELATED TO CIRCLE-COLORS */ + + override fun getCircleColor(index: Int): Int { + return circleColors[index] + } + + override val circleColorCount: Int + get() = circleColors.size + + /** + * Sets the colors that should be used for the circles of this DataSet. + * Colors are reused as soon as the number of Entries the DataSet represents + * is higher than the size of the colors array. Make sure that the colors + * are already prepared (by calling getResources().getColor(...)) before + * adding them to the DataSet. + * + * @param colors + */ + fun setCircleColors(vararg colors: Int) { + this.circleColors = ColorTemplate.createColors(colors) + } + + /** + * ets the colors that should be used for the circles of this DataSet. + * Colors are reused as soon as the number of Entries the DataSet represents + * is higher than the size of the colors array. You can use + * "new String[] { R.color.red, R.color.green, ... }" to provide colors for + * this method. Internally, the colors are resolved using + * getResources().getColor(...) + * + * @param colors + */ + fun setCircleColors(colors: IntArray, c: Context) { + val clrs = this.circleColors + clrs.clear() + + for (color in colors) { + clrs.add(ResourcesCompat.getColor(c.resources, color, c.theme)) + } + + this.circleColors = clrs + } + + /** + * Sets the one and ONLY color that should be used for this DataSet. + * Internally, this recreates the colors array and adds the specified color. + * + * @param color + */ + fun setCircleColor(color: Int) { + resetCircleColors() + circleColors.add(color) + } + + /** + * resets the circle-colors array and creates a new one + */ + fun resetCircleColors() { + circleColors.clear() + } + + override var circleHoleColor: Int + get() = mCircleHoleColor + set(value) { + mCircleHoleColor = value + } + + override var isDrawCircleHoleEnabled: Boolean + get() = mDrawCircleHole + set(value) { + mDrawCircleHole = value + } + + override var fillFormatter: IFillFormatter + get() = mFillFormatter + set(value) { + mFillFormatter = value + } + + enum class Mode { + LINEAR, + STEPPED, + CUBIC_BEZIER, + HORIZONTAL_BEZIER + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineRadarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineRadarDataSet.java deleted file mode 100644 index df422666e3..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineRadarDataSet.java +++ /dev/null @@ -1,135 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.annotation.TargetApi; -import android.graphics.Color; -import android.graphics.drawable.Drawable; - -import com.github.mikephil.charting.interfaces.datasets.ILineRadarDataSet; -import com.github.mikephil.charting.utils.Utils; - -import java.util.List; - -/** - * Base dataset for line and radar DataSets. - * - * @author Philipp Jahoda - */ -public abstract class LineRadarDataSet extends LineScatterCandleRadarDataSet implements ILineRadarDataSet { - - // TODO: Move to using `Fill` class - /** - * the color that is used for filling the line surface - */ - private int mFillColor = Color.rgb(140, 234, 255); - - /** - * the drawable to be used for filling the line surface - */ - protected Drawable mFillDrawable; - - /** - * transparency used for filling line surface - */ - private int mFillAlpha = 85; - - /** - * the width of the drawn data lines - */ - private float mLineWidth = 2.5f; - - /** - * if true, the data will also be drawn filled - */ - private boolean mDrawFilled = false; - - - public LineRadarDataSet(List yVals, String label) { - super(yVals, label); - } - - @Override - public int getFillColor() { - return mFillColor; - } - - /** - * Sets the color that is used for filling the area below the line. - * Resets an eventually set "fillDrawable". - * - * @param color - */ - public void setFillColor(int color) { - mFillColor = color; - mFillDrawable = null; - } - - @Override - public Drawable getFillDrawable() { - return mFillDrawable; - } - - /** - * Sets the drawable to be used to fill the area below the line. - * - * @param drawable - */ - @TargetApi(18) - public void setFillDrawable(Drawable drawable) { - this.mFillDrawable = drawable; - } - - @Override - public int getFillAlpha() { - return mFillAlpha; - } - - /** - * sets the alpha value (transparency) that is used for filling the line - * surface (0-255), default: 85 - * - * @param alpha - */ - public void setFillAlpha(int alpha) { - mFillAlpha = alpha; - } - - /** - * set the line width of the chart (min = 0.2f, max = 10f); default 1f NOTE: - * thinner line == better performance, thicker line == worse performance - * - * @param width - */ - public void setLineWidth(float width) { - - if (width < 0.0f) - width = 0.0f; - if (width > 10.0f) - width = 10.0f; - mLineWidth = Utils.convertDpToPixel(width); - } - - @Override - public float getLineWidth() { - return mLineWidth; - } - - @Override - public void setDrawFilled(boolean filled) { - mDrawFilled = filled; - } - - @Override - public boolean isDrawFilledEnabled() { - return mDrawFilled; - } - - protected void copy(LineRadarDataSet lineRadarDataSet) { - super.copy((BaseDataSet) lineRadarDataSet); - lineRadarDataSet.mDrawFilled = mDrawFilled; - lineRadarDataSet.mFillAlpha = mFillAlpha; - lineRadarDataSet.mFillColor = mFillColor; - lineRadarDataSet.mFillDrawable = mFillDrawable; - lineRadarDataSet.mLineWidth = mLineWidth; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineRadarDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineRadarDataSet.kt new file mode 100644 index 0000000000..5143318177 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineRadarDataSet.kt @@ -0,0 +1,82 @@ +package com.github.mikephil.charting.data + +import android.graphics.Color +import android.graphics.drawable.Drawable +import com.github.mikephil.charting.interfaces.datasets.ILineRadarDataSet +import com.github.mikephil.charting.utils.Utils + +/** + * Base dataset for line and radar DataSets. + * + * @author Philipp Jahoda + */ +abstract class LineRadarDataSet(yVals: MutableList, label: String) : LineScatterCandleRadarDataSet(yVals, label), ILineRadarDataSet { + // TODO: Move to using `Fill` class + /** + * the color that is used for filling the line surface + */ + private var mFillColor = Color.rgb(140, 234, 255) + + /** + * the drawable to be used for filling the line surface + */ + protected var mFillDrawable: Drawable? = null + + /** + * transparency used for filling line surface + */ + private var mFillAlpha = 85 + + /** + * the width of the drawn data lines + */ + private var mLineWidth = 2.5f + + /** + * if true, the data will also be drawn filled + */ + private var mDrawFilled = false + + override var fillColor: Int + get() = mFillColor + set(value) { + mFillColor = value + mFillDrawable = null + } + + override var fillDrawable: Drawable? + get() = mFillDrawable + set(value) { + mFillDrawable = value + } + + override var fillAlpha: Int + get() = mFillAlpha + set(value) { + mFillAlpha = value + } + + override var lineWidth: Float + get() = mLineWidth + set(value) { + var width = value + if (width < 0.0f) width = 0.0f + if (width > 10.0f) width = 10.0f + mLineWidth = Utils.convertDpToPixel(width) + } + + override var isDrawFilledEnabled: Boolean + get() = mDrawFilled + set(value) { + mDrawFilled = value + } + + protected fun copy(lineRadarDataSet: LineRadarDataSet<*>) { + super.copy(lineRadarDataSet) + lineRadarDataSet.mDrawFilled = mDrawFilled + lineRadarDataSet.mFillAlpha = mFillAlpha + lineRadarDataSet.mFillColor = mFillColor + lineRadarDataSet.mFillDrawable = mFillDrawable + lineRadarDataSet.mLineWidth = mLineWidth + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineScatterCandleRadarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineScatterCandleRadarDataSet.java deleted file mode 100644 index 151294630d..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineScatterCandleRadarDataSet.java +++ /dev/null @@ -1,120 +0,0 @@ -package com.github.mikephil.charting.data; - -import android.graphics.DashPathEffect; - -import com.github.mikephil.charting.interfaces.datasets.ILineScatterCandleRadarDataSet; -import com.github.mikephil.charting.utils.Utils; - -import java.util.List; - -/** - * Created by Philipp Jahoda on 11/07/15. - */ -public abstract class LineScatterCandleRadarDataSet extends BarLineScatterCandleBubbleDataSet implements ILineScatterCandleRadarDataSet { - - protected boolean mDrawVerticalHighlightIndicator = true; - protected boolean mDrawHorizontalHighlightIndicator = true; - - /** the width of the highlight indicator lines */ - protected float mHighlightLineWidth = 0.5f; - - /** the path effect for dashed highlight-lines */ - protected DashPathEffect mHighlightDashPathEffect = null; - - - public LineScatterCandleRadarDataSet(List yVals, String label) { - super(yVals, label); - mHighlightLineWidth = Utils.convertDpToPixel(0.5f); - } - - /** - * Enables / disables the horizontal highlight-indicator. If disabled, the indicator is not drawn. - * @param enabled - */ - public void setDrawHorizontalHighlightIndicator(boolean enabled) { - this.mDrawHorizontalHighlightIndicator = enabled; - } - - /** - * Enables / disables the vertical highlight-indicator. If disabled, the indicator is not drawn. - * @param enabled - */ - public void setDrawVerticalHighlightIndicator(boolean enabled) { - this.mDrawVerticalHighlightIndicator = enabled; - } - - /** - * Enables / disables both vertical and horizontal highlight-indicators. - * @param enabled - */ - public void setDrawHighlightIndicators(boolean enabled) { - setDrawVerticalHighlightIndicator(enabled); - setDrawHorizontalHighlightIndicator(enabled); - } - - @Override - public boolean isVerticalHighlightIndicatorEnabled() { - return mDrawVerticalHighlightIndicator; - } - - @Override - public boolean isHorizontalHighlightIndicatorEnabled() { - return mDrawHorizontalHighlightIndicator; - } - - /** - * Sets the width of the highlight line in dp. - * @param width - */ - public void setHighlightLineWidth(float width) { - mHighlightLineWidth = Utils.convertDpToPixel(width); - } - - @Override - public float getHighlightLineWidth() { - return mHighlightLineWidth; - } - - /** - * Enables the highlight-line to be drawn in dashed mode, e.g. like this "- - - - - -" - * - * @param lineLength the length of the line pieces - * @param spaceLength the length of space inbetween the line-pieces - * @param phase offset, in degrees (normally, use 0) - */ - public void enableDashedHighlightLine(float lineLength, float spaceLength, float phase) { - mHighlightDashPathEffect = new DashPathEffect(new float[] { - lineLength, spaceLength - }, phase); - } - - /** - * Disables the highlight-line to be drawn in dashed mode. - */ - public void disableDashedHighlightLine() { - mHighlightDashPathEffect = null; - } - - /** - * Returns true if the dashed-line effect is enabled for highlight lines, false if not. - * Default: disabled - * - * @return - */ - public boolean isDashedHighlightLineEnabled() { - return mHighlightDashPathEffect == null ? false : true; - } - - @Override - public DashPathEffect getDashPathEffectHighlight() { - return mHighlightDashPathEffect; - } - - protected void copy(LineScatterCandleRadarDataSet lineScatterCandleRadarDataSet) { - super.copy((BaseDataSet) lineScatterCandleRadarDataSet); - lineScatterCandleRadarDataSet.mDrawHorizontalHighlightIndicator = mDrawHorizontalHighlightIndicator; - lineScatterCandleRadarDataSet.mDrawVerticalHighlightIndicator = mDrawVerticalHighlightIndicator; - lineScatterCandleRadarDataSet.mHighlightLineWidth = mHighlightLineWidth; - lineScatterCandleRadarDataSet.mHighlightDashPathEffect = mHighlightDashPathEffect; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineScatterCandleRadarDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineScatterCandleRadarDataSet.kt new file mode 100644 index 0000000000..226a910821 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/LineScatterCandleRadarDataSet.kt @@ -0,0 +1,102 @@ +package com.github.mikephil.charting.data + +import android.graphics.DashPathEffect +import com.github.mikephil.charting.interfaces.datasets.ILineScatterCandleRadarDataSet +import com.github.mikephil.charting.utils.Utils + +/** + * Created by Philipp Jahoda on 11/07/15. + */ +abstract class LineScatterCandleRadarDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), + ILineScatterCandleRadarDataSet { + override var isVerticalHighlightIndicatorEnabled: Boolean = true + protected set + override var isHorizontalHighlightIndicatorEnabled: Boolean = true + protected set + + /** the width of the highlight indicator lines */ + protected var mHighlightLineWidth: Float = 0.5f + + /** the path effect for dashed highlight-lines */ + override var dashPathEffectHighlight: DashPathEffect? = null + protected set + + + init { + mHighlightLineWidth = Utils.convertDpToPixel(0.5f) + } + + /** + * Enables / disables the horizontal highlight-indicator. If disabled, the indicator is not drawn. + * @param enabled + */ + fun setDrawHorizontalHighlightIndicator(enabled: Boolean) { + this.isHorizontalHighlightIndicatorEnabled = enabled + } + + /** + * Enables / disables the vertical highlight-indicator. If disabled, the indicator is not drawn. + * @param enabled + */ + fun setDrawVerticalHighlightIndicator(enabled: Boolean) { + this.isVerticalHighlightIndicatorEnabled = enabled + } + + /** + * Enables / disables both vertical and horizontal highlight-indicators. + * @param enabled + */ + fun setDrawHighlightIndicators(enabled: Boolean) { + setDrawVerticalHighlightIndicator(enabled) + setDrawHorizontalHighlightIndicator(enabled) + } + + override var highlightLineWidth: Float + get() = mHighlightLineWidth + /** + * Sets the width of the highlight line in dp. + * @param width + */ + set(width) { + mHighlightLineWidth = Utils.convertDpToPixel(width) + } + + /** + * Enables the highlight-line to be drawn in dashed mode, e.g. like this "- - - - - -" + * + * @param lineLength the length of the line pieces + * @param spaceLength the length of space inbetween the line-pieces + * @param phase offset, in degrees (normally, use 0) + */ + fun enableDashedHighlightLine(lineLength: Float, spaceLength: Float, phase: Float) { + this.dashPathEffectHighlight = DashPathEffect( + floatArrayOf( + lineLength, spaceLength + ), phase + ) + } + + /** + * Disables the highlight-line to be drawn in dashed mode. + */ + fun disableDashedHighlightLine() { + this.dashPathEffectHighlight = null + } + + val isDashedHighlightLineEnabled: Boolean + /** + * Returns true if the dashed-line effect is enabled for highlight lines, false if not. + * Default: disabled + * + * @return + */ + get() = this.dashPathEffectHighlight != null + + protected fun copy(lineScatterCandleRadarDataSet: LineScatterCandleRadarDataSet<*>) { + super.copy(lineScatterCandleRadarDataSet) + lineScatterCandleRadarDataSet.isHorizontalHighlightIndicatorEnabled = this.isHorizontalHighlightIndicatorEnabled + lineScatterCandleRadarDataSet.isVerticalHighlightIndicatorEnabled = this.isVerticalHighlightIndicatorEnabled + lineScatterCandleRadarDataSet.mHighlightLineWidth = mHighlightLineWidth + lineScatterCandleRadarDataSet.dashPathEffectHighlight = this.dashPathEffectHighlight + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.java deleted file mode 100644 index 423ce19b16..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.java +++ /dev/null @@ -1,100 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.util.Log; - -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IPieDataSet; - -import java.util.ArrayList; -import java.util.List; - -/** - * A PieData object can only represent one DataSet. Unlike all other charts, the - * legend labels of the PieChart are created from the x-values array, and not - * from the DataSet labels. Each PieData object can only represent one - * PieDataSet (multiple PieDataSets inside a single PieChart are not possible). - * - * @author Philipp Jahoda - */ -public class PieData extends ChartData { - - public PieData() { - super(); - } - - public PieData(IPieDataSet dataSet) { - super(dataSet); - } - - /** - * Sets the PieDataSet this data object should represent. - * - * @param dataSet - */ - public void setDataSet(IPieDataSet dataSet) { - mDataSets.clear(); - mDataSets.add(dataSet); - notifyDataChanged(); - } - - /** - * Returns the DataSet this PieData object represents. A PieData object can - * only contain one DataSet. - * - * @return - */ - public IPieDataSet getDataSet() { - return mDataSets.get(0); - } - - @Override - public List getDataSets() { - List dataSets = super.getDataSets(); - - if (dataSets.size() < 1) { - Log.e("MPAndroidChart", - "Found multiple data sets while pie chart only allows one"); - } - - return dataSets; - } - - /** - * The PieData object can only have one DataSet. Use getDataSet() method instead. - * - * @param index - * @return - */ - @Override - public IPieDataSet getDataSetByIndex(int index) { - return index == 0 ? getDataSet() : null; - } - - @Override - public IPieDataSet getDataSetByLabel(String label, boolean ignorecase) { - return ignorecase ? label.equalsIgnoreCase(mDataSets.get(0).getLabel()) ? mDataSets.get(0) - : null : label.equals(mDataSets.get(0).getLabel()) ? mDataSets.get(0) : null; - } - - @Override - public Entry getEntryForHighlight(Highlight highlight) { - return getDataSet().getEntryForIndex((int) highlight.getX()); - } - - /** - * Returns the sum of all values in this PieData object. - * - * @return - */ - public float getYValueSum() { - - float sum = 0; - - for (int i = 0; i < getDataSet().getEntryCount(); i++) - sum += getDataSet().getEntryForIndex(i).getY(); - - - return sum; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.kt new file mode 100644 index 0000000000..1c1e7904aa --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.kt @@ -0,0 +1,88 @@ +package com.github.mikephil.charting.data + +import android.util.Log +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IPieDataSet + +/** + * A PieData object can only represent one DataSet. Unlike all other charts, the + * legend labels of the PieChart are created from the x-values array, and not + * from the DataSet labels. Each PieData object can only represent one + * PieDataSet (multiple PieDataSets inside a single PieChart are not possible). + * + * @author Philipp Jahoda + */ +class PieData : ChartData { + constructor() : super() + + constructor(dataSet: IPieDataSet) : super(dataSet) + + var dataSet: IPieDataSet + /** + * Returns the DataSet this PieData object represents. A PieData object can + * only contain one DataSet. + * + * @return + */ + get() = dataSets[0] + /** + * Sets the PieDataSet this data object should represent. + * + * @param dataSet + */ + set(dataSet) { + dataSets.clear() + dataSets.add(dataSet) + notifyDataChanged() + } + + override val dataSets: MutableList + get() { + val dataSets = super.dataSets + + if (dataSets.size > 1) { + Log.e( + "MPAndroidChart", + "Found multiple data sets while pie chart only allows one" + ) + } + + return dataSets + } + + /** + * The PieData object can only have one DataSet. Use getDataSet() method instead. + * + * @param index + * @return + */ + override fun getDataSetByIndex(index: Int): IPieDataSet { + return if (index == 0) this.dataSet else throw ArrayIndexOutOfBoundsException(index) + } + + override fun getDataSetByLabel(label: String, ignorecase: Boolean): IPieDataSet? { + return if (ignorecase) if (label.equals(dataSets[0].label, ignoreCase = true)) + dataSets[0] + else + null else if (label == dataSets[0].label) dataSets[0] else null + } + + override fun getEntryForHighlight(highlight: Highlight): PieEntry? { + return this.dataSet.getEntryForIndex(highlight.x.toInt()) + } + + val yValueSum: Float + /** + * Returns the sum of all values in this PieData object. + * + * @return + */ + get() { + var sum = 0f + + for (i in 0.. implements IPieDataSet { - - /** - * the space in pixels between the chart-slices, default 0f - */ - private float mSliceSpace = 0f; - private boolean mAutomaticallyDisableSliceSpacing; - - /** - * indicates the selection distance of a pie slice - */ - private float mShift = 18f; - - private ValuePosition mXValuePosition = ValuePosition.INSIDE_SLICE; - private ValuePosition mYValuePosition = ValuePosition.INSIDE_SLICE; - private int mValueLineColor = 0xff000000; - private boolean mUseValueColorForLine = false; - private float mValueLineWidth = 1.0f; - private float mValueLinePart1OffsetPercentage = 75.f; - private float mValueLinePart1Length = 0.3f; - private float mValueLinePart2Length = 0.4f; - private boolean mValueLineVariableLength = true; - private Integer mHighlightColor = null; - - public PieDataSet(List yVals, String label) { - super(yVals, label); -// mShift = Utils.convertDpToPixel(12f); - } - - @Override - public DataSet copy() { - List entries = new ArrayList<>(); - for (int i = 0; i < mEntries.size(); i++) { - entries.add(mEntries.get(i).copy()); - } - PieDataSet copied = new PieDataSet(entries, getLabel()); - copy(copied); - return copied; - } - - protected void copy(PieDataSet pieDataSet) { - super.copy((BaseDataSet) pieDataSet); - } - - @Override - protected void calcMinMax(PieEntry e) { - - if (e == null) - return; - - calcMinMaxY(e); - } - - /** - * Sets the space that is left out between the piechart-slices in dp. - * Default: 0 --> no space, maximum 20f - * - * @param spaceDp - */ - public void setSliceSpace(float spaceDp) { - - if (spaceDp > 20) - spaceDp = 20f; - if (spaceDp < 0) - spaceDp = 0f; - - mSliceSpace = Utils.convertDpToPixel(spaceDp); - } - - @Override - public float getSliceSpace() { - return mSliceSpace; - } - - /** - * When enabled, slice spacing will be 0.0 when the smallest value is going to be - * smaller than the slice spacing itself. - * - * @param autoDisable - */ - public void setAutomaticallyDisableSliceSpacing(boolean autoDisable) { - mAutomaticallyDisableSliceSpacing = autoDisable; - } - - /** - * When enabled, slice spacing will be 0.0 when the smallest value is going to be - * smaller than the slice spacing itself. - * - * @return - */ - @Override - public boolean isAutomaticallyDisableSliceSpacingEnabled() { - return mAutomaticallyDisableSliceSpacing; - } - - /** - * sets the distance the highlighted piechart-slice of this DataSet is - * "shifted" away from the center of the chart, default 12f - * - * @param shift - */ - public void setSelectionShift(float shift) { - mShift = Utils.convertDpToPixel(shift); - } - - @Override - public float getSelectionShift() { - return mShift; - } - - @Override - public ValuePosition getXValuePosition() { - return mXValuePosition; - } - - public void setXValuePosition(ValuePosition xValuePosition) { - this.mXValuePosition = xValuePosition; - } - - @Override - public ValuePosition getYValuePosition() { - return mYValuePosition; - } - - public void setYValuePosition(ValuePosition yValuePosition) { - this.mYValuePosition = yValuePosition; - } - - /** - * This method is deprecated. - * Use isUseValueColorForLineEnabled() instead. - */ - @Deprecated - public boolean isUsingSliceColorAsValueLineColor() { - return isUseValueColorForLineEnabled(); - } - - /** - * This method is deprecated. - * Use setUseValueColorForLine(...) instead. - * - * @param enabled - */ - @Deprecated - public void setUsingSliceColorAsValueLineColor(boolean enabled) { - setUseValueColorForLine(enabled); - } - - /** - * When valuePosition is OutsideSlice, indicates line color - */ - @Override - public int getValueLineColor() { - return mValueLineColor; - } - - public void setValueLineColor(int valueLineColor) { - this.mValueLineColor = valueLineColor; - } - - @Override - public boolean isUseValueColorForLineEnabled() - { - return mUseValueColorForLine; - } - - public void setUseValueColorForLine(boolean enabled) - { - mUseValueColorForLine = enabled; - } - - /** - * When valuePosition is OutsideSlice, indicates line width - */ - @Override - public float getValueLineWidth() { - return mValueLineWidth; - } - - public void setValueLineWidth(float valueLineWidth) { - this.mValueLineWidth = valueLineWidth; - } - - /** - * When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size - */ - @Override - public float getValueLinePart1OffsetPercentage() { - return mValueLinePart1OffsetPercentage; - } - - public void setValueLinePart1OffsetPercentage(float valueLinePart1OffsetPercentage) { - this.mValueLinePart1OffsetPercentage = valueLinePart1OffsetPercentage; - } - - /** - * When valuePosition is OutsideSlice, indicates length of first half of the line - */ - @Override - public float getValueLinePart1Length() { - return mValueLinePart1Length; - } - - public void setValueLinePart1Length(float valueLinePart1Length) { - this.mValueLinePart1Length = valueLinePart1Length; - } - - /** - * When valuePosition is OutsideSlice, indicates length of second half of the line - */ - @Override - public float getValueLinePart2Length() { - return mValueLinePart2Length; - } - - public void setValueLinePart2Length(float valueLinePart2Length) { - this.mValueLinePart2Length = valueLinePart2Length; - } - - /** - * When valuePosition is OutsideSlice, this allows variable line length - */ - @Override - public boolean isValueLineVariableLength() { - return mValueLineVariableLength; - } - - public void setValueLineVariableLength(boolean valueLineVariableLength) { - this.mValueLineVariableLength = valueLineVariableLength; - } - - /** Gets the color for the highlighted sector */ - @Override - @Nullable - public Integer getHighlightColor() - { - return mHighlightColor; - } - - /** Sets the color for the highlighted sector (null for using entry color) */ - public void setHighlightColor(@Nullable Integer color) - { - this.mHighlightColor = color; - } - - - public enum ValuePosition { - INSIDE_SLICE, - OUTSIDE_SLICE - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieDataSet.kt new file mode 100644 index 0000000000..f14437a8d9 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieDataSet.kt @@ -0,0 +1,209 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.IPieDataSet +import com.github.mikephil.charting.utils.Utils + +open class PieDataSet(yVals: MutableList, label: String) : DataSet(yVals, label), IPieDataSet { + /** + * the space in pixels between the chart-slices, default 0f + */ + private var mSliceSpace = 0f + private var mAutomaticallyDisableSliceSpacing = false + + /** + * indicates the selection distance of a pie slice + */ + private var mShift = 18f + + private var mXValuePosition: ValuePosition? = ValuePosition.INSIDE_SLICE + private var mYValuePosition: ValuePosition? = ValuePosition.INSIDE_SLICE + private var mValueLineColor = -0x1000000 + private var mUseValueColorForLine = false + private var mValueLineWidth = 1.0f + private var mValueLinePart1OffsetPercentage = 75f + private var mValueLinePart1Length = 0.3f + private var mValueLinePart2Length = 0.4f + private var mValueLineVariableLength = true + private var mHighlightColor: Int? = null + + override fun copy(): DataSet { + val entries: MutableList = ArrayList() + for (i in mEntries.indices) { + entries.add(mEntries[i].copy()) + } + val copied = PieDataSet(entries, label) + copy(copied) + return copied + } + + protected fun copy(pieDataSet: PieDataSet) { + super.copy(pieDataSet) + } + + override fun calcMinMax(e: PieEntry) { + calcMinMaxY(e) + } + + /** + * Sets the space that is left out between the piechart-slices in dp. + * Default: 0 --> no space, maximum 20f + * + * @param spaceDp + */ + fun setSliceSpace(spaceDp: Float) { + var spaceDp = spaceDp + if (spaceDp > 20) spaceDp = 20f + if (spaceDp < 0) spaceDp = 0f + + mSliceSpace = Utils.convertDpToPixel(spaceDp) + } + + override val sliceSpace: Float + get() = mSliceSpace + + /** + * When enabled, slice spacing will be 0.0 when the smallest value is going to be + * smaller than the slice spacing itself. + * + * @param autoDisable + */ + fun setAutomaticallyDisableSliceSpacing(autoDisable: Boolean) { + mAutomaticallyDisableSliceSpacing = autoDisable + } + + /** + * When enabled, slice spacing will be 0.0 when the smallest value is going to be + * smaller than the slice spacing itself. + * + * @return + */ + override val isAutomaticallyDisableSliceSpacingEnabled: Boolean + get() = mAutomaticallyDisableSliceSpacing + + /** + * sets the distance the highlighted piechart-slice of this DataSet is + * "shifted" away from the center of the chart, default 12f + * + * @param shift + */ + fun setSelectionShift(shift: Float) { + mShift = Utils.convertDpToPixel(shift) + } + + override val selectionShift: Float + get() = mShift + + override val xValuePosition: ValuePosition? + get() = mXValuePosition + + fun setXValuePosition(xValuePosition: ValuePosition?) { + this.mXValuePosition = xValuePosition + } + + override val yValuePosition: ValuePosition? + get() = mYValuePosition + + fun setYValuePosition(yValuePosition: ValuePosition?) { + this.mYValuePosition = yValuePosition + } + + @get:Deprecated("") + @set:Deprecated("") + var isUsingSliceColorAsValueLineColor: Boolean + /** + * This method is deprecated. + * Use isUseValueColorForLineEnabled() instead. + */ + get() = isUseValueColorForLineEnabled + /** + * This method is deprecated. + * Use setUseValueColorForLine(...) instead. + * + * @param enabled + */ + set(enabled) { + setUseValueColorForLine(enabled) + } + + /** + * When valuePosition is OutsideSlice, indicates line color + */ + override val valueLineColor: Int + get() = mValueLineColor + + fun setValueLineColor(valueLineColor: Int) { + this.mValueLineColor = valueLineColor + } + + override val isUseValueColorForLineEnabled: Boolean + get() = mUseValueColorForLine + + fun setUseValueColorForLine(enabled: Boolean) { + mUseValueColorForLine = enabled + } + + /** + * When valuePosition is OutsideSlice, indicates line width + */ + override val valueLineWidth: Float + get() = mValueLineWidth + + fun setValueLineWidth(valueLineWidth: Float) { + this.mValueLineWidth = valueLineWidth + } + + /** + * When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size + */ + override val valueLinePart1OffsetPercentage: Float + get() = mValueLinePart1OffsetPercentage + + fun setValueLinePart1OffsetPercentage(valueLinePart1OffsetPercentage: Float) { + this.mValueLinePart1OffsetPercentage = valueLinePart1OffsetPercentage + } + + /** + * When valuePosition is OutsideSlice, indicates length of first half of the line + */ + override val valueLinePart1Length: Float + get() = mValueLinePart1Length + + fun setValueLinePart1Length(valueLinePart1Length: Float) { + this.mValueLinePart1Length = valueLinePart1Length + } + + /** + * When valuePosition is OutsideSlice, indicates length of second half of the line + */ + override val valueLinePart2Length: Float + get() = mValueLinePart2Length + + fun setValueLinePart2Length(valueLinePart2Length: Float) { + this.mValueLinePart2Length = valueLinePart2Length + } + + /** + * When valuePosition is OutsideSlice, this allows variable line length + */ + override val isValueLineVariableLength: Boolean + get() = mValueLineVariableLength + + fun setValueLineVariableLength(valueLineVariableLength: Boolean) { + this.mValueLineVariableLength = valueLineVariableLength + } + + /** Gets the color for the highlighted sector */ + override val highlightColor: Int? + get() = mHighlightColor + + /** Sets the color for the highlighted sector (null for using entry color) */ + fun setHighlightColor(color: Int?) { + this.mHighlightColor = color + } + + + enum class ValuePosition { + INSIDE_SLICE, + OUTSIDE_SLICE + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieEntry.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieEntry.java deleted file mode 100644 index 65741ef1da..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieEntry.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.github.mikephil.charting.data; - -import android.annotation.SuppressLint; -import android.graphics.drawable.Drawable; -import android.util.Log; - -/** - * @author Philipp Jahoda - */ -@SuppressLint("ParcelCreator") -public class PieEntry extends Entry { - - private String label; - - public PieEntry(float value) { - super(0f, value); - } - - public PieEntry(float value, Object data) { - super(0f, value, data); - } - - public PieEntry(float value, Drawable icon) { - super(0f, value, icon); - } - - public PieEntry(float value, Drawable icon, Object data) { - super(0f, value, icon, data); - } - - public PieEntry(float value, String label) { - super(0f, value); - this.label = label; - } - - public PieEntry(float value, String label, Object data) { - super(0f, value, data); - this.label = label; - } - - public PieEntry(float value, String label, Drawable icon) { - super(0f, value, icon); - this.label = label; - } - - public PieEntry(float value, String label, Drawable icon, Object data) { - super(0f, value, icon, data); - this.label = label; - } - - /** - * This is the same as getY(). Returns the value of the PieEntry. - * - * @return - */ - public float getValue() { - return getY(); - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - @Deprecated - @Override - public void setX(float x) { - super.setX(x); - Log.i("DEPRECATED", "Pie entries do not have x values"); - } - - @Deprecated - @Override - public float getX() { - Log.i("DEPRECATED", "Pie entries do not have x values"); - return super.getX(); - } - - public PieEntry copy() { - PieEntry e = new PieEntry(getY(), label, getData()); - return e; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieEntry.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieEntry.kt new file mode 100644 index 0000000000..4bc875c457 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieEntry.kt @@ -0,0 +1,62 @@ +package com.github.mikephil.charting.data + +import android.annotation.SuppressLint +import android.graphics.drawable.Drawable +import android.util.Log + +/** + * @author Philipp Jahoda + */ +@SuppressLint("ParcelCreator") +class PieEntry : Entry { + var label: String? = null + + constructor(value: Float) : super(0f, value) + + constructor(value: Float, data: Any?) : super(0f, value, data) + + constructor(value: Float, icon: Drawable?) : super(0f, value, icon) + + constructor(value: Float, icon: Drawable?, data: Any?) : super(0f, value, icon, data) + + constructor(value: Float, label: String?) : super(0f, value) { + this.label = label + } + + constructor(value: Float, label: String?, data: Any?) : super(0f, value, data) { + this.label = label + } + + constructor(value: Float, label: String?, icon: Drawable?) : super(0f, value, icon) { + this.label = label + } + + constructor(value: Float, label: String?, icon: Drawable?, data: Any?) : super(0f, value, icon, data) { + this.label = label + } + + val value: Float + /** + * This is the same as getY(). Returns the value of the PieEntry. + * + * @return + */ + get() = y + + @get:Deprecated("") + @set:Deprecated("") + override var x: Float + get() { + Log.i("DEPRECATED", "Pie entries do not have x values") + return super.x + } + set(x) { + super.x = x + Log.i("DEPRECATED", "Pie entries do not have x values") + } + + override fun copy(): PieEntry { + val e = PieEntry(y, label, data) + return e + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarData.java deleted file mode 100644 index 0c1dbe5505..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarData.java +++ /dev/null @@ -1,58 +0,0 @@ - -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** - * Data container for the RadarChart. - * - * @author Philipp Jahoda - */ -public class RadarData extends ChartData { - - private List mLabels; - - public RadarData() { - super(); - } - - public RadarData(List dataSets) { - super(dataSets); - } - - public RadarData(IRadarDataSet... dataSets) { - super(dataSets); - } - - /** - * Sets the labels that should be drawn around the RadarChart at the end of each web line. - * - * @param labels - */ - public void setLabels(List labels) { - this.mLabels = labels; - } - - /** - * Sets the labels that should be drawn around the RadarChart at the end of each web line. - * - * @param labels - */ - public void setLabels(String... labels) { - this.mLabels = Arrays.asList(labels); - } - - public List getLabels() { - return mLabels; - } - - @Override - public Entry getEntryForHighlight(Highlight highlight) { - return getDataSetByIndex(highlight.getDataSetIndex()).getEntryForIndex((int) highlight.getX()); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarData.kt new file mode 100644 index 0000000000..5538e69e85 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarData.kt @@ -0,0 +1,37 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet + +/** + * Data container for the RadarChart. + * + * @author Philipp Jahoda + */ +class RadarData : ChartData { + /** + * Sets the labels that should be drawn around the RadarChart at the end of each web line. + * + * @param labels + */ + var labels: MutableList? = null + + constructor() : super() + + constructor(dataSets: MutableList) : super(dataSets) + + constructor(vararg dataSets: IRadarDataSet) : super(*dataSets) + + /** + * Sets the labels that should be drawn around the RadarChart at the end of each web line. + * + * @param labels + */ + fun setLabels(vararg labels: String) { + this.labels = mutableListOf(*labels) + } + + override fun getEntryForHighlight(highlight: Highlight): RadarEntry? { + return getDataSetByIndex(highlight.dataSetIndex).getEntryForIndex(highlight.x.toInt()) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.java deleted file mode 100644 index a01604b8c3..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.java +++ /dev/null @@ -1,122 +0,0 @@ - -package com.github.mikephil.charting.data; - -import android.graphics.Color; - -import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; - -import java.util.ArrayList; -import java.util.List; - -public class RadarDataSet extends LineRadarDataSet implements IRadarDataSet { - - /// flag indicating whether highlight circle should be drawn or not - protected boolean mDrawHighlightCircleEnabled = false; - - protected int mHighlightCircleFillColor = Color.WHITE; - - /// The stroke color for highlight circle. - /// If Utils.COLOR_NONE, the color of the dataset is taken. - protected int mHighlightCircleStrokeColor = ColorTemplate.COLOR_NONE; - - protected int mHighlightCircleStrokeAlpha = (int) (0.3 * 255); - protected float mHighlightCircleInnerRadius = 3.0f; - protected float mHighlightCircleOuterRadius = 4.0f; - protected float mHighlightCircleStrokeWidth = 2.0f; - - public RadarDataSet(List yVals, String label) { - super(yVals, label); - } - - /// Returns true if highlight circle should be drawn, false if not - @Override - public boolean isDrawHighlightCircleEnabled() { - return mDrawHighlightCircleEnabled; - } - - /// Sets whether highlight circle should be drawn or not - @Override - public void setDrawHighlightCircleEnabled(boolean enabled) { - mDrawHighlightCircleEnabled = enabled; - } - - @Override - public int getHighlightCircleFillColor() { - return mHighlightCircleFillColor; - } - - public void setHighlightCircleFillColor(int color) { - mHighlightCircleFillColor = color; - } - - /// Returns the stroke color for highlight circle. - /// If Utils.COLOR_NONE, the color of the dataset is taken. - @Override - public int getHighlightCircleStrokeColor() { - return mHighlightCircleStrokeColor; - } - - /// Sets the stroke color for highlight circle. - /// Set to Utils.COLOR_NONE in order to use the color of the dataset; - public void setHighlightCircleStrokeColor(int color) { - mHighlightCircleStrokeColor = color; - } - - @Override - public int getHighlightCircleStrokeAlpha() { - return mHighlightCircleStrokeAlpha; - } - - public void setHighlightCircleStrokeAlpha(int alpha) { - mHighlightCircleStrokeAlpha = alpha; - } - - @Override - public float getHighlightCircleInnerRadius() { - return mHighlightCircleInnerRadius; - } - - public void setHighlightCircleInnerRadius(float radius) { - mHighlightCircleInnerRadius = radius; - } - - @Override - public float getHighlightCircleOuterRadius() { - return mHighlightCircleOuterRadius; - } - - public void setHighlightCircleOuterRadius(float radius) { - mHighlightCircleOuterRadius = radius; - } - - @Override - public float getHighlightCircleStrokeWidth() { - return mHighlightCircleStrokeWidth; - } - - public void setHighlightCircleStrokeWidth(float strokeWidth) { - mHighlightCircleStrokeWidth = strokeWidth; - } - - @Override - public DataSet copy() { - List entries = new ArrayList(); - for (int i = 0; i < mEntries.size(); i++) { - entries.add(mEntries.get(i).copy()); - } - RadarDataSet copied = new RadarDataSet(entries, getLabel()); - copy(copied); - return copied; - } - - protected void copy(RadarDataSet radarDataSet) { - super.copy((BaseDataSet) radarDataSet); - radarDataSet.mDrawHighlightCircleEnabled = mDrawHighlightCircleEnabled; - radarDataSet.mHighlightCircleFillColor = mHighlightCircleFillColor; - radarDataSet.mHighlightCircleInnerRadius = mHighlightCircleInnerRadius; - radarDataSet.mHighlightCircleStrokeAlpha = mHighlightCircleStrokeAlpha; - radarDataSet.mHighlightCircleStrokeColor = mHighlightCircleStrokeColor; - radarDataSet.mHighlightCircleStrokeWidth = mHighlightCircleStrokeWidth; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.kt new file mode 100644 index 0000000000..ccc914f91c --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarDataSet.kt @@ -0,0 +1,40 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet +import com.github.mikephil.charting.utils.ColorTemplate + +open class RadarDataSet(yVals: MutableList, label: String) : LineRadarDataSet(yVals, label), IRadarDataSet { + /** The stroke color for highlight circle. + * If Utils.COLOR_NONE, the color of the dataset is taken. */ + override var highlightCircleStrokeColor: Int = ColorTemplate.COLOR_NONE + + override var highlightCircleStrokeAlpha: Int = (0.3 * 255).toInt() + override var highlightCircleInnerRadius: Float = 3.0f + override var highlightCircleOuterRadius: Float = 4.0f + override var highlightCircleStrokeWidth: Float = 2.0f + + /** Returns true if highlight circle should be drawn, false if not */ + override var isDrawHighlightCircleEnabled: Boolean = false + + override var highlightCircleFillColor: Int = ColorTemplate.COLOR_NONE + + override fun copy(): DataSet { + val entries: MutableList = ArrayList() + for (i in mEntries.indices) { + entries.add(mEntries[i].copy()) + } + val copied = RadarDataSet(entries, label) + copy(copied) + return copied + } + + protected fun copy(radarDataSet: RadarDataSet) { + super.copy(radarDataSet) + radarDataSet.isDrawHighlightCircleEnabled = isDrawHighlightCircleEnabled + radarDataSet.highlightCircleFillColor = highlightCircleFillColor + radarDataSet.highlightCircleInnerRadius = highlightCircleInnerRadius + radarDataSet.highlightCircleStrokeAlpha = highlightCircleStrokeAlpha + radarDataSet.highlightCircleStrokeColor = highlightCircleStrokeColor + radarDataSet.highlightCircleStrokeWidth = highlightCircleStrokeWidth + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarEntry.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarEntry.java deleted file mode 100644 index 02fdce7d32..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarEntry.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.github.mikephil.charting.data; - -import android.annotation.SuppressLint; - -/** - * Created by philipp on 13/06/16. - */ -@SuppressLint("ParcelCreator") -public class RadarEntry extends Entry { - - public RadarEntry(float value) { - super(0f, value); - } - - public RadarEntry(float value, Object data) { - super(0f, value, data); - } - - /** - * This is the same as getY(). Returns the value of the RadarEntry. - * - * @return - */ - public float getValue() { - return getY(); - } - - public RadarEntry copy() { - RadarEntry e = new RadarEntry(getY(), getData()); - return e; - } - - @Deprecated - @Override - public void setX(float x) { - super.setX(x); - } - - @Deprecated - @Override - public float getX() { - return super.getX(); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarEntry.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarEntry.kt new file mode 100644 index 0000000000..563a5ee8c2 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/RadarEntry.kt @@ -0,0 +1,33 @@ +package com.github.mikephil.charting.data + +import android.annotation.SuppressLint + +/** + * Created by philipp on 13/06/16. + */ +@SuppressLint("ParcelCreator") +class RadarEntry : Entry { + constructor(value: Float) : super(0f, value) + + constructor(value: Float, data: Any?) : super(0f, value, data) + + val value: Float + /** + * This is the same as getY(). Returns the value of the RadarEntry. + * + * @return + */ + get() = y + + override fun copy(): RadarEntry { + val e = RadarEntry(y, data) + return e + } + + @set:Deprecated("") + override var x: Float + get() = super.x + set(x) { + super.x = x + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.java deleted file mode 100644 index ba142360f9..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.java +++ /dev/null @@ -1,40 +0,0 @@ - -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; - -import java.util.List; - -public class ScatterData extends BarLineScatterCandleBubbleData { - - public ScatterData() { - super(); - } - - public ScatterData(List dataSets) { - super(dataSets); - } - - public ScatterData(IScatterDataSet... dataSets) { - super(dataSets); - } - - /** - * Returns the maximum shape-size across all DataSets. - * - * @return - */ - public float getGreatestShapeSize() { - - float max = 0f; - - for (IScatterDataSet set : mDataSets) { - float size = set.getScatterShapeSize(); - - if (size > max) - max = size; - } - - return max; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.kt new file mode 100644 index 0000000000..030de587f5 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.kt @@ -0,0 +1,29 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet + +class ScatterData : BarLineScatterCandleBubbleData { + constructor() : super() + + constructor(dataSets: MutableList) : super(dataSets) + + constructor(vararg dataSets: IScatterDataSet) : super(*dataSets) + + val greatestShapeSize: Float + /** + * Returns the maximum shape-size across all DataSets. + * + * @return + */ + get() { + var max = 0f + + for (set in dataSets) { + val size = set.scatterShapeSize + + if (size > max) max = size + } + + return max + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.java deleted file mode 100644 index 95b2e1ff05..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.java +++ /dev/null @@ -1,157 +0,0 @@ - -package com.github.mikephil.charting.data; - -import com.github.mikephil.charting.charts.ScatterChart; -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.renderer.scatter.ChevronDownShapeRenderer; -import com.github.mikephil.charting.renderer.scatter.ChevronUpShapeRenderer; -import com.github.mikephil.charting.renderer.scatter.CircleShapeRenderer; -import com.github.mikephil.charting.renderer.scatter.CrossShapeRenderer; -import com.github.mikephil.charting.renderer.scatter.IShapeRenderer; -import com.github.mikephil.charting.renderer.scatter.SquareShapeRenderer; -import com.github.mikephil.charting.renderer.scatter.TriangleShapeRenderer; -import com.github.mikephil.charting.renderer.scatter.XShapeRenderer; -import com.github.mikephil.charting.utils.ColorTemplate; - -import java.util.ArrayList; -import java.util.List; - -public class ScatterDataSet extends LineScatterCandleRadarDataSet implements IScatterDataSet { - - /** - * the size the scattershape will have, in density pixels - */ - private float mShapeSize = 15f; - - /** - * Renderer responsible for rendering this DataSet, default: square - */ - protected IShapeRenderer mShapeRenderer = new SquareShapeRenderer(); - - /** - * The radius of the hole in the shape (applies to Square, Circle and Triangle) - * - default: 0.0 - */ - private float mScatterShapeHoleRadius = 0f; - - /** - * Color for the hole in the shape. - * Setting to `ColorTemplate.COLOR_NONE` will behave as transparent. - * - default: ColorTemplate.COLOR_NONE - */ - private int mScatterShapeHoleColor = ColorTemplate.COLOR_NONE; - - public ScatterDataSet(List yVals, String label) { - super(yVals, label); - } - - @Override - public DataSet copy() { - List entries = new ArrayList(); - for (int i = 0; i < mEntries.size(); i++) { - entries.add(mEntries.get(i).copy()); - } - ScatterDataSet copied = new ScatterDataSet(entries, getLabel()); - copy(copied); - return copied; - } - - protected void copy(ScatterDataSet scatterDataSet) { - super.copy((BaseDataSet) scatterDataSet); - scatterDataSet.mShapeSize = mShapeSize; - scatterDataSet.mShapeRenderer = mShapeRenderer; - scatterDataSet.mScatterShapeHoleRadius = mScatterShapeHoleRadius; - scatterDataSet.mScatterShapeHoleColor = mScatterShapeHoleColor; - } - - /** - * Sets the size in density pixels the drawn scattershape will have. This - * only applies for non custom shapes. - * - * @param size - */ - public void setScatterShapeSize(float size) { - mShapeSize = size; - } - - @Override - public float getScatterShapeSize() { - return mShapeSize; - } - - /** - * Sets the ScatterShape this DataSet should be drawn with. This will search for an available IShapeRenderer and set this - * renderer for the DataSet. - * - * @param shape - */ - public void setScatterShape(ScatterChart.ScatterShape shape) { - mShapeRenderer = getRendererForShape(shape); - } - - /** - * Sets a new IShapeRenderer responsible for drawing this DataSet. - * This can also be used to set a custom IShapeRenderer aside from the default ones. - * - * @param shapeRenderer - */ - public void setShapeRenderer(IShapeRenderer shapeRenderer) { - mShapeRenderer = shapeRenderer; - } - - @Override - public IShapeRenderer getShapeRenderer() { - return mShapeRenderer; - } - - /** - * Sets the radius of the hole in the shape (applies to Square, Circle and Triangle) - * Set this to <= 0 to remove holes. - * - * @param holeRadius - */ - public void setScatterShapeHoleRadius(float holeRadius) { - mScatterShapeHoleRadius = holeRadius; - } - - @Override - public float getScatterShapeHoleRadius() { - return mScatterShapeHoleRadius; - } - - /** - * Sets the color for the hole in the shape. - * - * @param holeColor - */ - public void setScatterShapeHoleColor(int holeColor) { - mScatterShapeHoleColor = holeColor; - } - - @Override - public int getScatterShapeHoleColor() { - return mScatterShapeHoleColor; - } - - public static IShapeRenderer getRendererForShape(ScatterChart.ScatterShape shape) { - - switch (shape) { - case SQUARE: - return new SquareShapeRenderer(); - case CIRCLE: - return new CircleShapeRenderer(); - case TRIANGLE: - return new TriangleShapeRenderer(); - case CROSS: - return new CrossShapeRenderer(); - case X: - return new XShapeRenderer(); - case CHEVRON_UP: - return new ChevronUpShapeRenderer(); - case CHEVRON_DOWN: - return new ChevronDownShapeRenderer(); - } - - return null; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.kt new file mode 100644 index 0000000000..d651c07e3b --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterDataSet.kt @@ -0,0 +1,80 @@ +package com.github.mikephil.charting.data + +import com.github.mikephil.charting.charts.ScatterChart.ScatterShape +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.renderer.scatter.ChevronDownShapeRenderer +import com.github.mikephil.charting.renderer.scatter.ChevronUpShapeRenderer +import com.github.mikephil.charting.renderer.scatter.CircleShapeRenderer +import com.github.mikephil.charting.renderer.scatter.CrossShapeRenderer +import com.github.mikephil.charting.renderer.scatter.IShapeRenderer +import com.github.mikephil.charting.renderer.scatter.SquareShapeRenderer +import com.github.mikephil.charting.renderer.scatter.TriangleShapeRenderer +import com.github.mikephil.charting.renderer.scatter.XShapeRenderer +import com.github.mikephil.charting.utils.ColorTemplate + +open class ScatterDataSet(yVals: MutableList, label: String) : LineScatterCandleRadarDataSet(yVals, label), IScatterDataSet { + /** + * the size the scattershape will have, in density pixels + */ + override var scatterShapeSize = 15f + + /** + * Renderer responsible for rendering this DataSet, default: square + */ + override var shapeRenderer: IShapeRenderer? = SquareShapeRenderer() + + /** + * The radius of the hole in the shape (applies to Square, Circle and Triangle) + * - default: 0.0 + */ + override var scatterShapeHoleRadius = 0f + + /** + * Color for the hole in the shape. + * Setting to `ColorTemplate.COLOR_NONE` will behave as transparent. + * - default: ColorTemplate.COLOR_NONE + */ + override var scatterShapeHoleColor = ColorTemplate.COLOR_NONE + + override fun copy(): DataSet { + val entries: MutableList = ArrayList() + for (i in mEntries.indices) { + entries.add(mEntries[i].copy()) + } + val copied = ScatterDataSet(entries, label) + copy(copied) + return copied + } + + protected fun copy(scatterDataSet: ScatterDataSet) { + super.copy(scatterDataSet) + scatterDataSet.scatterShapeSize = scatterShapeSize + scatterDataSet.shapeRenderer = shapeRenderer + scatterDataSet.scatterShapeHoleRadius = scatterShapeHoleRadius + scatterDataSet.scatterShapeHoleColor = scatterShapeHoleColor + } + + /** + * Sets the ScatterShape this DataSet should be drawn with. This will search for an available IShapeRenderer and set this + * renderer for the DataSet. + * + * @param shape + */ + fun setScatterShape(shape: ScatterShape) { + shapeRenderer = getRendererForShape(shape) + } + + companion object { + fun getRendererForShape(shape: ScatterShape): IShapeRenderer? { + return when (shape) { + ScatterShape.SQUARE -> SquareShapeRenderer() + ScatterShape.CIRCLE -> CircleShapeRenderer() + ScatterShape.TRIANGLE -> TriangleShapeRenderer() + ScatterShape.CROSS -> CrossShapeRenderer() + ScatterShape.X -> XShapeRenderer() + ScatterShape.CHEVRON_UP -> ChevronUpShapeRenderer() + ScatterShape.CHEVRON_DOWN -> ChevronDownShapeRenderer() + } + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/Approximator.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/Approximator.java deleted file mode 100644 index 542188e602..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/Approximator.java +++ /dev/null @@ -1,102 +0,0 @@ - -package com.github.mikephil.charting.data.filter; - -import android.annotation.TargetApi; -import android.os.Build; - -import java.util.Arrays; - -/** - * Implemented according to Wiki-Pseudocode {@link} - * http://en.wikipedia.org/wiki/Ramer�Douglas�Peucker_algorithm - * - * @author Philipp Baldauf & Phliipp Jahoda - */ -public class Approximator { - - @TargetApi(Build.VERSION_CODES.GINGERBREAD) - public float[] reduceWithDouglasPeucker(float[] points, float tolerance) { - - int greatestIndex = 0; - float greatestDistance = 0f; - - Line line = new Line(points[0], points[1], points[points.length - 2], points[points.length - 1]); - - for (int i = 2; i < points.length - 2; i += 2) { - - float distance = line.distance(points[i], points[i + 1]); - - if (distance > greatestDistance) { - greatestDistance = distance; - greatestIndex = i; - } - } - - if (greatestDistance > tolerance) { - - float[] reduced1 = reduceWithDouglasPeucker(Arrays.copyOfRange(points, 0, greatestIndex + 2), tolerance); - float[] reduced2 = reduceWithDouglasPeucker(Arrays.copyOfRange(points, greatestIndex, points.length), - tolerance); - - float[] result1 = reduced1; - float[] result2 = Arrays.copyOfRange(reduced2, 2, reduced2.length); - - return concat(result1, result2); - } else { - return line.getPoints(); - } - } - - /** - * Combine arrays. - * - * @param arrays - * @return - */ - float[] concat(float[]... arrays) { - int length = 0; - for (float[] array : arrays) { - length += array.length; - } - float[] result = new float[length]; - int pos = 0; - for (float[] array : arrays) { - for (float element : array) { - result[pos] = element; - pos++; - } - } - return result; - } - - private class Line { - - private float[] points; - - private float sxey; - private float exsy; - - private float dx; - private float dy; - - private float length; - - public Line(float x1, float y1, float x2, float y2) { - dx = x1 - x2; - dy = y1 - y2; - sxey = x1 * y2; - exsy = x2 * y1; - length = (float) Math.sqrt(dx * dx + dy * dy); - - points = new float[]{x1, y1, x2, y2}; - } - - public float distance(float x, float y) { - return Math.abs(dy * x - dx * y + sxey - exsy) / length; - } - - public float[] getPoints() { - return points; - } - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/Approximator.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/Approximator.kt new file mode 100644 index 0000000000..ef1d46d726 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/Approximator.kt @@ -0,0 +1,83 @@ +package com.github.mikephil.charting.data.filter + +import kotlin.math.abs +import kotlin.math.sqrt + +/** + * Implemented according to Wiki-Pseudocode [] + * http://en.wikipedia.org/wiki/Ramer�Douglas�Peucker_algorithm + * + * @author Philipp Baldauf & Phliipp Jahoda + */ +class Approximator { + fun reduceWithDouglasPeucker(points: FloatArray, tolerance: Float): FloatArray { + var greatestIndex = 0 + var greatestDistance = 0f + + val line = Line(points[0], points[1], points[points.size - 2], points[points.size - 1]) + + var i = 2 + while (i < points.size - 2) { + val distance = line.distance(points[i], points[i + 1]) + + if (distance > greatestDistance) { + greatestDistance = distance + greatestIndex = i + } + i += 2 + } + + if (greatestDistance > tolerance) { + val reduced1 = reduceWithDouglasPeucker(points.copyOfRange(0, greatestIndex + 2), tolerance) + val reduced2 = reduceWithDouglasPeucker( + points.copyOfRange(greatestIndex, points.size), + tolerance + ) + + val result1 = reduced1 + val result2 = reduced2.copyOfRange(2, reduced2.size) + + return concat(result1, result2) + } else { + return line.points + } + } + + /** + * Combine arrays. + * + * @param arrays + * @return + */ + fun concat(vararg arrays: FloatArray): FloatArray { + var length = 0 + for (array in arrays) { + length += array.size + } + val result = FloatArray(length) + var pos = 0 + for (array in arrays) { + for (element in array) { + result[pos] = element + pos++ + } + } + return result + } + + private class Line(x1: Float, y1: Float, x2: Float, y2: Float) { + val points: FloatArray = floatArrayOf(x1, y1, x2, y2) + + private val sxey: Float = x1 * y2 + private val exsy: Float = x2 * y1 + + private val dx: Float = x1 - x2 + private val dy: Float = y1 - y2 + + private val length: Float = sqrt((dx * dx + dy * dy).toDouble()).toFloat() + + fun distance(x: Float, y: Float): Float { + return abs(dy * x - dx * y + sxey - exsy) / length + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.java deleted file mode 100644 index 9351341c76..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.java +++ /dev/null @@ -1,146 +0,0 @@ - -package com.github.mikephil.charting.data.filter; - -import java.util.ArrayList; - -/** - * Implemented according to modified Douglas Peucker {@link} - * http://psimpl.sourceforge.net/douglas-peucker.html - */ -public class ApproximatorN -{ - public float[] reduceWithDouglasPeucker(float[] points, float resultCount) { - - int pointCount = points.length / 2; - - // if a shape has 2 or less points it cannot be reduced - if (resultCount <= 2 || resultCount >= pointCount) - return points; - - boolean[] keep = new boolean[pointCount]; - - // first and last always stay - keep[0] = true; - keep[pointCount - 1] = true; - - int currentStoredPoints = 2; - - ArrayList queue = new ArrayList<>(); - Line line = new Line(0, pointCount - 1, points); - queue.add(line); - - do { - line = queue.remove(queue.size() - 1); - - // store the key - keep[line.index] = true; - - // check point count tolerance - currentStoredPoints += 1; - - if (currentStoredPoints == resultCount) - break; - - // split the polyline at the key and recurse - Line left = new Line(line.start, line.index, points); - if (left.index > 0) { - int insertionIndex = insertionIndex(left, queue); - queue.add(insertionIndex, left); - } - - Line right = new Line(line.index, line.end, points); - if (right.index > 0) { - int insertionIndex = insertionIndex(right, queue); - queue.add(insertionIndex, right); - } - } while (queue.isEmpty()); - - float[] reducedEntries = new float[currentStoredPoints * 2]; - - for (int i = 0, i2 = 0, r2 = 0; i < currentStoredPoints; i++, r2 += 2) { - if (keep[i]) { - reducedEntries[i2++] = points[r2]; - reducedEntries[i2++] = points[r2 + 1]; - } - } - - return reducedEntries; - } - - private static float distanceToLine( - float ptX, float ptY, float[] - fromLinePoint1, float[] fromLinePoint2) { - float dx = fromLinePoint2[0] - fromLinePoint1[0]; - float dy = fromLinePoint2[1] - fromLinePoint1[1]; - - float dividend = Math.abs( - dy * ptX - - dx * ptY - - fromLinePoint1[0] * fromLinePoint2[1] + - fromLinePoint2[0] * fromLinePoint1[1]); - double divisor = Math.sqrt(dx * dx + dy * dy); - - return (float)(dividend / divisor); - } - - private static class Line { - int start; - int end; - - float distance = 0; - int index = 0; - - Line(int start, int end, float[] points) { - this.start = start; - this.end = end; - - float[] startPoint = new float[]{points[start * 2], points[start * 2 + 1]}; - float[] endPoint = new float[]{points[end * 2], points[end * 2 + 1]}; - - if (end <= start + 1) return; - - for (int i = start + 1, i2 = i * 2; i < end; i++, i2 += 2) { - float distance = distanceToLine( - points[i2], points[i2 + 1], - startPoint, endPoint); - - if (distance > this.distance) { - this.index = i; - this.distance = distance; - } - } - } - - boolean equals(final Line rhs) { - return (start == rhs.start) && (end == rhs.end) && index == rhs.index; - } - - boolean lessThan(final Line rhs) { - return distance < rhs.distance; - } - } - - private static int insertionIndex(Line line, ArrayList queue) { - int min = 0; - int max = queue.size(); - - while (!queue.isEmpty()) { - int midIndex = min + (max - min) / 2; - Line midLine = queue.get(midIndex); - - if (midLine.equals(line)) { - return midIndex; - } - else if (line.lessThan(midLine)) { - // perform search in left half - max = midIndex; - } - else { - // perform search in right half - min = midIndex + 1; - } - } - - return min; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.kt new file mode 100644 index 0000000000..0c8b0e1faf --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/filter/ApproximatorN.kt @@ -0,0 +1,145 @@ +package com.github.mikephil.charting.data.filter + +import kotlin.math.abs +import kotlin.math.sqrt + +/** + * Implemented according to modified Douglas Peucker [] + * http://psimpl.sourceforge.net/douglas-peucker.html + */ +class ApproximatorN { + fun reduceWithDouglasPeucker(points: FloatArray, resultCount: Float): FloatArray { + val pointCount = points.size / 2 + + // if a shape has 2 or less points it cannot be reduced + if (resultCount <= 2 || resultCount >= pointCount) return points + + val keep = BooleanArray(pointCount) + + // first and last always stay + keep[0] = true + keep[pointCount - 1] = true + + var currentStoredPoints = 2 + + val queue = ArrayList() + var line = Line(0, pointCount - 1, points) + queue.add(line) + + do { + line = queue.removeAt(queue.size - 1) + + // store the key + keep[line.index] = true + + // check point count tolerance + currentStoredPoints += 1 + + if (currentStoredPoints.toFloat() == resultCount) break + + // split the polyline at the key and recurse + val left = Line(line.start, line.index, points) + if (left.index > 0) { + val insertionIndex: Int = insertionIndex(left, queue) + queue.add(insertionIndex, left) + } + + val right = Line(line.index, line.end, points) + if (right.index > 0) { + val insertionIndex: Int = insertionIndex(right, queue) + queue.add(insertionIndex, right) + } + } while (queue.isEmpty()) + + val reducedEntries = FloatArray(currentStoredPoints * 2) + + var i = 0 + var i2 = 0 + var r2 = 0 + while (i < currentStoredPoints) { + if (keep[i]) { + reducedEntries[i2++] = points[r2] + reducedEntries[i2++] = points[r2 + 1] + } + i++ + r2 += 2 + } + + return reducedEntries + } + + private class Line(var start: Int, var end: Int, points: FloatArray) { + var distance: Float = 0f + var index: Int = 0 + + init { + val startPoint = floatArrayOf(points[start * 2], points[start * 2 + 1]) + val endPoint = floatArrayOf(points[end * 2], points[end * 2 + 1]) + + if (end > start + 1) { + var i = start + 1 + var i2 = i * 2 + while (i < end) { + val distance: Float = distanceToLine( + points[i2], points[i2 + 1], + startPoint, endPoint + ) + + if (distance > this.distance) { + this.index = i + this.distance = distance + } + i++ + i2 += 2 + } + } + } + + fun equals(rhs: Line): Boolean { + return (start == rhs.start) && (end == rhs.end) && index == rhs.index + } + + fun lessThan(rhs: Line): Boolean { + return distance < rhs.distance + } + } + + companion object { + private fun distanceToLine( + ptX: Float, ptY: Float, fromLinePoint1: FloatArray, fromLinePoint2: FloatArray + ): Float { + val dx = fromLinePoint2[0] - fromLinePoint1[0] + val dy = fromLinePoint2[1] - fromLinePoint1[1] + + val dividend = abs( + dy * ptX - dx * ptY - fromLinePoint1[0] * fromLinePoint2[1] + + fromLinePoint2[0] * fromLinePoint1[1] + ) + val divisor = sqrt((dx * dx + dy * dy).toDouble()) + + return (dividend / divisor).toFloat() + } + + private fun insertionIndex(line: Line, queue: ArrayList): Int { + var min = 0 + var max = queue.size + + while (!queue.isEmpty()) { + val midIndex = min + (max - min) / 2 + val midLine = queue[midIndex] + + if (midLine.equals(line)) { + return midIndex + } else if (line.lessThan(midLine)) { + // perform search in left half + max = midIndex + } else { + // perform search in right half + min = midIndex + 1 + } + } + + return min + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/exception/DrawingDataSetNotCreatedException.java b/MPChartLib/src/main/java/com/github/mikephil/charting/exception/DrawingDataSetNotCreatedException.java deleted file mode 100644 index d6c1237c52..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/exception/DrawingDataSetNotCreatedException.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.github.mikephil.charting.exception; - -public class DrawingDataSetNotCreatedException extends RuntimeException { - - /** - * - */ - private static final long serialVersionUID = 1L; - - public DrawingDataSetNotCreatedException() { - super("Have to create a new drawing set first. Call ChartData's createNewDrawingDataSet() method"); - } - -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/exception/DrawingDataSetNotCreatedException.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/exception/DrawingDataSetNotCreatedException.kt new file mode 100644 index 0000000000..a6a670e9c8 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/exception/DrawingDataSetNotCreatedException.kt @@ -0,0 +1,10 @@ +package com.github.mikephil.charting.exception + +object DrawingDataSetNotCreatedException : RuntimeException() { + private fun readResolve(): Any = DrawingDataSetNotCreatedException + + /** + * + */ + private const val serialVersionUID = 1L +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultAxisValueFormatter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultAxisValueFormatter.kt index 9ad583e3fa..f095c11928 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultAxisValueFormatter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultAxisValueFormatter.kt @@ -31,7 +31,7 @@ open class DefaultAxisValueFormatter(digits: Int) : IAxisValueFormatter { decimalFormat = DecimalFormat("###,###,###,##0$b") } - override fun getFormattedValue(value: Float, axis: AxisBase?): String? { + override fun getFormattedValue(value: Float, axis: AxisBase?): String { // avoid memory allocations here (for performance) return decimalFormat.format(value.toDouble()) } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultFillFormatter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultFillFormatter.kt index 990027d497..ede441c6ec 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultFillFormatter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultFillFormatter.kt @@ -7,13 +7,12 @@ import com.github.mikephil.charting.interfaces.datasets.ILineDataSet * Default formatter that calculates the position of the filled line. */ open class DefaultFillFormatter : IFillFormatter { - - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider?): Float { + override fun getFillLinePosition(dataSet: ILineDataSet, dataProvider: LineDataProvider): Float { val fillMin: Float - val chartMaxY = dataProvider!!.yChartMax + val chartMaxY = dataProvider.yChartMax val chartMinY = dataProvider.yChartMin - val data = dataProvider.lineData - fillMin = if (dataSet!!.yMax > 0 && dataSet.yMin < 0) { + val data = dataProvider.lineData ?: return 0f + fillMin = if (dataSet.yMax > 0 && dataSet.yMin < 0) { 0f } else { val max: Float = if (data.yMax > 0) 0f diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultValueFormatter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultValueFormatter.kt index 3d357a9817..c2c14746f3 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultValueFormatter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/DefaultValueFormatter.kt @@ -48,6 +48,6 @@ open class DefaultValueFormatter(digits: Int) : IValueFormatter { // put more logic here ... // avoid memory allocations here (for performance reasons) - return decimalFormat!!.format(value.toDouble()) + return decimalFormat?.format(value.toDouble()) } } \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/IAxisValueFormatter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/IAxisValueFormatter.kt index 023669f909..01464fcbca 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/IAxisValueFormatter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/IAxisValueFormatter.kt @@ -15,5 +15,5 @@ interface IAxisValueFormatter { * @param axis the axis the value belongs to * @return */ - fun getFormattedValue(value: Float, axis: AxisBase?): String? + fun getFormattedValue(value: Float, axis: AxisBase?): String } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/IFillFormatter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/IFillFormatter.kt index 3487a751da..a41951979d 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/IFillFormatter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/IFillFormatter.kt @@ -15,5 +15,5 @@ interface IFillFormatter { * @param dataProvider * @return */ - fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider?): Float + fun getFillLinePosition(dataSet: ILineDataSet, dataProvider: LineDataProvider): Float } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/PercentFormatter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/PercentFormatter.kt index ca20515a9e..d13f1f7291 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/PercentFormatter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/formatter/PercentFormatter.kt @@ -31,7 +31,7 @@ open class PercentFormatter : IValueFormatter, IAxisValueFormatter { } // IAxisValueFormatter - override fun getFormattedValue(value: Float, axis: AxisBase?): String? { + override fun getFormattedValue(value: Float, axis: AxisBase?): String { return decimalFormat.format(value.toDouble()) + " %" } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.java b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.java deleted file mode 100644 index af83a4539f..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.java +++ /dev/null @@ -1,163 +0,0 @@ -package com.github.mikephil.charting.highlight; - -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData; -import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.utils.MPPointD; - -/** - * Created by Philipp Jahoda on 22/07/15. - */ -public class BarHighlighter extends ChartHighlighter { - - public BarHighlighter(BarDataProvider chart) { - super(chart); - } - - @Override - public Highlight getHighlight(float x, float y) { - Highlight high = super.getHighlight(x, y); - - if(high == null) { - return null; - } - - MPPointD pos = getValsForTouch(x, y); - - BarData barData = mChart.getBarData(); - - IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex()); - if (set.isStacked()) { - - return getStackedHighlight(high, - set, - (float) pos.x, - (float) pos.y); - } - - MPPointD.recycleInstance(pos); - - return high; - } - - /** - * This method creates the Highlight object that also indicates which value of a stacked BarEntry has been - * selected. - * - * @param high the Highlight to work with looking for stacked values - * @param set - * @param xVal - * @param yVal - * @return - */ - public Highlight getStackedHighlight(Highlight high, IBarDataSet set, float xVal, float yVal) { - - BarEntry entry = set.getEntryForXValue(xVal, yVal); - - if (entry == null) - return null; - - // not stacked - if (entry.getYVals() == null) { - return high; - } else { - Range[] ranges = entry.getRanges(); - - if (ranges.length > 0) { - int stackIndex = getClosestStackIndex(ranges, yVal); - - MPPointD pixels = mChart.getTransformer(set.getAxisDependency()).getPixelForValues(high.getX(), ranges[stackIndex].to); - - Highlight stackedHigh = new Highlight( - entry.getX(), - entry.getY(), - (float) pixels.x, - (float) pixels.y, - high.getDataSetIndex(), - stackIndex, - high.getAxis() - ); - - MPPointD.recycleInstance(pixels); - - return stackedHigh; - } - } - - return null; - } - - /** - * Returns the index of the closest value inside the values array / ranges (stacked barchart) to the value - * given as - * a parameter. - * - * @param ranges - * @param value - * @return - */ - protected int getClosestStackIndex(Range[] ranges, float value) { - - if (ranges == null || ranges.length == 0) - return 0; - - int stackIndex = 0; - - for (Range range : ranges) { - if (range.contains(value)) - return stackIndex; - else - stackIndex++; - } - - int length = Math.max(ranges.length - 1, 0); - - return (value > ranges[length].to) ? length : 0; - } - -// /** -// * Splits up the stack-values of the given bar-entry into Range objects. -// * -// * @param entry -// * @return -// */ -// protected Range[] getRanges(BarEntry entry) { -// -// float[] values = entry.getYVals(); -// -// if (values == null || values.length == 0) -// return new Range[0]; -// -// Range[] ranges = new Range[values.length]; -// -// float negRemain = -entry.getNegativeSum(); -// float posRemain = 0f; -// -// for (int i = 0; i < ranges.length; i++) { -// -// float value = values[i]; -// -// if (value < 0) { -// ranges[i] = new Range(negRemain, negRemain + Math.abs(value)); -// negRemain += Math.abs(value); -// } else { -// ranges[i] = new Range(posRemain, posRemain + value); -// posRemain += value; -// } -// } -// -// return ranges; -// } - - @Override - protected float getDistance(float x1, float y1, float x2, float y2) { - return Math.abs(x1 - x2); - } - - @Override - protected BarLineScatterCandleBubbleData getData() { - return mChart.getBarData(); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.kt new file mode 100644 index 0000000000..fe017731e7 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.kt @@ -0,0 +1,151 @@ +package com.github.mikephil.charting.highlight + +import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.utils.MPPointD +import kotlin.math.abs +import kotlin.math.max + +/** + * Created by Philipp Jahoda on 22/07/15. + */ +open class BarHighlighter(chart: BarDataProvider?) : ChartHighlighter(chart) { + override fun getHighlight(x: Float, y: Float): Highlight? { + val high = super.getHighlight(x, y) + + if (high == null) { + return null + } + + val pos = getValsForTouch(x, y) ?: return null + + val barData = mChart?.barData ?: return null + + val set = barData.getDataSetByIndex(high.dataSetIndex) + if (set.isStacked) { + return getStackedHighlight( + high, + set, + pos.x.toFloat(), + pos.y.toFloat() + ) + } + + MPPointD.Companion.recycleInstance(pos) + + return high + } + + /** + * This method creates the Highlight object that also indicates which value of a stacked BarEntry has been + * selected. + * + * @param high the Highlight to work with looking for stacked values + * @param set + * @param xVal + * @param yVal + * @return + */ + fun getStackedHighlight(high: Highlight, set: IBarDataSet, xVal: Float, yVal: Float): Highlight? { + val entry = set.getEntryForXValue(xVal, yVal) + + if (entry == null) return null + + // not stacked + if (entry.yVals == null) { + return high + } else { + val ranges = entry.ranges + + if (ranges.isNotEmpty()) { + val stackIndex = getClosestStackIndex(ranges, yVal) + + val pixels = mChart?.getTransformer(set.axisDependency)?.getPixelForValues(high.x, ranges[stackIndex].to) + + return if (pixels != null) { + val stackedHigh = Highlight( + entry.x, + entry.y, + pixels.x.toFloat(), + pixels.y.toFloat(), + high.dataSetIndex, + stackIndex, + high.axis + ) + + MPPointD.Companion.recycleInstance(pixels) + + stackedHigh + } else { + null + } + } + } + + return null + } + + /** + * Returns the index of the closest value inside the values array / ranges (stacked barchart) to the value + * given as + * a parameter. + * + * @param ranges + * @param value + * @return + */ + protected fun getClosestStackIndex(ranges: Array?, value: Float): Int { + if (ranges == null || ranges.isEmpty()) return 0 + + var stackIndex = 0 + + for (range in ranges) { + if (range.contains(value)) return stackIndex + else stackIndex++ + } + + val length = max(ranges.size - 1, 0) + + return if (value > ranges[length].to) length else 0 + } + + // /** + // * Splits up the stack-values of the given bar-entry into Range objects. + // * + // * @param entry + // * @return + // */ + // protected Range[] getRanges(BarEntry entry) { + // + // float[] values = entry.getYVals(); + // + // if (values == null || values.length == 0) + // return new Range[0]; + // + // Range[] ranges = new Range[values.length]; + // + // float negRemain = -entry.getNegativeSum(); + // float posRemain = 0f; + // + // for (int i = 0; i < ranges.length; i++) { + // + // float value = values[i]; + // + // if (value < 0) { + // ranges[i] = new Range(negRemain, negRemain + Math.abs(value)); + // negRemain += Math.abs(value); + // } else { + // ranges[i] = new Range(posRemain, posRemain + value); + // posRemain += value; + // } + // } + // + // return ranges; + // } + override fun getDistance(x1: Float, y1: Float, x2: Float, y2: Float): Float { + return abs(x1 - x2) + } + + override val data + get() = mChart?.barData +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.java b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.java deleted file mode 100644 index f889bf19d4..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.java +++ /dev/null @@ -1,246 +0,0 @@ -package com.github.mikephil.charting.highlight; - -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData; -import com.github.mikephil.charting.data.DataSet; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.interfaces.dataprovider.BarLineScatterCandleBubbleDataProvider; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.utils.MPPointD; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by Philipp Jahoda on 21/07/15. - */ -public class ChartHighlighter implements IHighlighter -{ - - /** - * instance of the data-provider - */ - protected T mChart; - - /** - * buffer for storing previously highlighted values - */ - protected List mHighlightBuffer = new ArrayList(); - - public ChartHighlighter(T chart) { - this.mChart = chart; - } - - @Override - public Highlight getHighlight(float x, float y) { - - MPPointD pos = getValsForTouch(x, y); - float xVal = (float) pos.x; - MPPointD.recycleInstance(pos); - - Highlight high = getHighlightForX(xVal, x, y); - return high; - } - - /** - * Returns a recyclable MPPointD instance. - * Returns the corresponding xPos for a given touch-position in pixels. - * - * @param x - * @param y - * @return - */ - protected MPPointD getValsForTouch(float x, float y) { - - // take any transformer to determine the x-axis value - MPPointD pos = mChart.getTransformer(YAxis.AxisDependency.LEFT).getValuesByTouchPoint(x, y); - return pos; - } - - /** - * Returns the corresponding Highlight for a given xVal and x- and y-touch position in pixels. - * - * @param xVal - * @param x - * @param y - * @return - */ - protected Highlight getHighlightForX(float xVal, float x, float y) { - - List closestValues = getHighlightsAtXValue(xVal, x, y); - - if(closestValues.isEmpty()) { - return null; - } - - float leftAxisMinDist = getMinimumDistance(closestValues, y, YAxis.AxisDependency.LEFT); - float rightAxisMinDist = getMinimumDistance(closestValues, y, YAxis.AxisDependency.RIGHT); - - YAxis.AxisDependency axis = leftAxisMinDist < rightAxisMinDist ? YAxis.AxisDependency.LEFT : YAxis.AxisDependency.RIGHT; - - Highlight detail = getClosestHighlightByPixel(closestValues, x, y, axis, mChart.getMaxHighlightDistance()); - - return detail; - } - - /** - * Returns the minimum distance from a touch value (in pixels) to the - * closest value (in pixels) that is displayed in the chart. - * - * @param closestValues - * @param pos - * @param axis - * @return - */ - protected float getMinimumDistance(List closestValues, float pos, YAxis.AxisDependency axis) { - - float distance = Float.MAX_VALUE; - - for (int i = 0; i < closestValues.size(); i++) { - - Highlight high = closestValues.get(i); - - if (high.getAxis() == axis) { - - float tempDistance = Math.abs(getHighlightPos(high) - pos); - if (tempDistance < distance) { - distance = tempDistance; - } - } - } - - return distance; - } - - protected float getHighlightPos(Highlight h) { - return h.getYPx(); - } - - /** - * Returns a list of Highlight objects representing the entries closest to the given xVal. - * The returned list contains two objects per DataSet (closest rounding up, closest rounding down). - * - * @param xVal the transformed x-value of the x-touch position - * @param x touch position - * @param y touch position - * @return - */ - protected List getHighlightsAtXValue(float xVal, float x, float y) { - - mHighlightBuffer.clear(); - - BarLineScatterCandleBubbleData data = getData(); - - if (data == null) - return mHighlightBuffer; - - for (int i = 0, dataSetCount = data.getDataSetCount(); i < dataSetCount; i++) { - - IDataSet dataSet = data.getDataSetByIndex(i); - - // don't include DataSets that cannot be highlighted - if (!dataSet.isHighlightEnabled()) - continue; - - mHighlightBuffer.addAll(buildHighlights(dataSet, i, xVal, DataSet.Rounding.CLOSEST)); - } - - return mHighlightBuffer; - } - - /** - * An array of `Highlight` objects corresponding to the selected xValue and dataSetIndex. - * - * @param set - * @param dataSetIndex - * @param xVal - * @param rounding - * @return - */ - protected List buildHighlights(IDataSet set, int dataSetIndex, float xVal, DataSet.Rounding rounding) { - - ArrayList highlights = new ArrayList<>(); - - //noinspection unchecked - List entries = set.getEntriesForXValue(xVal); - if (entries.size() == 0) { - // Try to find closest x-value and take all entries for that x-value - final Entry closest = set.getEntryForXValue(xVal, Float.NaN, rounding); - if (closest != null) - { - //noinspection unchecked - entries = set.getEntriesForXValue(closest.getX()); - } - } - - if (entries.size() == 0) - return highlights; - - for (Entry e : entries) { - MPPointD pixels = mChart.getTransformer( - set.getAxisDependency()).getPixelForValues(e.getX(), e.getY()); - - highlights.add(new Highlight( - e.getX(), e.getY(), - (float) pixels.x, (float) pixels.y, - dataSetIndex, set.getAxisDependency())); - } - - return highlights; - } - - /** - * Returns the Highlight of the DataSet that contains the closest value on the - * y-axis. - * - * @param closestValues contains two Highlight objects per DataSet closest to the selected x-position (determined by - * rounding up an down) - * @param x - * @param y - * @param axis the closest axis - * @param minSelectionDistance - * @return - */ - public Highlight getClosestHighlightByPixel(List closestValues, float x, float y, - YAxis.AxisDependency axis, float minSelectionDistance) { - - Highlight closest = null; - float distance = minSelectionDistance; - - for (int i = 0; i < closestValues.size(); i++) { - - Highlight high = closestValues.get(i); - - if (axis == null || high.getAxis() == axis) { - - float cDistance = getDistance(x, y, high.getXPx(), high.getYPx()); - - if (cDistance < distance) { - closest = high; - distance = cDistance; - } - } - } - - return closest; - } - - /** - * Calculates the distance between the two given points. - * - * @param x1 - * @param y1 - * @param x2 - * @param y2 - * @return - */ - protected float getDistance(float x1, float y1, float x2, float y2) { - //return Math.abs(y1 - y2); - //return Math.abs(x1 - x2); - return (float) Math.hypot(x1 - x2, y1 - y2); - } - - protected BarLineScatterCandleBubbleData getData() { - return mChart.getData(); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.kt new file mode 100644 index 0000000000..b8d4c886a2 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.kt @@ -0,0 +1,231 @@ +package com.github.mikephil.charting.highlight + +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.DataSet.Rounding +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.interfaces.dataprovider.BarLineScatterCandleBubbleDataProvider +import com.github.mikephil.charting.interfaces.datasets.IDataSet +import com.github.mikephil.charting.utils.MPPointD +import kotlin.math.abs +import kotlin.math.hypot + +/** + * Created by Philipp Jahoda on 21/07/15. + */ +open class ChartHighlighter( + /** + * instance of the data-provider + */ + protected var mChart: T? +) : IHighlighter { + /** + * buffer for storing previously highlighted values + */ + protected var mHighlightBuffer: MutableList = ArrayList() + + override fun getHighlight(x: Float, y: Float): Highlight? { + val pos = getValsForTouch(x, y) ?: return null + val xVal = pos.x.toFloat() + MPPointD.Companion.recycleInstance(pos) + + val high = getHighlightForX(xVal, x, y) + return high + } + + /** + * Returns a recyclable MPPointD instance. + * Returns the corresponding xPos for a given touch-position in pixels. + * + * @param x + * @param y + * @return + */ + protected fun getValsForTouch(x: Float, y: Float): MPPointD? { + // take any transformer to determine the x-axis value + + val pos = mChart?.getTransformer(AxisDependency.LEFT)?.getValuesByTouchPoint(x, y) + return pos + } + + /** + * Returns the corresponding Highlight for a given xVal and x- and y-touch position in pixels. + * + * @param xVal + * @param x + * @param y + * @return + */ + protected fun getHighlightForX(xVal: Float, x: Float, y: Float): Highlight? { + val closestValues = getHighlightsAtXValue(xVal, x, y) + + if (closestValues.isEmpty()) { + return null + } + + val leftAxisMinDist = getMinimumDistance(closestValues, y, AxisDependency.LEFT) + val rightAxisMinDist = getMinimumDistance(closestValues, y, AxisDependency.RIGHT) + + val axis = if (leftAxisMinDist < rightAxisMinDist) AxisDependency.LEFT else AxisDependency.RIGHT + + val detail = getClosestHighlightByPixel(closestValues, x, y, axis, mChart!!.maxHighlightDistance) + + return detail + } + + /** + * Returns the minimum distance from a touch value (in pixels) to the + * closest value (in pixels) that is displayed in the chart. + * + * @param closestValues + * @param pos + * @param axis + * @return + */ + protected fun getMinimumDistance(closestValues: MutableList, pos: Float, axis: AxisDependency?): Float { + var distance = Float.Companion.MAX_VALUE + + for (i in closestValues.indices) { + val high = closestValues[i] + + if (high.axis == axis) { + val tempDistance = abs(getHighlightPos(high) - pos) + if (tempDistance < distance) { + distance = tempDistance + } + } + } + + return distance + } + + protected fun getHighlightPos(h: Highlight): Float { + return h.yPx + } + + /** + * Returns a list of Highlight objects representing the entries closest to the given xVal. + * The returned list contains two objects per DataSet (closest rounding up, closest rounding down). + * + * @param xVal the transformed x-value of the x-touch position + * @param x touch position + * @param y touch position + * @return + */ + protected open fun getHighlightsAtXValue(xVal: Float, x: Float, y: Float): MutableList { + mHighlightBuffer.clear() + + val data = this.data + + if (data == null) return mHighlightBuffer + + var i = 0 + val dataSetCount = data.dataSetCount + while (i < dataSetCount) { + val dataSet = data.getDataSetByIndex(i) + + // don't include DataSets that cannot be highlighted + if (!dataSet.isHighlightEnabled) { + i++ + continue + } + + mHighlightBuffer.addAll(buildHighlights(dataSet, i, xVal, Rounding.CLOSEST)) + i++ + } + + return mHighlightBuffer + } + + /** + * An array of `Highlight` objects corresponding to the selected xValue and dataSetIndex. + * + * @param set + * @param dataSetIndex + * @param xVal + * @param rounding + * @return + */ + protected open fun buildHighlights(set: IDataSet<*>, dataSetIndex: Int, xVal: Float, rounding: Rounding?): MutableList { + val highlights = ArrayList() + + var entries = set.getEntriesForXValue(xVal) + if (entries.isEmpty()) { + // Try to find closest x-value and take all entries for that x-value + val closest: Entry? = set.getEntryForXValue(xVal, Float.Companion.NaN, rounding) + if (closest != null) { + entries = set.getEntriesForXValue(closest.x) + } + } + + if (entries.isEmpty()) return highlights + + for (e in entries) { + val pixels = mChart!!.getTransformer( + set.axisDependency + ).getPixelForValues(e.x, e.y) + + highlights.add( + Highlight( + e.x, e.y, + pixels.x.toFloat(), pixels.y.toFloat(), + dataSetIndex, set.axisDependency + ) + ) + } + + return highlights + } + + /** + * Returns the Highlight of the DataSet that contains the closest value on the + * y-axis. + * + * @param closestValues contains two Highlight objects per DataSet closest to the selected x-position (determined by + * rounding up an down) + * @param x + * @param y + * @param axis the closest axis + * @param minSelectionDistance + * @return + */ + fun getClosestHighlightByPixel( + closestValues: MutableList, x: Float, y: Float, + axis: AxisDependency?, minSelectionDistance: Float + ): Highlight? { + var closest: Highlight? = null + var distance = minSelectionDistance + + for (i in closestValues.indices) { + val high = closestValues[i] + + if (axis == null || high.axis == axis) { + val cDistance = getDistance(x, y, high.xPx, high.yPx) + + if (cDistance < distance) { + closest = high + distance = cDistance + } + } + } + + return closest + } + + /** + * Calculates the distance between the two given points. + * + * @param x1 + * @param y1 + * @param x2 + * @param y2 + * @return + */ + protected open fun getDistance(x1: Float, y1: Float, x2: Float, y2: Float): Float { + //return Math.abs(y1 - y2); + //return Math.abs(x1 - x2); + return hypot((x1 - x2).toDouble(), (y1 - y2).toDouble()).toFloat() + } + + protected open val data + get() = mChart!!.data +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.java b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.java deleted file mode 100644 index 76788af6e0..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.github.mikephil.charting.highlight; - -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData; -import com.github.mikephil.charting.data.ChartData; -import com.github.mikephil.charting.data.DataSet; -import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider; -import com.github.mikephil.charting.interfaces.dataprovider.CombinedDataProvider; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; - -import java.util.List; - -/** - * Created by Philipp Jahoda on 12/09/15. - */ -public class CombinedHighlighter extends ChartHighlighter implements IHighlighter -{ - - /** - * bar highlighter for supporting stacked highlighting - */ - protected BarHighlighter barHighlighter; - - public CombinedHighlighter(CombinedDataProvider chart, BarDataProvider barChart) { - super(chart); - - // if there is BarData, create a BarHighlighter - barHighlighter = barChart.getBarData() == null ? null : new BarHighlighter(barChart); - } - - @Override - protected List getHighlightsAtXValue(float xVal, float x, float y) { - - mHighlightBuffer.clear(); - - List dataObjects = mChart.getCombinedData().getAllData(); - - for (int i = 0; i < dataObjects.size(); i++) { - - ChartData dataObject = dataObjects.get(i); - - // in case of BarData, let the BarHighlighter take over - if (barHighlighter != null && dataObject instanceof BarData) { - Highlight high = barHighlighter.getHighlight(x, y); - - if (high != null) { - high.setDataIndex(i); - mHighlightBuffer.add(high); - } - } else { - - for (int j = 0, dataSetCount = dataObject.getDataSetCount(); j < dataSetCount; j++) { - - IDataSet dataSet = dataObjects.get(i).getDataSetByIndex(j); - - // don't include datasets that cannot be highlighted - if (!dataSet.isHighlightEnabled()) - continue; - - List highs = buildHighlights(dataSet, j, xVal, DataSet.Rounding.CLOSEST); - for (Highlight high : highs) - { - high.setDataIndex(i); - mHighlightBuffer.add(high); - } - } - } - } - - return mHighlightBuffer; - } - -// protected Highlight getClosest(float x, float y, Highlight... highs) { -// -// Highlight closest = null; -// float minDistance = Float.MAX_VALUE; -// -// for (Highlight high : highs) { -// -// if (high == null) -// continue; -// -// float tempDistance = getDistance(x, y, high.getXPx(), high.getYPx()); -// -// if (tempDistance < minDistance) { -// minDistance = tempDistance; -// closest = high; -// } -// } -// -// return closest; -// } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.kt new file mode 100644 index 0000000000..b157f0ff4b --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.kt @@ -0,0 +1,77 @@ +package com.github.mikephil.charting.highlight + +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.DataSet.Rounding +import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider +import com.github.mikephil.charting.interfaces.dataprovider.CombinedDataProvider + +/** + * Created by Philipp Jahoda on 12/09/15. + */ +open class CombinedHighlighter(chart: CombinedDataProvider?, barChart: BarDataProvider) : ChartHighlighter(chart), IHighlighter { + /** + * bar highlighter for supporting stacked highlighting + */ + // if there is BarData, create a BarHighlighter + protected var barHighlighter = if (barChart.barData == null) null else BarHighlighter(barChart) + + override fun getHighlightsAtXValue(xVal: Float, x: Float, y: Float): MutableList { + mHighlightBuffer.clear() + + val dataObjects = mChart?.combinedData?.allData ?: return mutableListOf() + + for (i in dataObjects.indices) { + val dataObject = dataObjects[i] + + // in case of BarData, let the BarHighlighter take over + if (barHighlighter != null && dataObject is BarData) { + val high = barHighlighter?.getHighlight(x, y) + + if (high != null) { + high.dataIndex = i + mHighlightBuffer.add(high) + } + } else { + var j = 0 + val dataSetCount = dataObject.dataSetCount + while (j < dataSetCount) { + val dataSet = dataObjects[i].getDataSetByIndex(j) + + // don't include datasets that cannot be highlighted + if (!dataSet.isHighlightEnabled) { + j++ + continue + } + + val highs = buildHighlights(dataSet, j, xVal, Rounding.CLOSEST) + for (high in highs) { + high.dataIndex = i + mHighlightBuffer.add(high) + } + j++ + } + } + } + + return mHighlightBuffer + } // protected Highlight getClosest(float x, float y, Highlight... highs) { + // + // Highlight closest = null; + // float minDistance = Float.MAX_VALUE; + // + // for (Highlight high : highs) { + // + // if (high == null) + // continue; + // + // float tempDistance = getDistance(x, y, high.getXPx(), high.getYPx()); + // + // if (tempDistance < minDistance) { + // minDistance = tempDistance; + // closest = high; + // } + // } + // + // return closest; + // } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.java b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.java deleted file mode 100644 index d96298e07d..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.java +++ /dev/null @@ -1,85 +0,0 @@ -package com.github.mikephil.charting.highlight; - -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.DataSet; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.utils.MPPointD; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by Philipp Jahoda on 22/07/15. - */ -public class HorizontalBarHighlighter extends BarHighlighter { - - public HorizontalBarHighlighter(BarDataProvider chart) { - super(chart); - } - - @Override - public Highlight getHighlight(float x, float y) { - - BarData barData = mChart.getBarData(); - - MPPointD pos = getValsForTouch(y, x); - - Highlight high = getHighlightForX((float) pos.y, y, x); - if (high == null) - return null; - - IBarDataSet set = barData.getDataSetByIndex(high.getDataSetIndex()); - if (set.isStacked()) { - - return getStackedHighlight(high, - set, - (float) pos.y, - (float) pos.x); - } - - MPPointD.recycleInstance(pos); - - return high; - } - - @Override - protected List buildHighlights(IDataSet set, int dataSetIndex, float xVal, DataSet.Rounding rounding) { - - ArrayList highlights = new ArrayList<>(); - - //noinspection unchecked - List entries = set.getEntriesForXValue(xVal); - if (entries.size() == 0) { - // Try to find closest x-value and take all entries for that x-value - final Entry closest = set.getEntryForXValue(xVal, Float.NaN, rounding); - if (closest != null) - { - //noinspection unchecked - entries = set.getEntriesForXValue(closest.getX()); - } - } - - if (entries.size() == 0) - return highlights; - - for (Entry e : entries) { - MPPointD pixels = mChart.getTransformer( - set.getAxisDependency()).getPixelForValues(e.getY(), e.getX()); - - highlights.add(new Highlight( - e.getX(), e.getY(), - (float) pixels.x, (float) pixels.y, - dataSetIndex, set.getAxisDependency())); - } - - return highlights; - } - - @Override - protected float getDistance(float x1, float y1, float x2, float y2) { - return Math.abs(y1 - y2); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.kt new file mode 100644 index 0000000000..e9c7d4612d --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.kt @@ -0,0 +1,73 @@ +package com.github.mikephil.charting.highlight + +import com.github.mikephil.charting.data.DataSet.Rounding +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider +import com.github.mikephil.charting.interfaces.datasets.IDataSet +import com.github.mikephil.charting.utils.MPPointD +import kotlin.math.abs + +/** + * Created by Philipp Jahoda on 22/07/15. + */ +class HorizontalBarHighlighter(chart: BarDataProvider?) : BarHighlighter(chart) { + override fun getHighlight(x: Float, y: Float): Highlight? { + val barData = mChart?.barData ?: return null + + val pos = getValsForTouch(y, x) ?: return null + + val high = getHighlightForX(pos.y.toFloat(), y, x) + if (high == null) return null + + val set = barData.getDataSetByIndex(high.dataSetIndex) + if (set.isStacked) { + return getStackedHighlight( + high, + set, + pos.y.toFloat(), + pos.x.toFloat() + ) + } + + MPPointD.Companion.recycleInstance(pos) + + return high + } + + override fun buildHighlights(set: IDataSet<*>, dataSetIndex: Int, xVal: Float, rounding: Rounding?): MutableList { + val highlights = ArrayList() + + var entries = set.getEntriesForXValue(xVal) + if (entries.isEmpty()) { + // Try to find closest x-value and take all entries for that x-value + val closest: Entry? = set.getEntryForXValue(xVal, Float.Companion.NaN, rounding) + if (closest != null) { + entries = set.getEntriesForXValue(closest.x) + } + } + + if (entries.isEmpty()) return highlights + + for (e in entries) { + val pixels = mChart?.getTransformer( + set.axisDependency + )?.getPixelForValues(e.y, e.x) + + if (pixels != null) { + highlights.add( + Highlight( + e.x, e.y, + pixels.x.toFloat(), pixels.y.toFloat(), + dataSetIndex, set.axisDependency + ) + ) + } + } + + return highlights + } + + override fun getDistance(x1: Float, y1: Float, x2: Float, y2: Float): Float { + return abs(y1 - y2) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/IHighlighter.java b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/IHighlighter.kt similarity index 62% rename from MPChartLib/src/main/java/com/github/mikephil/charting/highlight/IHighlighter.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/highlight/IHighlighter.kt index d0ca0cfe57..da4cd3e5e8 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/IHighlighter.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/IHighlighter.kt @@ -1,11 +1,9 @@ -package com.github.mikephil.charting.highlight; +package com.github.mikephil.charting.highlight /** * Created by philipp on 10/06/16. */ -public interface IHighlighter -{ - +interface IHighlighter { /** * Returns a Highlight object corresponding to the given x- and y- touch positions in pixels. * @@ -13,5 +11,5 @@ public interface IHighlighter * @param y * @return */ - Highlight getHighlight(float x, float y); + fun getHighlight(x: Float, y: Float): Highlight? } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieHighlighter.kt index 362cdf0dd9..d490246f84 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieHighlighter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieHighlighter.kt @@ -4,8 +4,8 @@ import com.github.mikephil.charting.charts.PieChart import com.github.mikephil.charting.data.Entry class PieHighlighter(chart: PieChart) : PieRadarHighlighter(chart) { - override fun getClosestHighlight(index: Int, x: Float, y: Float): Highlight { - val set = mChart!!.data!!.dataSet + override fun getClosestHighlight(index: Int, x: Float, y: Float): Highlight? { + val set = mChart?.data?.dataSet ?: return null val entry: Entry = set.getEntryForIndex(index) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.java b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.java deleted file mode 100644 index 4e58b761d4..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.github.mikephil.charting.highlight; - -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.charts.PieRadarChartBase; - -import java.util.ArrayList; -import java.util.List; - -/** - * Created by philipp on 12/06/16. - */ -public abstract class PieRadarHighlighter implements IHighlighter { - - protected T mChart; - - /** - * buffer for storing previously highlighted values - */ - protected List mHighlightBuffer = new ArrayList(); - - public PieRadarHighlighter(T chart) { - this.mChart = chart; - } - - @Override - public Highlight getHighlight(float x, float y) { - - float touchDistanceToCenter = mChart.distanceToCenter(x, y); - - // check if a slice was touched - if (touchDistanceToCenter > mChart.getRadius()) { - - // if no slice was touched, highlight nothing - return null; - - } else { - - float angle = mChart.getAngleForPoint(x, y); - - if (mChart instanceof PieChart) { - angle /= mChart.getAnimator().getPhaseY(); - } - - int index = mChart.getIndexForAngle(angle); - - // check if the index could be found - if (index < 0 || index >= mChart.getData().getMaxEntryCountSet().getEntryCount()) { - return null; - - } else { - return getClosestHighlight(index, x, y); - } - } - } - - /** - * Returns the closest Highlight object of the given objects based on the touch position inside the chart. - * - * @param index - * @param x - * @param y - * @return - */ - protected abstract Highlight getClosestHighlight(int index, float x, float y); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.kt new file mode 100644 index 0000000000..960fee4ccc --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.kt @@ -0,0 +1,52 @@ +package com.github.mikephil.charting.highlight + +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.charts.PieRadarChartBase + +/** + * Created by philipp on 12/06/16. + */ +abstract class PieRadarHighlighter?>(protected var mChart: T?) : IHighlighter { + /** + * buffer for storing previously highlighted values + */ + protected var mHighlightBuffer: MutableList = ArrayList() + + override fun getHighlight(x: Float, y: Float): Highlight? { + val chart = mChart ?: return null + + val touchDistanceToCenter = chart.distanceToCenter(x, y) + + // check if a slice was touched + if (touchDistanceToCenter > chart.radius) { + // if no slice was touched, highlight nothing + + return null + } else { + var angle = chart.getAngleForPoint(x, y) + + if (mChart is PieChart) { + angle /= chart.animator.phaseY + } + + val index = chart.getIndexForAngle(angle) + + // check if the index could be found + return if (index < 0 || chart.data?.maxEntryCountSet?.entryCount?.let { it >= index } == true) { + null + } else { + getClosestHighlight(index, x, y) + } + } + } + + /** + * Returns the closest Highlight object of the given objects based on the touch position inside the chart. + * + * @param index + * @param x + * @param y + * @return + */ + protected abstract fun getClosestHighlight(index: Int, x: Float, y: Float): Highlight? +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.java b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.java deleted file mode 100644 index 3c4f6d03ac..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.github.mikephil.charting.highlight; - -import com.github.mikephil.charting.charts.RadarChart; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.Utils; - -import java.util.List; - -/** - * Created by philipp on 12/06/16. - */ -public class RadarHighlighter extends PieRadarHighlighter { - - public RadarHighlighter(RadarChart chart) { - super(chart); - } - - @Override - protected Highlight getClosestHighlight(int index, float x, float y) { - - List highlights = getHighlightsAtIndex(index); - - float distanceToCenter = mChart.distanceToCenter(x, y) / mChart.getFactor(); - - Highlight closest = null; - float distance = Float.MAX_VALUE; - - for (int i = 0; i < highlights.size(); i++) { - - Highlight high = highlights.get(i); - - float cdistance = Math.abs(high.getY() - distanceToCenter); - if (cdistance < distance) { - closest = high; - distance = cdistance; - } - } - - return closest; - } - /** - * Returns an array of Highlight objects for the given index. The Highlight - * objects give information about the value at the selected index and the - * DataSet it belongs to. INFORMATION: This method does calculations at - * runtime. Do not over-use in performance critical situations. - * - * @param index - * @return - */ - protected List getHighlightsAtIndex(int index) { - - mHighlightBuffer.clear(); - - float phaseX = mChart.getAnimator().getPhaseX(); - float phaseY = mChart.getAnimator().getPhaseY(); - float sliceangle = mChart.getSliceAngle(); - float factor = mChart.getFactor(); - - MPPointF pOut = MPPointF.getInstance(0,0); - for (int i = 0; i < mChart.getData().getDataSetCount(); i++) { - - IDataSet dataSet = mChart.getData().getDataSetByIndex(i); - - final Entry entry = dataSet.getEntryForIndex(index); - - float y = (entry.getY() - mChart.getYChartMin()); - - Utils.getPosition( - mChart.getCenterOffsets(), y * factor * phaseY, - sliceangle * index * phaseX + mChart.getRotationAngle(), pOut); - - mHighlightBuffer.add(new Highlight(index, entry.getY(), pOut.x, pOut.y, i, dataSet.getAxisDependency())); - } - - return mHighlightBuffer; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.kt new file mode 100644 index 0000000000..a557c59ad1 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.kt @@ -0,0 +1,72 @@ +package com.github.mikephil.charting.highlight + +import com.github.mikephil.charting.charts.RadarChart +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Utils +import kotlin.math.abs + +/** + * Created by philipp on 12/06/16. + */ +open class RadarHighlighter(chart: RadarChart) : PieRadarHighlighter(chart) { + override fun getClosestHighlight(index: Int, x: Float, y: Float): Highlight? { + val chart = mChart ?: return null + val highlights = getHighlightsAtIndex(index) + + val distanceToCenter = chart.distanceToCenter(x, y) / chart.factor + + var closest: Highlight? = null + var distance = Float.Companion.MAX_VALUE + + for (i in highlights.indices) { + val high = highlights[i] + + val cdistance = abs(high.y - distanceToCenter) + if (cdistance < distance) { + closest = high + distance = cdistance + } + } + + return closest + } + + /** + * Returns an array of Highlight objects for the given index. The Highlight + * objects give information about the value at the selected index and the + * DataSet it belongs to. INFORMATION: This method does calculations at + * runtime. Do not over-use in performance critical situations. + * + * @param index + * @return + */ + protected fun getHighlightsAtIndex(index: Int): MutableList { + mHighlightBuffer.clear() + + val chart = mChart ?: return mutableListOf() + + val phaseX = chart.animator.phaseX + val phaseY = chart.animator.phaseY + val sliceangle = chart.sliceAngle + val factor = chart.factor + + val pOut: MPPointF = MPPointF.Companion.getInstance(0f, 0f) + val chartData = chart.data ?: return mutableListOf() + for (i in 0.. then ranges are (-10 - 0, 0 - 5, 5 - 25). - */ -public final class Range { - - public float from; - public float to; - - public Range(float from, float to) { - this.from = from; - this.to = to; - } - - /** - * Returns true if this range contains (if the value is in between) the given value, false if not. - * - * @param value - * @return - */ - public boolean contains(float value) { - - if (value > from && value <= to) - return true; - else - return false; - } - - public boolean isLarger(float value) { - return value > to; - } - - public boolean isSmaller(float value) { - return value < from; - } -} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/Range.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/Range.kt new file mode 100644 index 0000000000..01495626fa --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/Range.kt @@ -0,0 +1,25 @@ +package com.github.mikephil.charting.highlight + +/** + * Created by Philipp Jahoda on 24/07/15. Class that represents the range of one value in a stacked bar entry. e.g. + * stack values are -10, 5, 20 -> then ranges are (-10 - 0, 0 - 5, 5 - 25). + */ +class Range(var from: Float, var to: Float) { + /** + * Returns true if this range contains (if the value is in between) the given value, false if not. + * + * @param value + * @return + */ + fun contains(value: Float): Boolean { + return value > from && value <= to + } + + fun isLarger(value: Float): Boolean { + return value > to + } + + fun isSmaller(value: Float): Boolean { + return value < from + } +} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarDataProvider.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarDataProvider.java deleted file mode 100644 index 9dfee07f9c..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarDataProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mikephil.charting.interfaces.dataprovider; - -import com.github.mikephil.charting.data.BarData; - -public interface BarDataProvider extends BarLineScatterCandleBubbleDataProvider { - - BarData getBarData(); - boolean isDrawBarShadowEnabled(); - boolean isDrawValueAboveBarEnabled(); - boolean isHighlightFullBarEnabled(); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarDataProvider.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarDataProvider.kt new file mode 100644 index 0000000000..499a2a53e3 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarDataProvider.kt @@ -0,0 +1,10 @@ +package com.github.mikephil.charting.interfaces.dataprovider + +import com.github.mikephil.charting.data.BarData + +interface BarDataProvider : BarLineScatterCandleBubbleDataProvider { + var barData: BarData? + var isDrawBarShadowEnabled: Boolean + var isDrawValueAboveBarEnabled: Boolean + var isHighlightFullBarEnabled: Boolean +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarLineScatterCandleBubbleDataProvider.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarLineScatterCandleBubbleDataProvider.kt index eea0b5e562..20ff755f72 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarLineScatterCandleBubbleDataProvider.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BarLineScatterCandleBubbleDataProvider.kt @@ -1,13 +1,14 @@ package com.github.mikephil.charting.interfaces.dataprovider import com.github.mikephil.charting.components.YAxis.AxisDependency -import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData +import com.github.mikephil.charting.data.ChartData import com.github.mikephil.charting.utils.Transformer interface BarLineScatterCandleBubbleDataProvider : ChartInterface { - fun getTransformer(axis: AxisDependency?): Transformer? + fun getTransformer(axis: AxisDependency?): Transformer fun isInverted(axis: AxisDependency?): Boolean val lowestVisibleX: Float val highestVisibleX: Float - override fun getData(): BarLineScatterCandleBubbleData<*> + + override val data: ChartData<*, *>? } \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BubbleDataProvider.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BubbleDataProvider.java deleted file mode 100644 index 82ee30ad7c..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BubbleDataProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.mikephil.charting.interfaces.dataprovider; - -import com.github.mikephil.charting.data.BubbleData; - -public interface BubbleDataProvider extends BarLineScatterCandleBubbleDataProvider { - - BubbleData getBubbleData(); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BubbleDataProvider.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BubbleDataProvider.kt new file mode 100644 index 0000000000..f0a7bb4af3 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/BubbleDataProvider.kt @@ -0,0 +1,7 @@ +package com.github.mikephil.charting.interfaces.dataprovider + +import com.github.mikephil.charting.data.BubbleData + +interface BubbleDataProvider : BarLineScatterCandleBubbleDataProvider { + var bubbleData: BubbleData? +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CandleDataProvider.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CandleDataProvider.java deleted file mode 100644 index 357403f98a..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CandleDataProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.mikephil.charting.interfaces.dataprovider; - -import com.github.mikephil.charting.data.CandleData; - -public interface CandleDataProvider extends BarLineScatterCandleBubbleDataProvider { - - CandleData getCandleData(); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CandleDataProvider.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CandleDataProvider.kt new file mode 100644 index 0000000000..842f059e12 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CandleDataProvider.kt @@ -0,0 +1,7 @@ +package com.github.mikephil.charting.interfaces.dataprovider + +import com.github.mikephil.charting.data.CandleData + +interface CandleDataProvider : BarLineScatterCandleBubbleDataProvider { + var candleData: CandleData? +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ChartInterface.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ChartInterface.java deleted file mode 100644 index 86fc62c37f..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ChartInterface.java +++ /dev/null @@ -1,62 +0,0 @@ -package com.github.mikephil.charting.interfaces.dataprovider; - -import android.graphics.RectF; - -import com.github.mikephil.charting.data.ChartData; -import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.utils.MPPointF; - -import androidx.annotation.Nullable; - -/** - * Interface that provides everything there is to know about the dimensions, - * bounds, and range of the chart. - * - * @author Philipp Jahoda - */ -public interface ChartInterface { - - /** - * Returns the minimum x value of the chart, regardless of zoom or translation. - */ - float getXChartMin(); - - /** - * Returns the maximum x value of the chart, regardless of zoom or translation. - */ - float getXChartMax(); - - float getXRange(); - - /** - * Returns the minimum y value of the chart, regardless of zoom or translation. - */ - float getYChartMin(); - - /** - * Returns the maximum y value of the chart, regardless of zoom or translation. - */ - float getYChartMax(); - - /** - * Returns the maximum distance in scren dp a touch can be away from an entry to cause it to get highlighted. - */ - float getMaxHighlightDistance(); - - int getWidth(); - - int getHeight(); - - MPPointF getCenterOfView(); - - MPPointF getCenterOffsets(); - - RectF getContentRect(); - - IValueFormatter getDefaultValueFormatter(); - - @Nullable - ChartData getData(); - - int getMaxVisibleCount(); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ChartInterface.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ChartInterface.kt new file mode 100644 index 0000000000..343795731c --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ChartInterface.kt @@ -0,0 +1,57 @@ +package com.github.mikephil.charting.interfaces.dataprovider + +import android.graphics.RectF +import com.github.mikephil.charting.data.ChartData +import com.github.mikephil.charting.formatter.IValueFormatter +import com.github.mikephil.charting.utils.MPPointF + +/** + * Interface that provides everything there is to know about the dimensions, + * bounds, and range of the chart. + * + * @author Philipp Jahoda + */ +interface ChartInterface { + /** + * Returns the minimum x value of the chart, regardless of zoom or translation. + */ + val xChartMin: Float + + /** + * Returns the maximum x value of the chart, regardless of zoom or translation. + */ + val xChartMax: Float + + val xRange: Float + + /** + * Returns the minimum y value of the chart, regardless of zoom or translation. + */ + val yChartMin: Float + + /** + * Returns the maximum y value of the chart, regardless of zoom or translation. + */ + val yChartMax: Float + + /** + * Returns the maximum distance in scren dp a touch can be away from an entry to cause it to get highlighted. + */ + val maxHighlightDistance: Float + + fun getWidth(): Int + + fun getHeight(): Int + + val centerOfView: MPPointF? + + val centerOffsets: MPPointF + + val contentRect: RectF? + + val defaultValueFormatter: IValueFormatter? + + val data: ChartData<*, *>? + + val maxVisibleCount: Int +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CombinedDataProvider.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CombinedDataProvider.java deleted file mode 100644 index 574d26a2a2..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CombinedDataProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.github.mikephil.charting.interfaces.dataprovider; - -import com.github.mikephil.charting.data.CombinedData; - -/** - * Created by philipp on 11/06/16. - */ -public interface CombinedDataProvider extends LineDataProvider, BarDataProvider, BubbleDataProvider, CandleDataProvider, ScatterDataProvider { - - CombinedData getCombinedData(); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CombinedDataProvider.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CombinedDataProvider.kt new file mode 100644 index 0000000000..81472cbe63 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/CombinedDataProvider.kt @@ -0,0 +1,10 @@ +package com.github.mikephil.charting.interfaces.dataprovider + +import com.github.mikephil.charting.data.CombinedData + +/** + * Created by philipp on 11/06/16. + */ +interface CombinedDataProvider : LineDataProvider, BarDataProvider, BubbleDataProvider, CandleDataProvider, ScatterDataProvider { + val combinedData: CombinedData? +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/LineDataProvider.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/LineDataProvider.kt index 73785b90ed..8185c61553 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/LineDataProvider.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/LineDataProvider.kt @@ -5,6 +5,6 @@ import com.github.mikephil.charting.components.YAxis.AxisDependency import com.github.mikephil.charting.data.LineData interface LineDataProvider : BarLineScatterCandleBubbleDataProvider { - val lineData: LineData - fun getAxis(dependency: AxisDependency): YAxis? + var lineData: LineData? + fun getAxis(axis: AxisDependency?): YAxis? } \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ScatterDataProvider.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ScatterDataProvider.java deleted file mode 100644 index b58d5af95d..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ScatterDataProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.github.mikephil.charting.interfaces.dataprovider; - -import com.github.mikephil.charting.data.ScatterData; - -public interface ScatterDataProvider extends BarLineScatterCandleBubbleDataProvider { - - ScatterData getScatterData(); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ScatterDataProvider.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ScatterDataProvider.kt new file mode 100644 index 0000000000..80a2e57671 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/dataprovider/ScatterDataProvider.kt @@ -0,0 +1,7 @@ +package com.github.mikephil.charting.interfaces.dataprovider + +import com.github.mikephil.charting.data.ScatterData + +interface ScatterDataProvider : BarLineScatterCandleBubbleDataProvider { + var scatterData: ScatterData? +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBarDataSet.kt similarity index 68% rename from MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBarDataSet.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBarDataSet.kt index 5e82a48420..344ab64f78 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBarDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBarDataSet.kt @@ -1,25 +1,22 @@ -package com.github.mikephil.charting.interfaces.datasets; +package com.github.mikephil.charting.interfaces.datasets -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.utils.Fill; - -import java.util.List; +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.utils.Fill /** * Created by philipp on 21/10/15. */ -public interface IBarDataSet extends IBarLineScatterCandleBubbleDataSet { - - List getFills(); +interface IBarDataSet : IBarLineScatterCandleBubbleDataSet { + val fills: MutableList - Fill getFill(int index); + fun getFill(index: Int): Fill /** * Returns true if this DataSet is stacked (stacksize > 1) or not. * * @return */ - boolean isStacked(); + val isStacked: Boolean /** * Returns the maximum number of bars that can be stacked upon another in @@ -27,7 +24,7 @@ public interface IBarDataSet extends IBarLineScatterCandleBubbleDataSet? } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBubbleDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBubbleDataSet.java deleted file mode 100644 index e284aac209..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBubbleDataSet.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.github.mikephil.charting.interfaces.datasets; - -import com.github.mikephil.charting.data.BubbleEntry; - -/** - * Created by philipp on 21/10/15. - */ -public interface IBubbleDataSet extends IBarLineScatterCandleBubbleDataSet { - - /** - * Sets the width of the circle that surrounds the bubble when highlighted, - * in dp. - * - * @param width - */ - void setHighlightCircleWidth(float width); - - float getMaxSize(); - - boolean isNormalizeSizeEnabled(); - - /** - * Returns the width of the highlight-circle that surrounds the bubble - * @return - */ - float getHighlightCircleWidth(); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBubbleDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBubbleDataSet.kt new file mode 100644 index 0000000000..e0f4d07363 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IBubbleDataSet.kt @@ -0,0 +1,24 @@ +package com.github.mikephil.charting.interfaces.datasets + +import com.github.mikephil.charting.data.BubbleEntry + +/** + * Created by philipp on 21/10/15. + */ +interface IBubbleDataSet : IBarLineScatterCandleBubbleDataSet { + val maxSize: Float + + val isNormalizeSizeEnabled: Boolean + + /** + * Returns the width of the highlight-circle that surrounds the bubble + * @return + */ + /** + * Sets the width of the circle that surrounds the bubble when highlighted, + * in dp. + * + * @param width + */ + var highlightCircleWidth: Float +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ICandleDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ICandleDataSet.kt similarity index 65% rename from MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ICandleDataSet.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ICandleDataSet.kt index 1d004ed959..833b1c6ff8 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ICandleDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ICandleDataSet.kt @@ -1,21 +1,19 @@ -package com.github.mikephil.charting.interfaces.datasets; +package com.github.mikephil.charting.interfaces.datasets -import android.graphics.Paint; - -import com.github.mikephil.charting.data.CandleEntry; +import android.graphics.Paint +import com.github.mikephil.charting.data.CandleEntry /** * Created by philipp on 21/10/15. */ -public interface ICandleDataSet extends ILineScatterCandleRadarDataSet { - +interface ICandleDataSet : ILineScatterCandleRadarDataSet { /** * Returns the space that is left out on the left and right side of each * candle. * * @return */ - float getBarSpace(); + val barSpace: Float /** * Returns whether the candle bars should show? @@ -25,61 +23,61 @@ public interface ICandleDataSet extends ILineScatterCandleRadarDataSet close). * * @return */ - int getDecreasingColor(); + val decreasingColor: Int /** * Returns paint style when open < close * * @return */ - Paint.Style getIncreasingPaintStyle(); + val increasingPaintStyle: Paint.Style? /** * Returns paint style when open > close * * @return */ - Paint.Style getDecreasingPaintStyle(); + val decreasingPaintStyle: Paint.Style? /** * Is the shadow color same as the candle color? * * @return */ - boolean getShadowColorSameAsCandle(); + val shadowColorSameAsCandle: Boolean } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IDataSet.kt similarity index 73% rename from MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IDataSet.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IDataSet.kt index 6f1849e7e0..dd9b869321 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IDataSet.kt @@ -1,56 +1,52 @@ -package com.github.mikephil.charting.interfaces.datasets; +package com.github.mikephil.charting.interfaces.datasets -import android.graphics.DashPathEffect; -import android.graphics.Typeface; - -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.DataSet; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.utils.MPPointF; - -import java.util.List; - -public interface IDataSet { +import android.graphics.DashPathEffect +import android.graphics.Typeface +import com.github.mikephil.charting.components.Legend.LegendForm +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.DataSet.Rounding +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.formatter.IValueFormatter +import com.github.mikephil.charting.utils.MPPointF +interface IDataSet { /** * returns the minimum y-value this DataSet holds */ - float getYMin(); + val yMin: Float /** * returns the maximum y-value this DataSet holds */ - float getYMax(); + val yMax: Float /** * returns the minimum x-value this DataSet holds */ - float getXMin(); + val xMin: Float /** * returns the maximum x-value this DataSet holds */ - float getXMax(); + val xMax: Float /** * Returns the number of y-values this DataSet represents -> the size of the y-values array * -> yvals.size() */ - int getEntryCount(); + val entryCount: Int /** * Calculates the minimum and maximum x and y values (mXMin, mXMax, mYMin, mYMax). */ - void calcMinMax(); + fun calcMinMax() /** * Calculates the min and max y-values from the Entry closest to the given fromX to the Entry closest to the given toX value. * This is only needed for the autoScaleMinMax feature. * */ - void calcMinMaxY(float fromX, float toX); + fun calcMinMaxY(fromX: Float, toX: Float) /** * Returns the first Entry object found at the given x-value with binary @@ -63,11 +59,9 @@ public interface IDataSet { * @param xValue the x-value * @param closestToY If there are multiple y-values for the specified x-value, * @param rounding determine whether to round up/down/closest - * if there is no Entry matching the provided x-value - * - * + * if there is no Entry matching the provided x-value */ - T getEntryForXValue(float xValue, float closestToY, DataSet.Rounding rounding); + fun getEntryForXValue(xValue: Float, closestToY: Float, rounding: Rounding?): T? /** * Returns the first Entry object found at the given x-value with binary @@ -81,7 +75,7 @@ public interface IDataSet { * @param xValue the x-value * @param closestToY If there are multiple y-values for the specified x-value, */ - T getEntryForXValue(float xValue, float closestToY); + fun getEntryForXValue(xValue: Float, closestToY: Float): T? /** * Returns all Entry objects found at the given x-value with binary @@ -90,12 +84,12 @@ public interface IDataSet { * not over-use in performance critical situations. * */ - List getEntriesForXValue(float xValue); + fun getEntriesForXValue(xValue: Float): MutableList /** * Returns the Entry object found at the given index (NOT xIndex) in the values array. */ - T getEntryForIndex(int index); + fun getEntryForIndex(index: Int): T /** * Returns the first Entry index found at the given x-value with binary @@ -108,16 +102,16 @@ public interface IDataSet { * @param xValue the x-value * @param closestToY If there are multiple y-values for the specified x-value, * @param rounding determine whether to round up/down/closest - * if there is no Entry matching the provided x-value + * if there is no Entry matching the provided x-value */ - int getEntryIndex(float xValue, float closestToY, DataSet.Rounding rounding); + fun getEntryIndex(xValue: Float, closestToY: Float, rounding: Rounding?): Int /** * Returns the position of the provided entry in the DataSets Entry array. * Returns -1 if doesn't exist. * */ - int getEntryIndex(T e); + fun getEntryIndex(e: Entry): Int /** @@ -127,7 +121,7 @@ public interface IDataSet { * situations. * */ - int getIndexInEntries(int xIndex); + fun getIndexInEntries(xIndex: Int): Int /** * Adds an Entry to the DataSet dynamically. @@ -136,7 +130,7 @@ public interface IDataSet { * values of the DataSet and the value-sum. * */ - boolean addEntry(T e); + fun addEntry(e: T): Boolean /** @@ -146,21 +140,21 @@ public interface IDataSet { * values of the DataSet and the value-sum. * */ - void addEntryOrdered(T e); + fun addEntryOrdered(e: T) /** * Removes the first Entry (at index 0) of this DataSet from the entries array. * Returns true if successful, false if not. * */ - boolean removeFirst(); + fun removeFirst(): Boolean /** * Removes the last Entry (at index size-1) of this DataSet from the entries array. * Returns true if successful, false if not. * */ - boolean removeLast(); + fun removeLast(): Boolean /** * Removes an Entry from the DataSets entries array. This will also @@ -169,21 +163,21 @@ public interface IDataSet { * be removed. * */ - boolean removeEntry(T e); + fun removeEntry(e: T?): Boolean /** * Removes the Entry object closest to the given x-value from the DataSet. * Returns true if an Entry was removed, false if no Entry could be removed. * */ - boolean removeEntryByXValue(float xValue); + fun removeEntryByXValue(xValue: Float): Boolean /** * Removes the Entry object at the given index in the values array from the DataSet. * Returns true if an Entry was removed, false if no Entry could be removed. * */ - boolean removeEntry(int index); + fun removeEntry(index: Int): Boolean /** * Checks if this DataSet contains the specified Entry. Returns true if so, @@ -191,70 +185,68 @@ public interface IDataSet { * over-use in performance critical situations. * */ - boolean contains(T entry); + fun contains(entry: T?): Boolean /** * Removes all values from this DataSet and does all necessary recalculations. */ - void clear(); + fun clear() /** * Returns the label string that describes the DataSet. * */ - String getLabel(); - /** * Sets the label string that describes the DataSet. * */ - void setLabel(String label); + var label: String /** * Returns the axis this DataSet should be plotted against. */ - YAxis.AxisDependency getAxisDependency(); - /** * Set the y-axis this DataSet should be plotted against (either LEFT or * RIGHT). Default: LEFT * */ - void setAxisDependency(YAxis.AxisDependency dependency); + var axisDependency: AxisDependency? /** * returns all the colors that are set for this DataSet * */ - List getColors(); + val colors: MutableList /** * Returns the first color (index 0) of the colors-array this DataSet * contains. This is only used for performance reasons when only one color is in the colors array (size == 1) * */ - int getColor(); + val color: Int /** * Returns the color at the given index of the DataSet's color array. * Performs a IndexOutOfBounds check by modulus. * */ - int getColor(int index); + fun getColor(index: Int): Int /** * returns true if highlighting of values is enabled, false if not * */ - boolean isHighlightEnabled(); - /** * If set to true, value highlighting is enabled which means that values can * be highlighted programmatically or by touch gesture. * */ - void setHighlightEnabled(boolean enabled); + var isHighlightEnabled: Boolean + /** + * Returns the formatter used for drawing the values inside the chart. + * + */ /** * Sets the formatter to be used for drawing the values inside the chart. If * no formatter is set, the chart will automatically determine a reasonable @@ -263,146 +255,113 @@ public interface IDataSet { * calculated by the chart. * */ - void setValueFormatter(IValueFormatter f); + var valueFormatter: IValueFormatter /** - * Returns the formatter used for drawing the values inside the chart. + * Sets a list of colors to be used as the colors for the drawn values. * */ - IValueFormatter getValueFormatter(); + fun setValueTextColors(colors: MutableList) /** - * Returns true if the valueFormatter object of this DataSet is null. + * Returns only the first color of all colors that are set to be used for the values. * */ - boolean needsFormatter(); - /** * Sets the color the value-labels of this DataSet should have. * */ - void setValueTextColor(int color); + var valueTextColor: Int /** - * Sets a list of colors to be used as the colors for the drawn values. + * Returns the color at the specified index that is used for drawing the values inside the chart. + * Uses modulus internally. * */ - void setValueTextColors(List colors); + fun getValueTextColor(index: Int): Int /** - * Sets a Typeface for the value-labels of this DataSet. + * Returns the typeface that is used for drawing the values inside the chart * */ - void setValueTypeface(Typeface tf); - /** - * Sets the text-size of the value-labels of this DataSet in dp. + * Sets a Typeface for the value-labels of this DataSet. * */ - void setValueTextSize(float size); + var valueTypeface: Typeface? /** - * Returns only the first color of all colors that are set to be used for the values. + * Returns the text size that is used for drawing the values inside the chart * */ - int getValueTextColor(); - /** - * Returns the color at the specified index that is used for drawing the values inside the chart. - * Uses modulus internally. + * Sets the text-size of the value-labels of this DataSet in dp. * */ - int getValueTextColor(int index); + var valueTextSize: Float /** - * Returns the typeface that is used for drawing the values inside the chart + * The form to draw for this dataset in the legend. * - */ - Typeface getValueTypeface(); - - /** - * Returns the text size that is used for drawing the values inside the chart * - */ - float getValueTextSize(); - - /** - * The form to draw for this dataset in the legend. - *

* Return `DEFAULT` to use the default legend form. */ - Legend.LegendForm getForm(); + val form: LegendForm? /** * The form size to draw for this dataset in the legend. - *

+ * + * * Return `Float.NaN` to use the default legend form size. */ - float getFormSize(); + val formSize: Float /** * The line width for drawing the form of this dataset in the legend - *

+ * + * * Return `Float.NaN` to use the default legend form line width. */ - float getFormLineWidth(); + val formLineWidth: Float /** * The line dash path effect used for shapes that consist of lines. - *

+ * + * * Return `null` to use the default legend form line dash effect. */ - DashPathEffect getFormLineDashEffect(); - - /** - * set this to true to draw y-values on the chart. - * NOTE (for bar and line charts): if `maxVisibleCount` is reached, no values will be drawn even - * if this is enabled - */ - void setDrawValues(boolean enabled); + val formLineDashEffect: DashPathEffect? /** * Returns true if y-value drawing is enabled, false if not * */ - boolean isDrawValuesEnabled(); + var isDrawValuesEnabled: Boolean /** - * Set this to true to draw y-icons on the chart. - * NOTE (for bar and line charts): if `maxVisibleCount` is reached, no icons will be drawn even - * if this is enabled + * Returns true if y-icon drawing is enabled, false if not * */ - void setDrawIcons(boolean enabled); + var isDrawIconsEnabled: Boolean /** - * Returns true if y-icon drawing is enabled, false if not - * + * Get the offset for drawing icons. */ - boolean isDrawIconsEnabled(); - /** * Offset of icons drawn on the chart. * For all charts except Pie and Radar it will be ordinary (x offset,y offset). * For Pie and Radar chart it will be (y offset, distance from center offset); so if you want icon to be rendered under value, you should increase X component of CGPoint, and if you want icon to be rendered closet to center, you should decrease height component of CGPoint. */ - void setIconsOffset(MPPointF offset); + var iconsOffset: MPPointF /** - * Get the offset for drawing icons. + * Returns true if this DataSet is visible inside the chart, or false if it + * is currently hidden. */ - MPPointF getIconsOffset(); - /** * Set the visibility of this DataSet. If not visible, the DataSet will not * be drawn to the chart upon refreshing it. * */ - void setVisible(boolean visible); - - /** - * Returns true if this DataSet is visible inside the chart, or false if it - * is currently hidden. - */ - boolean isVisible(); + var isVisible: Boolean } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineDataSet.kt similarity index 63% rename from MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineDataSet.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineDataSet.kt index 3f534fe848..3dd3e0ebd0 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineDataSet.kt @@ -1,22 +1,20 @@ -package com.github.mikephil.charting.interfaces.datasets; +package com.github.mikephil.charting.interfaces.datasets -import android.graphics.DashPathEffect; - -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.formatter.IFillFormatter; +import android.graphics.DashPathEffect +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.formatter.IFillFormatter /** * Created by Philpp Jahoda on 21/10/15. */ -public interface ILineDataSet extends ILineRadarDataSet { - +interface ILineDataSet : ILineRadarDataSet { /** * Returns the drawing mode for this line dataset * * @return */ - LineDataSet.Mode getMode(); + var mode: LineDataSet.Mode? /** * Returns the intensity of the cubic lines (the effect intensity). @@ -24,23 +22,23 @@ public interface ILineDataSet extends ILineRadarDataSet { * * @return */ - float getCubicIntensity(); + var cubicIntensity: Float - @Deprecated - boolean isDrawCubicEnabled(); + @get:Deprecated("") + val isDrawCubicEnabled: Boolean - @Deprecated - boolean isDrawSteppedEnabled(); + @get:Deprecated("") + val isDrawSteppedEnabled: Boolean /** * Returns the size of the drawn circles. */ - float getCircleRadius(); + var circleRadius: Float /** * Returns the hole radius of the drawn circles. */ - float getCircleHoleRadius(); + var circleHoleRadius: Float /** * Returns the color at the given index of the DataSet's circle-color array. @@ -49,42 +47,42 @@ public interface ILineDataSet extends ILineRadarDataSet { * @param index * @return */ - int getCircleColor(int index); + fun getCircleColor(index: Int): Int /** * Returns the number of colors in this DataSet's circle-color array. * * @return */ - int getCircleColorCount(); + val circleColorCount: Int /** * Returns true if drawing circles for this DataSet is enabled, false if not * * @return */ - boolean isDrawCirclesEnabled(); + var isDrawCirclesEnabled: Boolean /** * Returns the color of the inner circle (the circle-hole). * * @return */ - int getCircleHoleColor(); + var circleHoleColor: Int /** * Returns true if drawing the circle-holes is enabled, false if not. * * @return */ - boolean isDrawCircleHoleEnabled(); + var isDrawCircleHoleEnabled: Boolean /** * Returns the DashPathEffect that is used for drawing the lines. * * @return */ - DashPathEffect getDashPathEffect(); + var dashPathEffect: DashPathEffect? /** * Returns true if the dashed-line effect is enabled, false if not. @@ -92,12 +90,12 @@ public interface ILineDataSet extends ILineRadarDataSet { * * @return */ - boolean isDashedLineEnabled(); + val isDashedLineEnabled: Boolean /** * Returns the IFillFormatter that is set for this DataSet. * * @return */ - IFillFormatter getFillFormatter(); + var fillFormatter: IFillFormatter } \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineRadarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineRadarDataSet.java deleted file mode 100644 index ce89822716..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineRadarDataSet.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.github.mikephil.charting.interfaces.datasets; - -import android.graphics.drawable.Drawable; - -import com.github.mikephil.charting.data.Entry; - -/** - * Created by Philipp Jahoda on 21/10/15. - */ -public interface ILineRadarDataSet extends ILineScatterCandleRadarDataSet { - - /** - * Returns the color that is used for filling the line surface area. - * - * @return - */ - int getFillColor(); - - /** - * Returns the drawable used for filling the area below the line. - * - * @return - */ - Drawable getFillDrawable(); - - /** - * Returns the alpha value that is used for filling the line surface, - * default: 85 - * - * @return - */ - int getFillAlpha(); - - /** - * Returns the stroke-width of the drawn line - * - * @return - */ - float getLineWidth(); - - /** - * Returns true if filled drawing is enabled, false if not - * - * @return - */ - boolean isDrawFilledEnabled(); - - /** - * Set to true if the DataSet should be drawn filled (surface), and not just - * as a line, disabling this will give great performance boost. Please note that this method - * uses the canvas.clipPath(...) method for drawing the filled area. - * For devices with API level < 18 (Android 4.3), hardware acceleration of the chart should - * be turned off. Default: false - * - * @param enabled - */ - void setDrawFilled(boolean enabled); -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineRadarDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineRadarDataSet.kt new file mode 100644 index 0000000000..2ddb46e0b0 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/ILineRadarDataSet.kt @@ -0,0 +1,45 @@ +package com.github.mikephil.charting.interfaces.datasets + +import android.graphics.drawable.Drawable +import com.github.mikephil.charting.data.Entry + +/** + * Created by Philipp Jahoda on 21/10/15. + */ +interface ILineRadarDataSet : ILineScatterCandleRadarDataSet { + /** + * Returns the color that is used for filling the line surface area. + * + * @return + */ + var fillColor: Int + + /** + * Returns the drawable used for filling the area below the line. + * + * @return + */ + var fillDrawable: Drawable? + + /** + * Returns the alpha value that is used for filling the line surface, + * default: 85 + * + * @return + */ + var fillAlpha: Int + + /** + * Returns the stroke-width of the drawn line + * + * @return + */ + var lineWidth: Float + + /** + * Returns true if filled drawing is enabled, false if not + * + * @return + */ + var isDrawFilledEnabled: Boolean +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.kt similarity index 59% rename from MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.kt index a11246b56f..c86e654092 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IPieDataSet.kt @@ -1,74 +1,69 @@ -package com.github.mikephil.charting.interfaces.datasets; +package com.github.mikephil.charting.interfaces.datasets -import androidx.annotation.Nullable; - -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; - -public interface IPieDataSet extends IDataSet { +import com.github.mikephil.charting.data.PieDataSet.ValuePosition +import com.github.mikephil.charting.data.PieEntry +interface IPieDataSet : IDataSet { /** * Returns the space that is set to be between the piechart-slices of this * DataSet, in pixels. */ - float getSliceSpace(); + val sliceSpace: Float /** * When enabled, slice spacing will be 0.0 when the smallest value is going to be * smaller than the slice spacing itself. */ - boolean isAutomaticallyDisableSliceSpacingEnabled(); + val isAutomaticallyDisableSliceSpacingEnabled: Boolean /** * Returns the distance a highlighted piechart slice is "shifted" away from * the chart-center in dp. */ - float getSelectionShift(); + val selectionShift: Float - PieDataSet.ValuePosition getXValuePosition(); + val xValuePosition: ValuePosition? - PieDataSet.ValuePosition getYValuePosition(); + val yValuePosition: ValuePosition? /** * When valuePosition is OutsideSlice, indicates line color */ - int getValueLineColor(); + val valueLineColor: Int /** * When valuePosition is OutsideSlice and enabled, line will have the same color as the slice */ - boolean isUseValueColorForLineEnabled(); + val isUseValueColorForLineEnabled: Boolean /** * When valuePosition is OutsideSlice, indicates line width */ - float getValueLineWidth(); + val valueLineWidth: Float /** * When valuePosition is OutsideSlice, indicates offset as percentage out of the slice size */ - float getValueLinePart1OffsetPercentage(); + val valueLinePart1OffsetPercentage: Float /** * When valuePosition is OutsideSlice, indicates length of first half of the line */ - float getValueLinePart1Length(); + val valueLinePart1Length: Float /** * When valuePosition is OutsideSlice, indicates length of second half of the line */ - float getValueLinePart2Length(); + val valueLinePart2Length: Float /** * When valuePosition is OutsideSlice, this allows variable line length */ - boolean isValueLineVariableLength(); + val isValueLineVariableLength: Boolean /** * Gets the color for the highlighted sector */ - @Nullable - Integer getHighlightColor(); - + val highlightColor: Int? } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IRadarDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IRadarDataSet.java deleted file mode 100644 index 8af00d5376..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IRadarDataSet.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.github.mikephil.charting.interfaces.datasets; - -import com.github.mikephil.charting.data.RadarEntry; - -/** - * Created by Philipp Jahoda on 03/11/15. - */ -public interface IRadarDataSet extends ILineRadarDataSet { - - /// flag indicating whether highlight circle should be drawn or not - boolean isDrawHighlightCircleEnabled(); - - /// Sets whether highlight circle should be drawn or not - void setDrawHighlightCircleEnabled(boolean enabled); - - int getHighlightCircleFillColor(); - - /// The stroke color for highlight circle. - /// If Utils.COLOR_NONE, the color of the dataset is taken. - int getHighlightCircleStrokeColor(); - - int getHighlightCircleStrokeAlpha(); - - float getHighlightCircleInnerRadius(); - - float getHighlightCircleOuterRadius(); - - float getHighlightCircleStrokeWidth(); - -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IRadarDataSet.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IRadarDataSet.kt new file mode 100644 index 0000000000..12fa903f8b --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IRadarDataSet.kt @@ -0,0 +1,26 @@ +package com.github.mikephil.charting.interfaces.datasets + +import com.github.mikephil.charting.data.RadarEntry + +/** + * Created by Philipp Jahoda on 03/11/15. + */ +interface IRadarDataSet : ILineRadarDataSet { + /** flag indicating whether highlight circle should be drawn or not */ + /** Sets whether highlight circle should be drawn or not */ + var isDrawHighlightCircleEnabled: Boolean + + val highlightCircleFillColor: Int + + /** The stroke color for highlight circle. + * If Utils.COLOR_NONE, the color of the dataset is taken. */ + val highlightCircleStrokeColor: Int + + val highlightCircleStrokeAlpha: Int + + val highlightCircleInnerRadius: Float + + val highlightCircleOuterRadius: Float + + val highlightCircleStrokeWidth: Float +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IScatterDataSet.java b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IScatterDataSet.kt similarity index 59% rename from MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IScatterDataSet.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IScatterDataSet.kt index ac6122742a..842ba6ea09 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IScatterDataSet.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/interfaces/datasets/IScatterDataSet.kt @@ -1,38 +1,37 @@ -package com.github.mikephil.charting.interfaces.datasets; +package com.github.mikephil.charting.interfaces.datasets -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.renderer.scatter.IShapeRenderer; +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.renderer.scatter.IShapeRenderer /** * Created by philipp on 21/10/15. */ -public interface IScatterDataSet extends ILineScatterCandleRadarDataSet { - +interface IScatterDataSet : ILineScatterCandleRadarDataSet { /** * Returns the currently set scatter shape size * * @return */ - float getScatterShapeSize(); + val scatterShapeSize: Float /** * Returns radius of the hole in the shape * * @return */ - float getScatterShapeHoleRadius(); + val scatterShapeHoleRadius: Float /** * Returns the color for the hole in the shape * * @return */ - int getScatterShapeHoleColor(); + val scatterShapeHoleColor: Int /** * Returns the IShapeRenderer responsible for rendering this DataSet. * * @return */ - IShapeRenderer getShapeRenderer(); + val shapeRenderer: IShapeRenderer? } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedMoveViewJob.java b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedMoveViewJob.java deleted file mode 100644 index e6903cf854..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedMoveViewJob.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.github.mikephil.charting.jobs; - -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.view.View; - -import com.github.mikephil.charting.utils.ObjectPool; -import com.github.mikephil.charting.utils.Transformer; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by Philipp Jahoda on 19/02/16. - */ -@SuppressLint("NewApi") -public class AnimatedMoveViewJob extends AnimatedViewPortJob { - - private static ObjectPool pool; - - static { - pool = ObjectPool.create(4, new AnimatedMoveViewJob(null,0,0,null,null,0,0,0)); - pool.setReplenishPercentage(0.5f); - } - - public static AnimatedMoveViewJob getInstance(ViewPortHandler viewPortHandler, float xValue, float yValue, Transformer trans, View v, float xOrigin, float yOrigin, long duration){ - AnimatedMoveViewJob result = pool.get(); - result.mViewPortHandler = viewPortHandler; - result.xValue = xValue; - result.yValue = yValue; - result.mTrans = trans; - result.view = v; - result.xOrigin = xOrigin; - result.yOrigin = yOrigin; - //result.resetAnimator(); - result.animator.setDuration(duration); - return result; - } - - public static void recycleInstance(AnimatedMoveViewJob instance){ - // Clear reference avoid memory leak - instance.xValue = 0f; - instance.yValue = 0f; - instance.xOrigin = 0f; - instance.yOrigin = 0f; - instance.animator.setDuration(0); - instance.recycle(); - pool.recycle(instance); - } - - - public AnimatedMoveViewJob(ViewPortHandler viewPortHandler, float xValue, float yValue, Transformer trans, View v, float xOrigin, float yOrigin, long duration) { - super(viewPortHandler, xValue, yValue, trans, v, xOrigin, yOrigin, duration); - } - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - - pts[0] = xOrigin + (xValue - xOrigin) * phase; - pts[1] = yOrigin + (yValue - yOrigin) * phase; - - mTrans.pointValuesToPixel(pts); - mViewPortHandler.centerViewPort(pts, view); - } - - public void recycleSelf(){ - recycleInstance(this); - } - - @Override - protected ObjectPool.Poolable instantiate() { - return new AnimatedMoveViewJob(null,0,0,null,null,0,0,0); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedMoveViewJob.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedMoveViewJob.kt new file mode 100644 index 0000000000..74cdca8a0e --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedMoveViewJob.kt @@ -0,0 +1,81 @@ +package com.github.mikephil.charting.jobs + +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.view.View +import com.github.mikephil.charting.utils.ObjectPool +import com.github.mikephil.charting.utils.Transformer +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by Philipp Jahoda on 19/02/16. + */ +@SuppressLint("NewApi") +class AnimatedMoveViewJob( + viewPortHandler: ViewPortHandler, + xValue: Float, + yValue: Float, + trans: Transformer?, + v: View?, + xOrigin: Float, + yOrigin: Float, + duration: Long +) : AnimatedViewPortJob(viewPortHandler, xValue, yValue, trans, v, xOrigin, yOrigin, duration) { + override fun onAnimationUpdate(animation: ValueAnimator) { + pts[0] = xOrigin + (xValue - xOrigin) * phase + pts[1] = yOrigin + (yValue - yOrigin) * phase + + mTrans?.pointValuesToPixel(pts) + mViewPortHandler.centerViewPort(pts, view) + } + + override fun recycleSelf() { + recycleInstance(this) + } + + override fun instantiate(): AnimatedMoveViewJob { + return AnimatedMoveViewJob(ViewPortHandler(), 0f, 0f, null, null, 0f, 0f, 0) + } + + companion object { + private val pool = ObjectPool.Companion.create(4, AnimatedMoveViewJob(ViewPortHandler(), 0f, 0f, null, null, 0f, 0f, 0)) + + init { + pool.replenishPercentage = 0.5f + } + + fun getInstance( + viewPortHandler: ViewPortHandler, + xValue: Float, + yValue: Float, + trans: Transformer?, + v: View?, + xOrigin: Float, + yOrigin: Float, + duration: Long + ): AnimatedMoveViewJob { + val result: AnimatedMoveViewJob = pool.get() + result.mViewPortHandler = viewPortHandler + result.xValue = xValue + result.yValue = yValue + result.mTrans = trans + result.view = v + result.xOrigin = xOrigin + result.yOrigin = yOrigin + //result.resetAnimator(); + result.animator.setDuration(duration) + return result + } + + fun recycleInstance(instance: AnimatedMoveViewJob) { + // Clear reference avoid memory leak + instance.xValue = 0f + instance.yValue = 0f + instance.xOrigin = 0f + instance.yOrigin = 0f + instance.animator.setDuration(0) + instance.recycle() + pool.recycle(instance) + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedViewPortJob.java b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedViewPortJob.java deleted file mode 100644 index f8b520a419..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedViewPortJob.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.github.mikephil.charting.jobs; - -import android.animation.Animator; -import android.animation.ObjectAnimator; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.view.View; - -import com.github.mikephil.charting.utils.Transformer; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by Philipp Jahoda on 19/02/16. - */ -@SuppressLint("NewApi") -public abstract class AnimatedViewPortJob extends ViewPortJob implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { - - protected ObjectAnimator animator; - - protected float phase; - - protected float xOrigin; - protected float yOrigin; - - public AnimatedViewPortJob(ViewPortHandler viewPortHandler, float xValue, float yValue, Transformer trans, View v, float xOrigin, float yOrigin, long duration) { - super(viewPortHandler, xValue, yValue, trans, v); - this.xOrigin = xOrigin; - this.yOrigin = yOrigin; - animator = ObjectAnimator.ofFloat(this, "phase", 0f, 1f); - animator.setDuration(duration); - animator.addUpdateListener(this); - animator.addListener(this); - } - - @SuppressLint("NewApi") - @Override - public void run() { - animator.start(); - } - - public float getPhase() { - return phase; - } - - public void setPhase(float phase) { - this.phase = phase; - } - - public float getXOrigin() { - return xOrigin; - } - - public float getYOrigin() { - return yOrigin; - } - - public abstract void recycleSelf(); - - protected void resetAnimator(){ - animator.removeAllListeners(); - animator.removeAllUpdateListeners(); - animator.reverse(); - animator.addUpdateListener(this); - animator.addListener(this); - } - - @Override - public void onAnimationStart(Animator animation) { - - } - - @Override - public void onAnimationEnd(Animator animation) { - try{ - recycleSelf(); - }catch (IllegalArgumentException e){ - // don't worry about it. - } - } - - @Override - public void onAnimationCancel(Animator animation) { - try{ - recycleSelf(); - }catch (IllegalArgumentException e){ - // don't worry about it. - } - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - - @Override - public void onAnimationUpdate(ValueAnimator animation) { - - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedViewPortJob.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedViewPortJob.kt new file mode 100644 index 0000000000..d89940c59f --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedViewPortJob.kt @@ -0,0 +1,75 @@ +package com.github.mikephil.charting.jobs + +import android.animation.Animator +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.animation.ValueAnimator.AnimatorUpdateListener +import android.annotation.SuppressLint +import android.view.View +import com.github.mikephil.charting.utils.Transformer +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by Philipp Jahoda on 19/02/16. + */ +@SuppressLint("NewApi") +abstract class AnimatedViewPortJob>( + viewPortHandler: ViewPortHandler, + xValue: Float, + yValue: Float, + trans: Transformer?, + v: View?, + var xOrigin: Float, + var yOrigin: Float, + duration: Long +) : ViewPortJob(viewPortHandler, xValue, yValue, trans, v), AnimatorUpdateListener, Animator.AnimatorListener { + protected val animator: ObjectAnimator = ObjectAnimator.ofFloat(this, "phase", 0f, 1f) + + var phase: Float = 0f + + init { + animator.setDuration(duration) + animator.addUpdateListener(this) + animator.addListener(this) + } + + @SuppressLint("NewApi") + override fun run() { + animator.start() + } + + abstract fun recycleSelf() + + protected fun resetAnimator() { + animator.removeAllListeners() + animator.removeAllUpdateListeners() + animator.reverse() + animator.addUpdateListener(this) + animator.addListener(this) + } + + override fun onAnimationStart(animation: Animator) { + } + + override fun onAnimationEnd(animation: Animator) { + try { + recycleSelf() + } catch (_: IllegalArgumentException) { + // don't worry about it. + } + } + + override fun onAnimationCancel(animation: Animator) { + try { + recycleSelf() + } catch (_: IllegalArgumentException) { + // don't worry about it. + } + } + + override fun onAnimationRepeat(animation: Animator) { + } + + override fun onAnimationUpdate(animation: ValueAnimator) { + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedZoomJob.java b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedZoomJob.java deleted file mode 100644 index d442a2ceeb..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedZoomJob.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.github.mikephil.charting.jobs; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.annotation.SuppressLint; -import android.graphics.Matrix; -import android.view.View; - -import com.github.mikephil.charting.charts.BarLineChartBase; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.utils.ObjectPool; -import com.github.mikephil.charting.utils.Transformer; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by Philipp Jahoda on 19/02/16. - */ -@SuppressLint("NewApi") -public class AnimatedZoomJob extends AnimatedViewPortJob implements Animator.AnimatorListener { - - private static ObjectPool pool; - - static { - pool = ObjectPool.create(8, new AnimatedZoomJob(null,null,null,null,0,0,0,0,0,0,0,0,0,0)); - } - - public static AnimatedZoomJob getInstance(ViewPortHandler viewPortHandler, View v, Transformer trans, YAxis axis, float xAxisRange, float scaleX, float scaleY, float xOrigin, float yOrigin, float zoomCenterX, float zoomCenterY, float zoomOriginX, float zoomOriginY, long duration) { - AnimatedZoomJob result = pool.get(); - result.mViewPortHandler = viewPortHandler; - result.xValue = scaleX; - result.yValue = scaleY; - result.mTrans = trans; - result.view = v; - result.xOrigin = xOrigin; - result.yOrigin = yOrigin; - result.yAxis = axis; - result.xAxisRange = xAxisRange; - result.zoomCenterX = zoomCenterX; - result.zoomCenterY = zoomCenterY; - result.zoomOriginX = zoomOriginX; - result.zoomOriginY = zoomOriginY; - result.resetAnimator(); - result.animator.setDuration(duration); - return result; - } - - protected float zoomOriginX; - protected float zoomOriginY; - - protected float zoomCenterX; - protected float zoomCenterY; - - protected YAxis yAxis; - - protected float xAxisRange; - - @SuppressLint("NewApi") - public AnimatedZoomJob(ViewPortHandler viewPortHandler, View v, Transformer trans, YAxis axis, float xAxisRange, float scaleX, float scaleY, float xOrigin, float yOrigin, float zoomCenterX, float zoomCenterY, float zoomOriginX, float zoomOriginY, long duration) { - super(viewPortHandler, scaleX, scaleY, trans, v, xOrigin, yOrigin, duration); - - this.zoomCenterX = zoomCenterX; - this.zoomCenterY = zoomCenterY; - this.zoomOriginX = zoomOriginX; - this.zoomOriginY = zoomOriginY; - this.animator.addListener(this); - this.yAxis = axis; - this.xAxisRange = xAxisRange; - } - - protected Matrix mOnAnimationUpdateMatrixBuffer = new Matrix(); - @Override - public void onAnimationUpdate(ValueAnimator animation) { - - float scaleX = xOrigin + (xValue - xOrigin) * phase; - float scaleY = yOrigin + (yValue - yOrigin) * phase; - - Matrix save = mOnAnimationUpdateMatrixBuffer; - mViewPortHandler.setZoom(scaleX, scaleY, save); - mViewPortHandler.refresh(save, view, false); - - float valsInView = yAxis.mAxisRange / mViewPortHandler.getScaleY(); - float xsInView = xAxisRange / mViewPortHandler.getScaleX(); - - pts[0] = zoomOriginX + ((zoomCenterX - xsInView / 2f) - zoomOriginX) * phase; - pts[1] = zoomOriginY + ((zoomCenterY + valsInView / 2f) - zoomOriginY) * phase; - - mTrans.pointValuesToPixel(pts); - - mViewPortHandler.translate(pts, save); - mViewPortHandler.refresh(save, view, true); - } - - @Override - public void onAnimationEnd(Animator animation) { - ((BarLineChartBase) view).calculateOffsets(); - view.postInvalidate(); - } - - @Override - public void onAnimationCancel(Animator animation) { - - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - - @Override - public void recycleSelf() { - - } - - @Override - public void onAnimationStart(Animator animation) { - - } - - @Override - protected ObjectPool.Poolable instantiate() { - return new AnimatedZoomJob(null,null,null,null,0,0,0,0,0,0,0,0,0,0); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedZoomJob.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedZoomJob.kt new file mode 100644 index 0000000000..9e518a923f --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/AnimatedZoomJob.kt @@ -0,0 +1,125 @@ +package com.github.mikephil.charting.jobs + +import android.animation.Animator +import android.animation.ValueAnimator +import android.annotation.SuppressLint +import android.graphics.Matrix +import android.view.View +import com.github.mikephil.charting.charts.BarLineChartBase +import com.github.mikephil.charting.components.YAxis +import com.github.mikephil.charting.utils.ObjectPool +import com.github.mikephil.charting.utils.Transformer +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by Philipp Jahoda on 19/02/16. + */ +@SuppressLint("NewApi") +open class AnimatedZoomJob @SuppressLint("NewApi") constructor( + viewPortHandler: ViewPortHandler, + v: View?, + trans: Transformer?, + axis: YAxis, + xAxisRange: Float, + scaleX: Float, + scaleY: Float, + xOrigin: Float, + yOrigin: Float, + protected var zoomCenterX: Float, + protected var zoomCenterY: Float, + protected var zoomOriginX: Float, + protected var zoomOriginY: Float, + duration: Long +) : AnimatedViewPortJob(viewPortHandler, scaleX, scaleY, trans, v, xOrigin, yOrigin, duration), Animator.AnimatorListener { + protected var yAxis: YAxis + + protected var xAxisRange: Float + + protected var mOnAnimationUpdateMatrixBuffer: Matrix = Matrix() + + init { + this.animator.addListener(this) + this.yAxis = axis + this.xAxisRange = xAxisRange + } + + override fun onAnimationUpdate(animation: ValueAnimator) { + val scaleX = xOrigin + (xValue - xOrigin) * phase + val scaleY = yOrigin + (yValue - yOrigin) * phase + + val save = mOnAnimationUpdateMatrixBuffer + mViewPortHandler.setZoom(scaleX, scaleY, save) + mViewPortHandler.refresh(save, view, false) + + val valsInView = yAxis.mAxisRange / mViewPortHandler.scaleY + val xsInView = xAxisRange / mViewPortHandler.scaleX + + pts[0] = zoomOriginX + ((zoomCenterX - xsInView / 2f) - zoomOriginX) * phase + pts[1] = zoomOriginY + ((zoomCenterY + valsInView / 2f) - zoomOriginY) * phase + + mTrans?.pointValuesToPixel(pts) + + mViewPortHandler.translate(pts, save) + mViewPortHandler.refresh(save, view, true) + } + + override fun onAnimationEnd(animation: Animator) { + (view as? BarLineChartBase<*, *, *>)?.calculateOffsets() + view?.postInvalidate() + } + + override fun onAnimationCancel(animation: Animator) { + } + + override fun onAnimationRepeat(animation: Animator) { + } + + override fun recycleSelf() { + } + + override fun onAnimationStart(animation: Animator) { + } + + override fun instantiate(): AnimatedZoomJob { + return AnimatedZoomJob(ViewPortHandler(), null, null, YAxis(), 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0) + } + + companion object { + private val pool = ObjectPool.Companion.create(8, AnimatedZoomJob(ViewPortHandler(), null, null, YAxis(), 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 0)) + + fun getInstance( + viewPortHandler: ViewPortHandler, + v: View?, + trans: Transformer, + axis: YAxis, + xAxisRange: Float, + scaleX: Float, + scaleY: Float, + xOrigin: Float, + yOrigin: Float, + zoomCenterX: Float, + zoomCenterY: Float, + zoomOriginX: Float, + zoomOriginY: Float, + duration: Long + ): AnimatedZoomJob { + val result: AnimatedZoomJob = pool.get() + result.mViewPortHandler = viewPortHandler + result.xValue = scaleX + result.yValue = scaleY + result.mTrans = trans + result.view = v + result.xOrigin = xOrigin + result.yOrigin = yOrigin + result.yAxis = axis + result.xAxisRange = xAxisRange + result.zoomCenterX = zoomCenterX + result.zoomCenterY = zoomCenterY + result.zoomOriginX = zoomOriginX + result.zoomOriginY = zoomOriginY + result.resetAnimator() + result.animator.setDuration(duration) + return result + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/MoveViewJob.java b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/MoveViewJob.java deleted file mode 100644 index e24d746f4a..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/MoveViewJob.java +++ /dev/null @@ -1,60 +0,0 @@ - -package com.github.mikephil.charting.jobs; - -import android.view.View; - -import com.github.mikephil.charting.utils.ObjectPool; -import com.github.mikephil.charting.utils.Transformer; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by Philipp Jahoda on 19/02/16. - */ -public class MoveViewJob extends ViewPortJob { - - private static ObjectPool pool; - - static { - pool = ObjectPool.create(2, new MoveViewJob(null,0,0,null,null)); - pool.setReplenishPercentage(0.5f); - } - - public static MoveViewJob getInstance(ViewPortHandler viewPortHandler, float xValue, float yValue, Transformer trans, View v){ - MoveViewJob result = pool.get(); - result.mViewPortHandler = viewPortHandler; - result.xValue = xValue; - result.yValue = yValue; - result.mTrans = trans; - result.view = v; - return result; - } - - public static void recycleInstance(MoveViewJob instance){ - instance.recycle(); - // Clear reference avoid memory leak - instance.xValue = 0f; - instance.yValue = 0f; - pool.recycle(instance); - } - - public MoveViewJob(ViewPortHandler viewPortHandler, float xValue, float yValue, Transformer trans, View v) { - super(viewPortHandler, xValue, yValue, trans, v); - } - - @Override - public void run() { - - pts[0] = xValue; - pts[1] = yValue; - - mTrans.pointValuesToPixel(pts); - mViewPortHandler.centerViewPort(pts, view); - - this.recycleInstance(this); - } - - @Override - protected ObjectPool.Poolable instantiate() { - return new MoveViewJob(mViewPortHandler, xValue, yValue, mTrans, view); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/MoveViewJob.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/MoveViewJob.kt new file mode 100644 index 0000000000..8bbcdf75fa --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/MoveViewJob.kt @@ -0,0 +1,52 @@ +package com.github.mikephil.charting.jobs + +import android.view.View +import com.github.mikephil.charting.utils.ObjectPool +import com.github.mikephil.charting.utils.Transformer +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by Philipp Jahoda on 19/02/16. + */ +open class MoveViewJob(viewPortHandler: ViewPortHandler, xValue: Float, yValue: Float, trans: Transformer?, v: View?) : + ViewPortJob(viewPortHandler, xValue, yValue, trans, v) { + override fun run() { + pts[0] = xValue + pts[1] = yValue + + mTrans?.pointValuesToPixel(pts) + mViewPortHandler.centerViewPort(pts, view) + + recycleInstance(this) + } + + override fun instantiate(): MoveViewJob { + return MoveViewJob(mViewPortHandler, xValue, yValue, mTrans, view) + } + + companion object { + private val pool = ObjectPool.Companion.create(2, MoveViewJob(ViewPortHandler(), 0f, 0f, null, null)) + + init { + pool.replenishPercentage = 0.5f + } + + fun getInstance(viewPortHandler: ViewPortHandler, xValue: Float, yValue: Float, trans: Transformer?, v: View?): MoveViewJob { + val result: MoveViewJob = pool.get() + result.mViewPortHandler = viewPortHandler + result.xValue = xValue + result.yValue = yValue + result.mTrans = trans + result.view = v + return result + } + + fun recycleInstance(instance: MoveViewJob) { + instance.recycle() + // Clear reference avoid memory leak + instance.xValue = 0f + instance.yValue = 0f + pool.recycle(instance) + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ViewPortJob.java b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ViewPortJob.java deleted file mode 100644 index a1df37425b..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ViewPortJob.java +++ /dev/null @@ -1,53 +0,0 @@ - -package com.github.mikephil.charting.jobs; - -import android.view.View; - -import com.github.mikephil.charting.utils.ObjectPool; -import com.github.mikephil.charting.utils.Transformer; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Runnable that is used for viewport modifications since they cannot be - * executed at any time. This can be used to delay the execution of viewport - * modifications until the onSizeChanged(...) method of the chart-view is called. - * This is especially important if viewport modifying methods are called on the chart - * directly after initialization. - * - * @author Philipp Jahoda - */ -public abstract class ViewPortJob extends ObjectPool.Poolable implements Runnable { - - protected float[] pts = new float[2]; - - protected ViewPortHandler mViewPortHandler; - protected float xValue = 0f; - protected float yValue = 0f; - protected Transformer mTrans; - protected View view; - - public ViewPortJob(ViewPortHandler viewPortHandler, float xValue, float yValue, - Transformer trans, View v) { - - this.mViewPortHandler = viewPortHandler; - this.xValue = xValue; - this.yValue = yValue; - this.mTrans = trans; - this.view = v; - - } - - public float getXValue() { - return xValue; - } - - public float getYValue() { - return yValue; - } - - protected void recycle() { - mViewPortHandler = null; - mTrans = null; - view = null; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ViewPortJob.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ViewPortJob.kt new file mode 100644 index 0000000000..8c75822a25 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ViewPortJob.kt @@ -0,0 +1,40 @@ +package com.github.mikephil.charting.jobs + +import android.view.View +import com.github.mikephil.charting.utils.ObjectPool.Poolable +import com.github.mikephil.charting.utils.Transformer +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Runnable that is used for viewport modifications since they cannot be + * executed at any time. This can be used to delay the execution of viewport + * modifications until the onSizeChanged(...) method of the chart-view is called. + * This is especially important if viewport modifying methods are called on the chart + * directly after initialization. + * + * @author Philipp Jahoda + */ +abstract class ViewPortJob>( + protected var mViewPortHandler: ViewPortHandler, xValue: Float, yValue: Float, + trans: Transformer?, v: View? +) : Poolable(), Runnable { + protected var pts: FloatArray = FloatArray(2) + + var xValue: Float = 0f + protected set + var yValue: Float = 0f + protected set + protected var mTrans: Transformer? + protected var view: View? + + init { + this.xValue = xValue + this.yValue = yValue + this.mTrans = trans + this.view = v + } + + protected fun recycle() { + view = null + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ZoomJob.java b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ZoomJob.java deleted file mode 100644 index 8c4c74df96..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ZoomJob.java +++ /dev/null @@ -1,94 +0,0 @@ - -package com.github.mikephil.charting.jobs; - -import android.graphics.Matrix; -import android.view.View; - -import com.github.mikephil.charting.charts.BarLineChartBase; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.utils.ObjectPool; -import com.github.mikephil.charting.utils.Transformer; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by Philipp Jahoda on 19/02/16. - */ -public class ZoomJob extends ViewPortJob { - - private static ObjectPool pool; - - static { - pool = ObjectPool.create(1, new ZoomJob(null, 0, 0, 0, 0, null, null, null)); - pool.setReplenishPercentage(0.5f); - } - - public static ZoomJob getInstance(ViewPortHandler viewPortHandler, float scaleX, float scaleY, float xValue, float yValue, - Transformer trans, YAxis.AxisDependency axis, View v) { - ZoomJob result = pool.get(); - result.xValue = xValue; - result.yValue = yValue; - result.scaleX = scaleX; - result.scaleY = scaleY; - result.mViewPortHandler = viewPortHandler; - result.mTrans = trans; - result.axisDependency = axis; - result.view = v; - return result; - } - - public static void recycleInstance(ZoomJob instance) { - // Clear reference avoid memory leak - instance.xValue = 0f; - instance.yValue = 0f; - instance.scaleX = 0f; - instance.scaleY = 0f; - instance.axisDependency = null; - instance.recycle(); - pool.recycle(instance); - } - - protected float scaleX; - protected float scaleY; - - protected YAxis.AxisDependency axisDependency; - - public ZoomJob(ViewPortHandler viewPortHandler, float scaleX, float scaleY, float xValue, float yValue, Transformer trans, - YAxis.AxisDependency axis, View v) { - super(viewPortHandler, xValue, yValue, trans, v); - - this.scaleX = scaleX; - this.scaleY = scaleY; - this.axisDependency = axis; - } - - protected Matrix mRunMatrixBuffer = new Matrix(); - - @Override - public void run() { - - Matrix save = mRunMatrixBuffer; - mViewPortHandler.zoom(scaleX, scaleY, save); - mViewPortHandler.refresh(save, view, false); - - float yValsInView = ((BarLineChartBase) view).getAxis(axisDependency).mAxisRange / mViewPortHandler.getScaleY(); - float xValsInView = ((BarLineChartBase) view).getXAxis().mAxisRange / mViewPortHandler.getScaleX(); - - pts[0] = xValue - xValsInView / 2f; - pts[1] = yValue + yValsInView / 2f; - - mTrans.pointValuesToPixel(pts); - - mViewPortHandler.translate(pts, save); - mViewPortHandler.refresh(save, view, false); - - ((BarLineChartBase) view).calculateOffsets(); - view.postInvalidate(); - - recycleInstance(this); - } - - @Override - protected ObjectPool.Poolable instantiate() { - return new ZoomJob(null, 0, 0, 0, 0, null, null, null); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ZoomJob.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ZoomJob.kt new file mode 100644 index 0000000000..75bc0126be --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/jobs/ZoomJob.kt @@ -0,0 +1,82 @@ +package com.github.mikephil.charting.jobs + +import android.graphics.Matrix +import android.view.View +import com.github.mikephil.charting.charts.BarLineChartBase +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.utils.ObjectPool +import com.github.mikephil.charting.utils.Transformer +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by Philipp Jahoda on 19/02/16. + */ +open class ZoomJob( + viewPortHandler: ViewPortHandler, protected var scaleX: Float, protected var scaleY: Float, xValue: Float, yValue: Float, trans: Transformer?, + protected var axisDependency: AxisDependency?, v: View? +) : ViewPortJob(viewPortHandler, xValue, yValue, trans, v) { + protected var mRunMatrixBuffer: Matrix = Matrix() + + override fun run() { + val save = mRunMatrixBuffer + mViewPortHandler.zoom(scaleX, scaleY, save) + mViewPortHandler.refresh(save, view, false) + + (view as? BarLineChartBase<*, *, *>)?.let { view -> + val yValsInView = view.getAxis(axisDependency).mAxisRange / mViewPortHandler.scaleY + val xValsInView = view.xAxis.mAxisRange / mViewPortHandler.scaleX + + pts[0] = xValue - xValsInView / 2f + pts[1] = yValue + yValsInView / 2f + + mTrans?.pointValuesToPixel(pts) + + mViewPortHandler.translate(pts, save) + mViewPortHandler.refresh(save, view, false) + + view.calculateOffsets() + view.postInvalidate() + } + + recycleInstance(this) + } + + override fun instantiate(): ZoomJob { + return ZoomJob(ViewPortHandler(), 0f, 0f, 0f, 0f, null, null, null) + } + + companion object { + private val pool = ObjectPool.Companion.create(1, ZoomJob(ViewPortHandler(), 0f, 0f, 0f, 0f, null, null, null)) + + init { + pool.replenishPercentage = 0.5f + } + + fun getInstance( + viewPortHandler: ViewPortHandler, scaleX: Float, scaleY: Float, xValue: Float, yValue: Float, + trans: Transformer?, axis: AxisDependency?, v: View? + ): ZoomJob { + val result: ZoomJob = pool.get() + result.xValue = xValue + result.yValue = yValue + result.scaleX = scaleX + result.scaleY = scaleY + result.mViewPortHandler = viewPortHandler + result.mTrans = trans + result.axisDependency = axis + result.view = v + return result + } + + fun recycleInstance(instance: ZoomJob) { + // Clear reference avoid memory leak + instance.xValue = 0f + instance.yValue = 0f + instance.scaleX = 0f + instance.scaleY = 0f + instance.axisDependency = null + instance.recycle() + pool.recycle(instance) + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/BarLineChartTouchListener.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/BarLineChartTouchListener.kt index c18906be97..16658b7589 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/BarLineChartTouchListener.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/BarLineChartTouchListener.kt @@ -9,9 +9,6 @@ import android.view.View import android.view.animation.AnimationUtils import com.github.mikephil.charting.charts.BarLineChartBase import com.github.mikephil.charting.charts.HorizontalBarChart -import com.github.mikephil.charting.data.BarLineScatterCandleBubbleData -import com.github.mikephil.charting.data.Entry -import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet import com.github.mikephil.charting.interfaces.datasets.IDataSet import com.github.mikephil.charting.utils.MPPointF import com.github.mikephil.charting.utils.Utils @@ -24,11 +21,10 @@ import kotlin.math.sqrt */ @Suppress("MemberVisibilityCanBePrivate") class BarLineChartTouchListener( - chart: BarLineChartBase?>?>, + chart: BarLineChartBase<*, *, *>, touchMatrix: Matrix, dragTriggerDistance: Float -) : - ChartTouchListener?>?>?>(chart) { +) : ChartTouchListener>(chart) { /** * returns the matrix object the listener holds * @@ -105,20 +101,18 @@ class BarLineChartTouchListener( if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain() } - mVelocityTracker!!.addMovement(event) + mVelocityTracker?.addMovement(event) if (event.actionMasked == MotionEvent.ACTION_CANCEL) { - if (mVelocityTracker != null) { - mVelocityTracker!!.recycle() - mVelocityTracker = null - } + mVelocityTracker?.recycle() + mVelocityTracker = null } - if (mTouchMode == NONE || mChart!!.isFlingEnabled) { - mGestureDetector.onTouchEvent(event) + if (touchMode == NONE || mChart.isFlingEnabled) { + mGestureDetector?.onTouchEvent(event) } - if (!mChart!!.isDragEnabled && (!mChart!!.isScaleXEnabled && !mChart!!.isScaleYEnabled)) return true + if (!mChart.isDragEnabled && (!mChart.isScaleXEnabled && !mChart.isScaleYEnabled)) return true // Handle touch events here... when (event.action and MotionEvent.ACTION_MASK) { @@ -131,7 +125,7 @@ class BarLineChartTouchListener( } MotionEvent.ACTION_POINTER_DOWN -> if (event.pointerCount >= 2) { - mChart!!.disableScroll() + mChart.disableScroll() saveTouchStart(event) @@ -145,11 +139,11 @@ class BarLineChartTouchListener( savedDist = spacing(event) if (savedDist > 10f) { - mTouchMode = if (mChart!!.isPinchZoomEnabled) { + touchMode = if (mChart.isPinchZoomEnabled) { PINCH_ZOOM } else { - if (mChart!!.isScaleXEnabled != mChart!!.isScaleYEnabled) { - if (mChart!!.isScaleXEnabled) X_ZOOM else Y_ZOOM + if (mChart.isScaleXEnabled != mChart.isScaleYEnabled) { + if (mChart.isScaleXEnabled) X_ZOOM else Y_ZOOM } else { if (savedXDist > savedYDist) X_ZOOM else Y_ZOOM } @@ -160,18 +154,18 @@ class BarLineChartTouchListener( midPoint(touchPointCenter, event) } - MotionEvent.ACTION_MOVE -> if (mTouchMode == DRAG) { - mChart!!.disableScroll() + MotionEvent.ACTION_MOVE -> if (touchMode == DRAG) { + mChart.disableScroll() - val x = if (mChart!!.isDragXEnabled) event.x - touchStartPoint.x else 0f - val y = if (mChart!!.isDragYEnabled) event.y - touchStartPoint.y else 0f + val x = if (mChart.isDragXEnabled) event.x - touchStartPoint.x else 0f + val y = if (mChart.isDragYEnabled) event.y - touchStartPoint.y else 0f performDrag(event, x, y) - } else if (mTouchMode == X_ZOOM || mTouchMode == Y_ZOOM || mTouchMode == PINCH_ZOOM) { - mChart!!.disableScroll() + } else if (touchMode == X_ZOOM || touchMode == Y_ZOOM || touchMode == PINCH_ZOOM) { + mChart.disableScroll() - if (mChart!!.isScaleXEnabled || mChart!!.isScaleYEnabled) performZoom(event) - } else if (mTouchMode == NONE + if (mChart.isScaleXEnabled || mChart.isScaleYEnabled) performZoom(event) + } else if (touchMode == NONE && abs( distance( event.x, touchStartPoint.x, event.y, @@ -179,26 +173,26 @@ class BarLineChartTouchListener( ).toDouble() ) > dragTriggerDist ) { - if (mChart!!.isDragEnabled) { - val shouldPan = !mChart!!.isFullyZoomedOut || - !mChart!!.hasNoDragOffset() + if (mChart.isDragEnabled) { + val shouldPan = !mChart.isFullyZoomedOut || + !mChart.hasNoDragOffset() if (shouldPan) { val distanceX = abs((event.x - touchStartPoint.x).toDouble()).toFloat() val distanceY = abs((event.y - touchStartPoint.y).toDouble()).toFloat() // Disable dragging in a direction that's disallowed - if ((mChart!!.isDragXEnabled || distanceY >= distanceX) && - (mChart!!.isDragYEnabled || distanceY <= distanceX) + if ((mChart.isDragXEnabled || distanceY >= distanceX) && + (mChart.isDragYEnabled || distanceY <= distanceX) ) { - mLastGesture = ChartGesture.DRAG - mTouchMode = DRAG + lastGesture = ChartGesture.DRAG + touchMode = DRAG } } else { - if (mChart!!.isHighlightPerDragEnabled) { - mLastGesture = ChartGesture.DRAG + if (mChart.isHighlightPerDragEnabled) { + lastGesture = ChartGesture.DRAG - if (mChart!!.isHighlightPerDragEnabled) performHighlightDrag(event) + if (mChart.isHighlightPerDragEnabled) performHighlightDrag(event) } } } @@ -207,14 +201,14 @@ class BarLineChartTouchListener( MotionEvent.ACTION_UP -> { val velocityTracker = mVelocityTracker val pointerId = event.getPointerId(0) - velocityTracker!!.computeCurrentVelocity(1000, Utils.getMaximumFlingVelocity().toFloat()) - val velocityY = velocityTracker.getYVelocity(pointerId) - val velocityX = velocityTracker.getXVelocity(pointerId) + velocityTracker?.computeCurrentVelocity(1000, Utils.maximumFlingVelocity.toFloat()) + val velocityY = velocityTracker?.getYVelocity(pointerId) ?: 0f + val velocityX = velocityTracker?.getXVelocity(pointerId) ?: 0f - if (abs(velocityX.toDouble()) > Utils.getMinimumFlingVelocity() || - abs(velocityY.toDouble()) > Utils.getMinimumFlingVelocity() + if (abs(velocityX.toDouble()) > Utils.minimumFlingVelocity || + abs(velocityY.toDouble()) > Utils.minimumFlingVelocity ) { - if (mTouchMode == DRAG && mChart!!.isDragDecelerationEnabled) { + if (touchMode == DRAG && mChart.isDragDecelerationEnabled) { stopDeceleration() mDecelerationLastTime = AnimationUtils.currentAnimationTimeMillis() @@ -230,17 +224,17 @@ class BarLineChartTouchListener( } } - if (mTouchMode == X_ZOOM || mTouchMode == Y_ZOOM || mTouchMode == PINCH_ZOOM || mTouchMode == POST_ZOOM) { + if (touchMode == X_ZOOM || touchMode == Y_ZOOM || touchMode == PINCH_ZOOM || touchMode == POST_ZOOM) { // Range might have changed, which means that Y-axis labels // could have changed in size, affecting Y-axis size. // So we need to recalculate offsets. - mChart!!.calculateOffsets() - mChart!!.postInvalidate() + mChart.calculateOffsets() + mChart.postInvalidate() } - mTouchMode = NONE - mChart!!.enableScroll() + touchMode = NONE + mChart.enableScroll() mVelocityTracker?.recycle() mVelocityTracker = null @@ -249,19 +243,19 @@ class BarLineChartTouchListener( } MotionEvent.ACTION_POINTER_UP -> { - Utils.velocityTrackerPointerUpCleanUpIfNecessary(event, mVelocityTracker) + mVelocityTracker?.let { Utils.velocityTrackerPointerUpCleanUpIfNecessary(event, it) } - mTouchMode = POST_ZOOM + touchMode = POST_ZOOM } MotionEvent.ACTION_CANCEL -> { - mTouchMode = NONE + touchMode = NONE endAction(event) } } // perform the transformation, update the chart - matrix = mChart!!.viewPortHandler.refresh(matrix, mChart!!, true) + matrix = mChart.viewPortHandler.refresh(matrix, mChart, true) return true // indicate event was handled } @@ -277,7 +271,7 @@ class BarLineChartTouchListener( touchStartPoint.x = event.x touchStartPoint.y = event.y - mClosestDataSetToTouch = mChart!!.getDataSetByTouchPoint(event.x, event.y) + mClosestDataSetToTouch = mChart.getDataSetByTouchPoint(event.x, event.y) } /** @@ -288,11 +282,11 @@ class BarLineChartTouchListener( private fun performDrag(event: MotionEvent, distanceX: Float, distanceY: Float) { var distanceXLocal = distanceX var distanceYLocal = distanceY - mLastGesture = ChartGesture.DRAG + lastGesture = ChartGesture.DRAG matrix.set(savedMatrix) - val l = mChart!!.onChartGestureListener + val l = mChart.onChartGestureListener // check if axis is inverted if (inverted()) { @@ -318,7 +312,7 @@ class BarLineChartTouchListener( private fun performZoom(event: MotionEvent) { if (event.pointerCount >= 2) { // two finger zoom - val l = mChart!!.onChartGestureListener + val l = mChart.onChartGestureListener // get the distance between the pointers of the touch event val totalDist = spacing(event) @@ -327,11 +321,11 @@ class BarLineChartTouchListener( // get the translation val t = getTrans(touchPointCenter.x, touchPointCenter.y) - val h = mChart!!.viewPortHandler + val h = mChart.viewPortHandler // take actions depending on the activated touch mode - if (mTouchMode == PINCH_ZOOM) { - mLastGesture = ChartGesture.PINCH_ZOOM + if (touchMode == PINCH_ZOOM) { + lastGesture = ChartGesture.PINCH_ZOOM val scale = totalDist / savedDist // total scale @@ -341,8 +335,8 @@ class BarLineChartTouchListener( val canZoomMoreY = if (isZoomingOut) h.canZoomOutMoreY() else h.canZoomInMoreY() - val scaleX = if (mChart!!.isScaleXEnabled) scale else 1f - val scaleY = if (mChart!!.isScaleYEnabled) scale else 1f + val scaleX = if (mChart.isScaleXEnabled) scale else 1f + val scaleY = if (mChart.isScaleYEnabled) scale else 1f if (canZoomMoreY || canZoomMoreX) { matrix.set(savedMatrix) @@ -350,8 +344,8 @@ class BarLineChartTouchListener( l?.onChartScale(event, scaleX, scaleY) } - } else if (mTouchMode == X_ZOOM && mChart!!.isScaleXEnabled) { - mLastGesture = ChartGesture.X_ZOOM + } else if (touchMode == X_ZOOM && mChart.isScaleXEnabled) { + lastGesture = ChartGesture.X_ZOOM val xDist = getXDist(event) val scaleX = xDist / savedXDist // x-axis scale @@ -365,8 +359,8 @@ class BarLineChartTouchListener( l?.onChartScale(event, scaleX, 1f) } - } else if (mTouchMode == Y_ZOOM && mChart!!.isScaleYEnabled) { - mLastGesture = ChartGesture.Y_ZOOM + } else if (touchMode == Y_ZOOM && mChart.isScaleYEnabled) { + lastGesture = ChartGesture.Y_ZOOM val yDist = getYDist(event) val scaleY = yDist / savedYDist // y-axis scale @@ -394,7 +388,7 @@ class BarLineChartTouchListener( * @return */ private fun getLimitedScaleX(scaleX: Float, t: MPPointF): Float { - val h = mChart!!.viewPortHandler + val h = mChart.viewPortHandler tempMatrix.set(savedMatrix) tempMatrix.postScale(scaleX, 1f, t.x, t.y) @@ -421,7 +415,7 @@ class BarLineChartTouchListener( * @return */ private fun getLimitedScaleY(scaleY: Float, t: MPPointF): Float { - val h = mChart!!.viewPortHandler + val h = mChart.viewPortHandler tempMatrix.set(savedMatrix) tempMatrix.postScale(1f, scaleY, t.x, t.y) @@ -447,11 +441,11 @@ class BarLineChartTouchListener( * @param e */ private fun performHighlightDrag(e: MotionEvent) { - val h = mChart!!.getHighlightByTouchPoint(e.x, e.y) + val h = mChart.getHighlightByTouchPoint(e.x, e.y) if (h != null && !h.equalTo(mLastHighlighted)) { mLastHighlighted = h - mChart!!.highlightValue(h, true) + mChart.highlightValue(h, true) } } @@ -466,16 +460,15 @@ class BarLineChartTouchListener( * @return */ fun getTrans(x: Float, y: Float): MPPointF { - val vph = mChart!!.viewPortHandler + val vph = mChart.viewPortHandler val xTrans = x - vph.offsetLeft() - var yTrans = 0f // check if axis is inverted - yTrans = if (inverted()) { + val yTrans = if (inverted()) { -(y - vph.offsetTop()) } else { - -(mChart!!.measuredHeight - y - vph.offsetBottom()) + -(mChart.measuredHeight - y - vph.offsetBottom()) } return MPPointF.getInstance(xTrans, yTrans) @@ -487,8 +480,8 @@ class BarLineChartTouchListener( * @return */ private fun inverted(): Boolean { - return (mClosestDataSetToTouch == null && mChart!!.isAnyAxisInverted) || (mClosestDataSetToTouch != null - && mChart!!.isInverted(mClosestDataSetToTouch!!.axisDependency)) + return (mClosestDataSetToTouch == null && mChart.isAnyAxisInverted) || (mClosestDataSetToTouch != null + && mChart.isInverted(mClosestDataSetToTouch!!.axisDependency)) } /** @@ -507,22 +500,22 @@ class BarLineChartTouchListener( } override fun onDoubleTap(e: MotionEvent): Boolean { - mLastGesture = ChartGesture.DOUBLE_TAP + lastGesture = ChartGesture.DOUBLE_TAP - val l = mChart!!.onChartGestureListener + val l = mChart.onChartGestureListener l?.onChartDoubleTapped(e) // check if double-tap zooming is enabled - if (mChart!!.isDoubleTapToZoomEnabled && mChart!!.data!!.entryCount > 0) { + if (mChart.isDoubleTapToZoomEnabled && mChart.data!!.entryCount > 0) { val trans = getTrans(e.x, e.y) - val scaleX = if (mChart!!.isScaleXEnabled) 1.4f else 1f - val scaleY = if (mChart!!.isScaleYEnabled) 1.4f else 1f + val scaleX = if (mChart.isScaleXEnabled) 1.4f else 1f + val scaleY = if (mChart.isScaleYEnabled) 1.4f else 1f - mChart!!.zoom(scaleX, scaleY, trans.x, trans.y) + mChart.zoom(scaleX, scaleY, trans.x, trans.y) - if (mChart!!.isLogEnabled) Log.i( + if (mChart.isLogEnabled) Log.i( "BarlineChartTouch", ("Double-Tap, Zooming In, x: " + trans.x + ", y: " + trans.y) ) @@ -536,34 +529,34 @@ class BarLineChartTouchListener( } override fun onLongPress(e: MotionEvent) { - mLastGesture = ChartGesture.LONG_PRESS + lastGesture = ChartGesture.LONG_PRESS - val l = mChart!!.onChartGestureListener + val l = mChart.onChartGestureListener l?.onChartLongPressed(e) } override fun onSingleTapUp(e: MotionEvent): Boolean { - mLastGesture = ChartGesture.SINGLE_TAP + lastGesture = ChartGesture.SINGLE_TAP - val l = mChart!!.onChartGestureListener + val l = mChart.onChartGestureListener l?.onChartSingleTapped(e) - if (!mChart!!.isHighlightPerTapEnabled) { + if (!mChart.isHighlightPerTapEnabled) { return false } - val h = mChart!!.getHighlightByTouchPoint(e.x, e.y) + val h = mChart.getHighlightByTouchPoint(e.x, e.y) performHighlight(h, e) return super.onSingleTapUp(e) } override fun onFling(e1: MotionEvent?, e2: MotionEvent, velocityX: Float, velocityY: Float): Boolean { - mLastGesture = ChartGesture.FLING + lastGesture = ChartGesture.FLING - val l = mChart!!.onChartGestureListener + val l = mChart.onChartGestureListener l?.onChartFling(e1, e2, velocityX, velocityY) @@ -581,8 +574,8 @@ class BarLineChartTouchListener( val currentTime = AnimationUtils.currentAnimationTimeMillis() - mDecelerationVelocity.x *= mChart!!.dragDecelerationFrictionCoef - mDecelerationVelocity.y *= mChart!!.dragDecelerationFrictionCoef + mDecelerationVelocity.x *= mChart.dragDecelerationFrictionCoef + mDecelerationVelocity.y *= mChart.dragDecelerationFrictionCoef val timeInterval = (currentTime - mDecelerationLastTime).toFloat() / 1000f @@ -597,13 +590,13 @@ class BarLineChartTouchListener( mDecelerationCurrentPoint.y, 0 ) - val dragDistanceX = if (mChart!!.isDragXEnabled) mDecelerationCurrentPoint.x - touchStartPoint.x else 0f - val dragDistanceY = if (mChart!!.isDragYEnabled) mDecelerationCurrentPoint.y - touchStartPoint.y else 0f + val dragDistanceX = if (mChart.isDragXEnabled) mDecelerationCurrentPoint.x - touchStartPoint.x else 0f + val dragDistanceY = if (mChart.isDragYEnabled) mDecelerationCurrentPoint.y - touchStartPoint.y else 0f performDrag(event, dragDistanceX, dragDistanceY) event.recycle() - matrix = mChart!!.viewPortHandler.refresh(matrix, mChart!!, false) + matrix = mChart.viewPortHandler.refresh(matrix, mChart, false) mDecelerationLastTime = currentTime @@ -612,8 +605,8 @@ class BarLineChartTouchListener( // Range might have changed, which means that Y-axis labels // could have changed in size, affecting Y-axis size. // So we need to recalculate offsets. - mChart!!.calculateOffsets() - mChart!!.postInvalidate() + mChart.calculateOffsets() + mChart.postInvalidate() stopDeceleration() } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/ChartTouchListener.java b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/ChartTouchListener.java deleted file mode 100644 index 75c8e864b4..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/ChartTouchListener.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.github.mikephil.charting.listener; - -import android.view.GestureDetector; -import android.view.MotionEvent; -import android.view.View; - -import com.github.mikephil.charting.charts.Chart; -import com.github.mikephil.charting.highlight.Highlight; - -/** - * Created by philipp on 12/06/15. - */ -public abstract class ChartTouchListener> extends GestureDetector.SimpleOnGestureListener implements View.OnTouchListener { - - public enum ChartGesture { - NONE, DRAG, X_ZOOM, Y_ZOOM, PINCH_ZOOM, ROTATE, SINGLE_TAP, DOUBLE_TAP, LONG_PRESS, FLING - } - - /** - * the last touch gesture that has been performed - **/ - protected ChartGesture mLastGesture = ChartGesture.NONE; - - // states - protected static final int NONE = 0; - protected static final int DRAG = 1; - protected static final int X_ZOOM = 2; - protected static final int Y_ZOOM = 3; - protected static final int PINCH_ZOOM = 4; - protected static final int POST_ZOOM = 5; - protected static final int ROTATE = 6; - - /** - * integer field that holds the current touch-state - */ - protected int mTouchMode = NONE; - - /** - * the last highlighted object (via touch) - */ - protected Highlight mLastHighlighted; - - /** - * the gesturedetector used for detecting taps and longpresses, ... - */ - protected GestureDetector mGestureDetector; - - /** - * the chart the listener represents - */ - protected T mChart; - - public ChartTouchListener(T chart) { - this.mChart = chart; - - mGestureDetector = new GestureDetector(chart.getContext(), this); - } - - /** - * Calls the OnChartGestureListener to do the start callback - * - * @param me - */ - public void startAction(MotionEvent me) { - - OnChartGestureListener l = mChart.getOnChartGestureListener(); - - if (l != null) - l.onChartGestureStart(me, mLastGesture); - } - - /** - * Calls the OnChartGestureListener to do the end callback - * - * @param me - */ - public void endAction(MotionEvent me) { - - OnChartGestureListener l = mChart.getOnChartGestureListener(); - - if (l != null) - l.onChartGestureEnd(me, mLastGesture); - } - - /** - * Sets the last value that was highlighted via touch. - * - * @param high - */ - public void setLastHighlighted(Highlight high) { - mLastHighlighted = high; - } - - /** - * returns the touch mode the listener is currently in - * - * @return - */ - public int getTouchMode() { - return mTouchMode; - } - - /** - * Returns the last gesture that has been performed on the chart. - * - * @return - */ - public ChartGesture getLastGesture() { - return mLastGesture; - } - - - /** - * Perform a highlight operation. - * - * @param e - */ - protected void performHighlight(Highlight h, MotionEvent e) { - - if (h == null || h.equalTo(mLastHighlighted)) { - mChart.highlightValue(null, true); - mLastHighlighted = null; - } else { - mChart.highlightValue(h, true); - mLastHighlighted = h; - } - } - - /** - * returns the distance between two points - * - * @param eventX - * @param startX - * @param eventY - * @param startY - * @return - */ - protected static float distance(float eventX, float startX, float eventY, float startY) { - float dx = eventX - startX; - float dy = eventY - startY; - return (float) Math.sqrt(dx * dx + dy * dy); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/ChartTouchListener.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/ChartTouchListener.kt new file mode 100644 index 0000000000..34ab681e46 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/ChartTouchListener.kt @@ -0,0 +1,128 @@ +package com.github.mikephil.charting.listener + +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import android.view.View.OnTouchListener +import com.github.mikephil.charting.charts.Chart +import com.github.mikephil.charting.highlight.Highlight +import kotlin.math.sqrt + +/** + * Created by philipp on 12/06/15. + */ +abstract class ChartTouchListener>( + /** + * the chart the listener represents + */ + protected var mChart: T, +) : SimpleOnGestureListener(), OnTouchListener { + enum class ChartGesture { + NONE, DRAG, X_ZOOM, Y_ZOOM, PINCH_ZOOM, ROTATE, SINGLE_TAP, DOUBLE_TAP, LONG_PRESS, FLING + } + + /** + * Returns the last gesture that has been performed on the chart. + * + * @return + */ + /** + * the last touch gesture that has been performed + */ + var lastGesture: ChartGesture = ChartGesture.NONE + protected set + + /** + * returns the touch mode the listener is currently in + * + * @return + */ + /** + * integer field that holds the current touch-state + */ + var touchMode: Int = NONE + protected set + + /** + * the last highlighted object (via touch) + */ + protected var mLastHighlighted: Highlight? = null + + /** + * the gesturedetector used for detecting taps and longpresses, ... + */ + protected val mGestureDetector: GestureDetector? = GestureDetector(mChart.context, this) + + /** + * Calls the OnChartGestureListener to do the start callback + * + * @param me + */ + fun startAction(me: MotionEvent?) { + val l = mChart.onChartGestureListener + + l?.onChartGestureStart(me, this.lastGesture) + } + + /** + * Calls the OnChartGestureListener to do the end callback + * + * @param me + */ + fun endAction(me: MotionEvent?) { + val l = mChart.onChartGestureListener + + l?.onChartGestureEnd(me, this.lastGesture) + } + + /** + * Sets the last value that was highlighted via touch. + * + * @param high + */ + fun setLastHighlighted(high: Highlight?) { + mLastHighlighted = high + } + + + /** + * Perform a highlight operation. + * + * @param e + */ + protected fun performHighlight(h: Highlight?, e: MotionEvent?) { + if (h == null || h.equalTo(mLastHighlighted)) { + mChart.highlightValue(null, true) + mLastHighlighted = null + } else { + mChart.highlightValue(h, true) + mLastHighlighted = h + } + } + + companion object { + // states + protected const val NONE: Int = 0 + protected const val DRAG: Int = 1 + protected const val X_ZOOM: Int = 2 + protected const val Y_ZOOM: Int = 3 + protected const val PINCH_ZOOM: Int = 4 + protected const val POST_ZOOM: Int = 5 + protected const val ROTATE: Int = 6 + + /** + * returns the distance between two points + * + * @param eventX + * @param startX + * @param eventY + * @param startY + * @return + */ + protected fun distance(eventX: Float, startX: Float, eventY: Float, startY: Float): Float { + val dx = eventX - startX + val dy = eventY - startY + return sqrt((dx * dx + dy * dy).toDouble()).toFloat() + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.java b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.kt similarity index 64% rename from MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.kt index da0c5ed180..85e9e548f1 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartGestureListener.kt @@ -1,21 +1,21 @@ -package com.github.mikephil.charting.listener; +package com.github.mikephil.charting.listener -import android.view.MotionEvent; +import android.view.MotionEvent +import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture /** * Listener for callbacks when doing gestures on the chart. * * @author Philipp Jahoda */ -public interface OnChartGestureListener { - +interface OnChartGestureListener { /** * Callbacks when a touch-gesture has started on the chart (ACTION_DOWN) * * @param me * @param lastPerformedGesture */ - void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture); + fun onChartGestureStart(me: MotionEvent?, lastPerformedGesture: ChartGesture?) /** * Callbacks when a touch-gesture has ended on the chart (ACTION_UP, ACTION_CANCEL) @@ -23,28 +23,28 @@ public interface OnChartGestureListener { * @param me * @param lastPerformedGesture */ - void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture); + fun onChartGestureEnd(me: MotionEvent?, lastPerformedGesture: ChartGesture?) /** * Callbacks when the chart is longpressed. * * @param me */ - void onChartLongPressed(MotionEvent me); + fun onChartLongPressed(me: MotionEvent?) /** * Callbacks when the chart is double-tapped. * * @param me */ - void onChartDoubleTapped(MotionEvent me); + fun onChartDoubleTapped(me: MotionEvent?) /** * Callbacks when the chart is single-tapped. * * @param me */ - void onChartSingleTapped(MotionEvent me); + fun onChartSingleTapped(me: MotionEvent?) /** * Callbacks then a fling gesture is made on the chart. @@ -54,7 +54,7 @@ public interface OnChartGestureListener { * @param velocityX * @param velocityY */ - void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY); + fun onChartFling(me1: MotionEvent?, me2: MotionEvent?, velocityX: Float, velocityY: Float) /** * Callbacks when the chart is scaled / zoomed via pinch zoom / double-tap gesture. @@ -63,7 +63,7 @@ public interface OnChartGestureListener { * @param scaleX scalefactor on the x-axis * @param scaleY scalefactor on the y-axis */ - void onChartScale(MotionEvent me, float scaleX, float scaleY); + fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) /** * Callbacks when the chart is moved / translated via drag gesture. @@ -72,5 +72,5 @@ public interface OnChartGestureListener { * @param dX translation distance on the x-axis * @param dY translation distance on the y-axis */ - void onChartTranslate(MotionEvent me, float dX, float dY); + fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartValueSelectedListener.java b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartValueSelectedListener.kt similarity index 54% rename from MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartValueSelectedListener.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartValueSelectedListener.kt index 7f50232b7e..d3aaae7cf7 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartValueSelectedListener.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnChartValueSelectedListener.kt @@ -1,7 +1,7 @@ -package com.github.mikephil.charting.listener; +package com.github.mikephil.charting.listener -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight /** * Listener for callbacks when selecting values inside the chart by @@ -9,19 +9,18 @@ * * @author Philipp Jahoda */ -public interface OnChartValueSelectedListener { - +interface OnChartValueSelectedListener { /** * Called when a value has been selected inside the chart. * * @param e The selected Entry * @param h The corresponding highlight object that contains information - * about the highlighted position such as dataSetIndex, ... + * about the highlighted position such as dataSetIndex, ... */ - void onValueSelected(Entry e, Highlight h); + fun onValueSelected(e: Entry?, h: Highlight?) /** * Called when nothing has been selected or an "un-select" has been made. */ - void onNothingSelected(); + fun onNothingSelected() } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawLineChartTouchListener.java b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawLineChartTouchListener.java deleted file mode 100644 index ca3a67f677..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawLineChartTouchListener.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.github.mikephil.charting.listener; - -import android.view.GestureDetector.SimpleOnGestureListener; -import android.view.MotionEvent; -import android.view.View; -import android.view.View.OnTouchListener; - -public class OnDrawLineChartTouchListener extends SimpleOnGestureListener implements OnTouchListener { - - @Override - public boolean onTouch(View v, MotionEvent event) { - return false; - } - -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawLineChartTouchListener.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawLineChartTouchListener.kt new file mode 100644 index 0000000000..1b1b99e1c6 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawLineChartTouchListener.kt @@ -0,0 +1,12 @@ +package com.github.mikephil.charting.listener + +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import android.view.View +import android.view.View.OnTouchListener + +class OnDrawLineChartTouchListener : SimpleOnGestureListener(), OnTouchListener { + override fun onTouch(v: View?, event: MotionEvent?): Boolean { + return false + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawListener.java b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawListener.java deleted file mode 100644 index 5890350bcd..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawListener.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.github.mikephil.charting.listener; - -import com.github.mikephil.charting.data.DataSet; -import com.github.mikephil.charting.data.Entry; - -/** - * Listener for callbacks when drawing on the chart. - * - * @author Philipp - * - */ -public interface OnDrawListener { - - /** - * Called whenever an entry is added with the finger. Note this is also called for entries that are generated by the - * library, when the touch gesture is too fast and skips points. - * - * @param entry - * the last drawn entry - */ - void onEntryAdded(Entry entry); - - /** - * Called whenever an entry is moved by the user after beeing highlighted - * - * @param entry - */ - void onEntryMoved(Entry entry); - - /** - * Called when drawing finger is lifted and the draw is finished. - * - * @param dataSet - * the last drawn DataSet - */ - void onDrawFinished(DataSet dataSet); - -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawListener.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawListener.kt new file mode 100644 index 0000000000..3eba66f1c0 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/OnDrawListener.kt @@ -0,0 +1,35 @@ +package com.github.mikephil.charting.listener + +import com.github.mikephil.charting.data.DataSet +import com.github.mikephil.charting.data.Entry + +/** + * Listener for callbacks when drawing on the chart. + * + * @author Philipp + */ +interface OnDrawListener { + /** + * Called whenever an entry is added with the finger. Note this is also called for entries that are generated by the + * library, when the touch gesture is too fast and skips points. + * + * @param entry + * the last drawn entry + */ + fun onEntryAdded(entry: Entry?) + + /** + * Called whenever an entry is moved by the user after beeing highlighted + * + * @param entry + */ + fun onEntryMoved(entry: Entry?) + + /** + * Called when drawing finger is lifted and the draw is finished. + * + * @param dataSet + * the last drawn DataSet + */ + fun onDrawFinished(dataSet: DataSet<*>?) +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/PieRadarChartTouchListener.java b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/PieRadarChartTouchListener.java deleted file mode 100644 index d3527f924a..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/PieRadarChartTouchListener.java +++ /dev/null @@ -1,288 +0,0 @@ - -package com.github.mikephil.charting.listener; - -import android.annotation.SuppressLint; -import android.graphics.PointF; -import android.view.MotionEvent; -import android.view.View; -import android.view.animation.AnimationUtils; - -import com.github.mikephil.charting.charts.PieRadarChartBase; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.Utils; - -import java.util.ArrayList; - -/** - * Touchlistener for the PieChart. - * - * @author Philipp Jahoda - */ -public class PieRadarChartTouchListener extends ChartTouchListener> { - - private MPPointF mTouchStartPoint = MPPointF.getInstance(0,0); - - /** - * the angle where the dragging started - */ - private float mStartAngle = 0f; - - private ArrayList _velocitySamples = new ArrayList(); - - private long mDecelerationLastTime = 0; - private float mDecelerationAngularVelocity = 0.f; - - public PieRadarChartTouchListener(PieRadarChartBase chart) { - super(chart); - } - - @SuppressLint("ClickableViewAccessibility") - @Override - public boolean onTouch(View v, MotionEvent event) { - - if (mGestureDetector.onTouchEvent(event)) - return true; - - // if rotation by touch is enabled - // TODO: Also check if the pie itself is being touched, rather than the entire chart area - if (mChart.isRotationEnabled()) { - - float x = event.getX(); - float y = event.getY(); - - switch (event.getAction()) { - - case MotionEvent.ACTION_DOWN: - - startAction(event); - - stopDeceleration(); - - resetVelocity(); - - if (mChart.isDragDecelerationEnabled()) - sampleVelocity(x, y); - - setGestureStartAngle(x, y); - mTouchStartPoint.x = x; - mTouchStartPoint.y = y; - - break; - case MotionEvent.ACTION_MOVE: - - if (mChart.isDragDecelerationEnabled()) - sampleVelocity(x, y); - - if (mTouchMode == NONE - && distance(x, mTouchStartPoint.x, y, mTouchStartPoint.y) - > Utils.convertDpToPixel(8f)) { - mLastGesture = ChartGesture.ROTATE; - mTouchMode = ROTATE; - mChart.disableScroll(); - } else if (mTouchMode == ROTATE) { - updateGestureRotation(x, y); - mChart.invalidate(); - } - - endAction(event); - - break; - case MotionEvent.ACTION_UP: - - if (mChart.isDragDecelerationEnabled()) { - - stopDeceleration(); - - sampleVelocity(x, y); - - mDecelerationAngularVelocity = calculateVelocity(); - - if (mDecelerationAngularVelocity != 0.f) { - mDecelerationLastTime = AnimationUtils.currentAnimationTimeMillis(); - - Utils.postInvalidateOnAnimation(mChart); // This causes computeScroll to fire, recommended for this by Google - } - } - - mChart.enableScroll(); - mTouchMode = NONE; - - endAction(event); - - break; - } - } - - return true; - } - - @Override - public void onLongPress(MotionEvent me) { - - mLastGesture = ChartGesture.LONG_PRESS; - - OnChartGestureListener l = mChart.getOnChartGestureListener(); - - if (l != null) { - l.onChartLongPressed(me); - } - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - return true; - } - - @Override - public boolean onSingleTapUp(MotionEvent e) { - - mLastGesture = ChartGesture.SINGLE_TAP; - - OnChartGestureListener l = mChart.getOnChartGestureListener(); - - if (l != null) { - l.onChartSingleTapped(e); - } - - if(!mChart.isHighlightPerTapEnabled()) { - return false; - } - - Highlight high = mChart.getHighlightByTouchPoint(e.getX(), e.getY()); - performHighlight(high, e); - - return true; - } - - private void resetVelocity() { - _velocitySamples.clear(); - } - - private void sampleVelocity(float touchLocationX, float touchLocationY) { - - long currentTime = AnimationUtils.currentAnimationTimeMillis(); - - _velocitySamples.add(new AngularVelocitySample(currentTime, mChart.getAngleForPoint(touchLocationX, touchLocationY))); - - // Remove samples older than our sample time - 1 seconds - for (int i = 0, count = _velocitySamples.size(); i < count - 2; i++) { - if (currentTime - _velocitySamples.get(i).time > 1000) { - _velocitySamples.remove(0); - i--; - count--; - } else { - break; - } - } - } - - private float calculateVelocity() { - - if (_velocitySamples.isEmpty()) - return 0.f; - - AngularVelocitySample firstSample = _velocitySamples.get(0); - AngularVelocitySample lastSample = _velocitySamples.get(_velocitySamples.size() - 1); - - // Look for a sample that's closest to the latest sample, but not the same, so we can deduce the direction - AngularVelocitySample beforeLastSample = firstSample; - for (int i = _velocitySamples.size() - 1; i >= 0; i--) { - beforeLastSample = _velocitySamples.get(i); - if (beforeLastSample.angle != lastSample.angle) { - break; - } - } - - // Calculate the sampling time - float timeDelta = (lastSample.time - firstSample.time) / 1000.f; - if (timeDelta == 0.f) { - timeDelta = 0.1f; - } - - // Calculate clockwise/ccw by choosing two values that should be closest to each other, - // so if the angles are two far from each other we know they are inverted "for sure" - boolean clockwise = lastSample.angle >= beforeLastSample.angle; - if (Math.abs(lastSample.angle - beforeLastSample.angle) > 270.0) { - clockwise = !clockwise; - } - - // Now if the "gesture" is over a too big of an angle - then we know the angles are inverted, and we need to move them closer to each other from both sides of the 360.0 wrapping point - if (lastSample.angle - firstSample.angle > 180.0) { - firstSample.angle += 360.0; - } else if (firstSample.angle - lastSample.angle > 180.0) { - lastSample.angle += 360.0; - } - - // The velocity - float velocity = Math.abs((lastSample.angle - firstSample.angle) / timeDelta); - - // Direction? - if (!clockwise) { - velocity = -velocity; - } - - return velocity; - } - - /** - * 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 setGestureStartAngle(float x, float y) { - mStartAngle = mChart.getAngleForPoint(x, y) - mChart.getRawRotationAngle(); - } - - /** - * updates the view rotation depending on the given touch position, also - * takes the starting angle into consideration - * - * @param x - * @param y - */ - public void updateGestureRotation(float x, float y) { - mChart.setRotationAngle(mChart.getAngleForPoint(x, y) - mStartAngle); - } - - /** - * Sets the deceleration-angular-velocity to 0f - */ - public void stopDeceleration() { - mDecelerationAngularVelocity = 0.f; - } - - public void computeScroll() { - - if (mDecelerationAngularVelocity == 0.f) - return; // There's no deceleration in progress - - final long currentTime = AnimationUtils.currentAnimationTimeMillis(); - - mDecelerationAngularVelocity *= mChart.getDragDecelerationFrictionCoef(); - - final float timeInterval = (float) (currentTime - mDecelerationLastTime) / 1000.f; - - mChart.setRotationAngle(mChart.getRotationAngle() + mDecelerationAngularVelocity * timeInterval); - - mDecelerationLastTime = currentTime; - - if (Math.abs(mDecelerationAngularVelocity) >= 0.001) - Utils.postInvalidateOnAnimation(mChart); // This causes computeScroll to fire, recommended for this by Google - else - stopDeceleration(); - } - - private class AngularVelocitySample { - - public long time; - public float angle; - - public AngularVelocitySample(long time, float angle) { - this.time = time; - this.angle = angle; - } - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/listener/PieRadarChartTouchListener.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/PieRadarChartTouchListener.kt new file mode 100644 index 0000000000..2be79fd686 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/listener/PieRadarChartTouchListener.kt @@ -0,0 +1,246 @@ +package com.github.mikephil.charting.listener + +import android.annotation.SuppressLint +import android.view.MotionEvent +import android.view.View +import android.view.animation.AnimationUtils +import com.github.mikephil.charting.charts.PieRadarChartBase +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Utils +import kotlin.math.abs + +/** + * Touchlistener for the PieChart. + * + * @author Philipp Jahoda + */ +class PieRadarChartTouchListener(chart: PieRadarChartBase<*, *, *>) : ChartTouchListener>(chart) { + private val mTouchStartPoint: MPPointF = MPPointF.Companion.getInstance(0f, 0f) + + /** + * the angle where the dragging started + */ + private var mStartAngle = 0f + + private val _velocitySamples: ArrayList = ArrayList() + + private var mDecelerationLastTime: Long = 0 + private var mDecelerationAngularVelocity = 0f + + @SuppressLint("ClickableViewAccessibility") + override fun onTouch(v: View?, event: MotionEvent): Boolean { + if (mGestureDetector?.onTouchEvent(event) == true) return true + + // if rotation by touch is enabled + // TODO: Also check if the pie itself is being touched, rather than the entire chart area + if (mChart.isRotationEnabled) { + val x = event.x + val y = event.y + + when (event.action) { + MotionEvent.ACTION_DOWN -> { + startAction(event) + + stopDeceleration() + + resetVelocity() + + if (mChart.isDragDecelerationEnabled) sampleVelocity(x, y) + + setGestureStartAngle(x, y) + mTouchStartPoint.x = x + mTouchStartPoint.y = y + } + + MotionEvent.ACTION_MOVE -> { + if (mChart.isDragDecelerationEnabled) sampleVelocity(x, y) + + if (touchMode == NONE + && (distance(x, mTouchStartPoint.x, y, mTouchStartPoint.y) + > Utils.convertDpToPixel(8f)) + ) { + lastGesture = ChartGesture.ROTATE + touchMode = ROTATE + mChart.disableScroll() + } else if (touchMode == ROTATE) { + updateGestureRotation(x, y) + mChart.invalidate() + } + + endAction(event) + } + + MotionEvent.ACTION_UP -> { + if (mChart.isDragDecelerationEnabled) { + stopDeceleration() + + sampleVelocity(x, y) + + mDecelerationAngularVelocity = calculateVelocity() + + if (mDecelerationAngularVelocity != 0f) { + mDecelerationLastTime = AnimationUtils.currentAnimationTimeMillis() + + Utils.postInvalidateOnAnimation(mChart) // This causes computeScroll to fire, recommended for this by Google + } + } + + mChart.enableScroll() + touchMode = NONE + + endAction(event) + } + } + } + + return true + } + + override fun onLongPress(me: MotionEvent) { + lastGesture = ChartGesture.LONG_PRESS + + val l = mChart.onChartGestureListener + + l?.onChartLongPressed(me) + } + + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + return true + } + + override fun onSingleTapUp(e: MotionEvent): Boolean { + lastGesture = ChartGesture.SINGLE_TAP + + val l = mChart.onChartGestureListener + + l?.onChartSingleTapped(e) + + if (!mChart.isHighlightPerTapEnabled) { + return false + } + + val high = mChart.getHighlightByTouchPoint(e.x, e.y) + performHighlight(high, e) + + return true + } + + private fun resetVelocity() { + _velocitySamples.clear() + } + + private fun sampleVelocity(touchLocationX: Float, touchLocationY: Float) { + val currentTime = AnimationUtils.currentAnimationTimeMillis() + + _velocitySamples.add(AngularVelocitySample(currentTime, mChart.getAngleForPoint(touchLocationX, touchLocationY))) + + // Remove samples older than our sample time - 1 seconds + var i = 0 + var count = _velocitySamples.size + while (i < count - 2) { + if (currentTime - _velocitySamples[i].time > 1000) { + _velocitySamples.removeAt(0) + i-- + count-- + } else { + break + } + i++ + } + } + + private fun calculateVelocity(): Float { + if (_velocitySamples.isEmpty()) return 0f + + val firstSample = _velocitySamples[0] + val lastSample = _velocitySamples[_velocitySamples.size - 1] + + // Look for a sample that's closest to the latest sample, but not the same, so we can deduce the direction + var beforeLastSample = firstSample + for (i in _velocitySamples.indices.reversed()) { + beforeLastSample = _velocitySamples[i] + if (beforeLastSample.angle != lastSample.angle) { + break + } + } + + // Calculate the sampling time + var timeDelta = (lastSample.time - firstSample.time) / 1000f + if (timeDelta == 0f) { + timeDelta = 0.1f + } + + // Calculate clockwise/ccw by choosing two values that should be closest to each other, + // so if the angles are two far from each other we know they are inverted "for sure" + var clockwise = lastSample.angle >= beforeLastSample.angle + if (abs(lastSample.angle - beforeLastSample.angle) > 270.0) { + clockwise = !clockwise + } + + // Now if the "gesture" is over a too big of an angle - then we know the angles are inverted, and we need to move them closer to each other from both sides of the 360.0 wrapping point + if (lastSample.angle - firstSample.angle > 180.0) { + firstSample.angle += 360.0.toFloat() + } else if (firstSample.angle - lastSample.angle > 180.0) { + lastSample.angle += 360.0.toFloat() + } + + // The velocity + var velocity = abs((lastSample.angle - firstSample.angle) / timeDelta) + + // Direction? + if (!clockwise) { + velocity = -velocity + } + + return velocity + } + + /** + * 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 + */ + fun setGestureStartAngle(x: Float, y: Float) { + mStartAngle = mChart.getAngleForPoint(x, y) - mChart.rawRotationAngle + } + + /** + * updates the view rotation depending on the given touch position, also + * takes the starting angle into consideration + * + * @param x + * @param y + */ + fun updateGestureRotation(x: Float, y: Float) { + mChart.rotationAngle = mChart.getAngleForPoint(x, y) - mStartAngle + } + + /** + * Sets the deceleration-angular-velocity to 0f + */ + fun stopDeceleration() { + mDecelerationAngularVelocity = 0f + } + + fun computeScroll() { + if (mDecelerationAngularVelocity == 0f) return // There's no deceleration in progress + + + val currentTime = AnimationUtils.currentAnimationTimeMillis() + + mDecelerationAngularVelocity *= mChart.dragDecelerationFrictionCoef + + val timeInterval = (currentTime - mDecelerationLastTime).toFloat() / 1000f + + mChart.rotationAngle = (mChart.rotationAngle + mDecelerationAngularVelocity * timeInterval) + + mDecelerationLastTime = currentTime + + if (abs(mDecelerationAngularVelocity) >= 0.001) Utils.postInvalidateOnAnimation(mChart) // This causes computeScroll to fire, recommended for this by Google + else stopDeceleration() + } + + private inner class AngularVelocitySample(var time: Long, var angle: Float) +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/matrix/Vector3.java b/MPChartLib/src/main/java/com/github/mikephil/charting/matrix/Vector3.java deleted file mode 100644 index c4457248d9..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/matrix/Vector3.java +++ /dev/null @@ -1,134 +0,0 @@ - -package com.github.mikephil.charting.matrix; - -/** - * Simple 3D vector class. Handles basic vector math for 3D vectors. - */ -public final class Vector3 { - public float x; - public float y; - public float z; - - public static final Vector3 ZERO = new Vector3(0, 0, 0); - public static final Vector3 UNIT_X = new Vector3(1, 0, 0); - public static final Vector3 UNIT_Y = new Vector3(0, 1, 0); - public static final Vector3 UNIT_Z = new Vector3(0, 0, 1); - - public Vector3() { - } - - public Vector3(float[] array) { - set(array[0], array[1], array[2]); - } - - public Vector3(float xValue, float yValue, float zValue) { - set(xValue, yValue, zValue); - } - - public Vector3(Vector3 other) { - set(other); - } - - public final void add(Vector3 other) { - x += other.x; - y += other.y; - z += other.z; - } - - public final void add(float otherX, float otherY, float otherZ) { - x += otherX; - y += otherY; - z += otherZ; - } - - public final void subtract(Vector3 other) { - x -= other.x; - y -= other.y; - z -= other.z; - } - - public final void subtractMultiple(Vector3 other, float multiplicator) { - x -= other.x * multiplicator; - y -= other.y * multiplicator; - z -= other.z * multiplicator; - } - - public final void multiply(float magnitude) { - x *= magnitude; - y *= magnitude; - z *= magnitude; - } - - public final void multiply(Vector3 other) { - x *= other.x; - y *= other.y; - z *= other.z; - } - - public final void divide(float magnitude) { - if (magnitude != 0.0f) { - x /= magnitude; - y /= magnitude; - z /= magnitude; - } - } - - public final void set(Vector3 other) { - x = other.x; - y = other.y; - z = other.z; - } - - public final void set(float xValue, float yValue, float zValue) { - x = xValue; - y = yValue; - z = zValue; - } - - public final float dot(Vector3 other) { - return (x * other.x) + (y * other.y) + (z * other.z); - } - - public final Vector3 cross(Vector3 other) { - return new Vector3(y * other.z - z * other.y, - z * other.x - x * other.z, - x * other.y - y * other.x); - } - - public final float length() { - return (float) Math.sqrt(length2()); - } - - public final float length2() { - return (x * x) + (y * y) + (z * z); - } - - public final float distance2(Vector3 other) { - float dx = x - other.x; - float dy = y - other.y; - float dz = z - other.z; - return (dx * dx) + (dy * dy) + (dz * dz); - } - - public final float normalize() { - final float magnitude = length(); - - // TODO: I'm choosing safety over speed here. - if (magnitude != 0.0f) { - x /= magnitude; - y /= magnitude; - z /= magnitude; - } - - return magnitude; - } - - public final void zero() { - set(0.0f, 0.0f, 0.0f); - } - - public final boolean pointsInSameDirection(Vector3 other) { - return this.dot(other) > 0; - } - -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/matrix/Vector3.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/matrix/Vector3.kt new file mode 100644 index 0000000000..88f069ab32 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/matrix/Vector3.kt @@ -0,0 +1,137 @@ +package com.github.mikephil.charting.matrix + +import kotlin.math.sqrt + +/** + * Simple 3D vector class. Handles basic vector math for 3D vectors. + */ +class Vector3 { + var x: Float = 0f + var y: Float = 0f + var z: Float = 0f + + constructor() + + constructor(array: FloatArray) { + set(array[0], array[1], array[2]) + } + + constructor(xValue: Float, yValue: Float, zValue: Float) { + set(xValue, yValue, zValue) + } + + constructor(other: Vector3) { + set(other) + } + + fun add(other: Vector3) { + x += other.x + y += other.y + z += other.z + } + + fun add(otherX: Float, otherY: Float, otherZ: Float) { + x += otherX + y += otherY + z += otherZ + } + + fun subtract(other: Vector3) { + x -= other.x + y -= other.y + z -= other.z + } + + fun subtractMultiple(other: Vector3, multiplicator: Float) { + x -= other.x * multiplicator + y -= other.y * multiplicator + z -= other.z * multiplicator + } + + fun multiply(magnitude: Float) { + x *= magnitude + y *= magnitude + z *= magnitude + } + + fun multiply(other: Vector3) { + x *= other.x + y *= other.y + z *= other.z + } + + fun divide(magnitude: Float) { + if (magnitude != 0.0f) { + x /= magnitude + y /= magnitude + z /= magnitude + } + } + + fun set(other: Vector3) { + x = other.x + y = other.y + z = other.z + } + + fun set(xValue: Float, yValue: Float, zValue: Float) { + x = xValue + y = yValue + z = zValue + } + + fun dot(other: Vector3): Float { + return (x * other.x) + (y * other.y) + (z * other.z) + } + + fun cross(other: Vector3): Vector3 { + return Vector3( + y * other.z - z * other.y, + z * other.x - x * other.z, + x * other.y - y * other.x + ) + } + + fun length(): Float { + return sqrt(length2().toDouble()).toFloat() + } + + fun length2(): Float { + return (x * x) + (y * y) + (z * z) + } + + fun distance2(other: Vector3): Float { + val dx = x - other.x + val dy = y - other.y + val dz = z - other.z + return (dx * dx) + (dy * dy) + (dz * dz) + } + + fun normalize(): Float { + val magnitude = length() + + // TODO: I'm choosing safety over speed here. + if (magnitude != 0.0f) { + x /= magnitude + y /= magnitude + z /= magnitude + } + + return magnitude + } + + fun zero() { + set(0.0f, 0.0f, 0.0f) + } + + fun pointsInSameDirection(other: Vector3): Boolean { + return this.dot(other) > 0 + } + + companion object { + val ZERO: Vector3 = Vector3(0f, 0f, 0f) + val UNIT_X: Vector3 = Vector3(1f, 0f, 0f) + val UNIT_Y: Vector3 = Vector3(0f, 1f, 0f) + val UNIT_Z: Vector3 = Vector3(0f, 0f, 1f) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/model/GradientColor.java b/MPChartLib/src/main/java/com/github/mikephil/charting/model/GradientColor.java deleted file mode 100644 index 55f6bf6b6e..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/model/GradientColor.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.github.mikephil.charting.model; - -import com.github.mikephil.charting.utils.Fill; - -/** - * Deprecated. Use `Fill` - */ -@Deprecated -public class GradientColor extends Fill { - /** - * Deprecated. Use `Fill.getGradientColors()` - */ - @Deprecated - public int getStartColor() { - return getGradientColors()[0]; - } - - /** - * Deprecated. Use `Fill.setGradientColors(...)` - */ - @Deprecated - public void setStartColor(int startColor) { - if (getGradientColors() == null || getGradientColors().length != 2) { - setGradientColors(new int[]{ - startColor, - getGradientColors() != null && getGradientColors().length > 1 - ? getGradientColors()[1] - : 0 - }); - } else { - getGradientColors()[0] = startColor; - } - } - - /** - * Deprecated. Use `Fill.getGradientColors()` - */ - @Deprecated - public int getEndColor() { - return getGradientColors()[1]; - } - - /** - * Deprecated. Use `Fill.setGradientColors(...)` - */ - @Deprecated - public void setEndColor(int endColor) { - if (getGradientColors() == null || getGradientColors().length != 2) { - setGradientColors(new int[]{ - getGradientColors() != null && getGradientColors().length > 0 - ? getGradientColors()[0] - : 0, - endColor - }); - } else { - getGradientColors()[1] = endColor; - } - } - -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/model/GradientColor.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/model/GradientColor.kt new file mode 100644 index 0000000000..fc5c166fc8 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/model/GradientColor.kt @@ -0,0 +1,57 @@ +package com.github.mikephil.charting.model + +import com.github.mikephil.charting.utils.Fill + +/** + * Deprecated. Use `Fill` + */ +@Deprecated("") +class GradientColor : Fill() { + @get:Deprecated("") + @set:Deprecated("") + var startColor: Int + /** + * Deprecated. Use `Fill.getGradientColors()` + */ + get() = gradientColors!![0] + /** + * Deprecated. Use `Fill.setGradientColors(...)` + */ + set(startColor) { + if (gradientColors == null || gradientColors!!.size != 2) { + this.gradientColors = intArrayOf( + startColor, + if (gradientColors != null && gradientColors!!.size > 1) + gradientColors!![1] + else + 0 + ) + } else { + gradientColors!![0] = startColor + } + } + + @get:Deprecated("") + @set:Deprecated("") + var endColor: Int + /** + * Deprecated. Use `Fill.getGradientColors()` + */ + get() = gradientColors!![1] + /** + * Deprecated. Use `Fill.setGradientColors(...)` + */ + set(endColor) { + if (gradientColors == null || gradientColors!!.size != 2) { + this.gradientColors = intArrayOf( + if (gradientColors != null && gradientColors!!.isNotEmpty()) + gradientColors!![0] + else + 0, + endColor + ) + } else { + gradientColors!![1] = endColor + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarChartRenderer.kt index 5279dd1b90..b408272434 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarChartRenderer.kt @@ -19,8 +19,8 @@ import kotlin.math.min open class BarChartRenderer( @JvmField var chart: BarDataProvider, - animator: ChartAnimator?, - viewPortHandler: ViewPortHandler? + animator: ChartAnimator, + viewPortHandler: ViewPortHandler, ) : BarLineScatterCandleBubbleRenderer(animator, viewPortHandler) { /** * the rect object that is used for drawing the bars @@ -29,7 +29,7 @@ open class BarChartRenderer( protected var barRect: RectF = RectF() @JvmField - protected var barBuffers: MutableList = mutableListOf() + protected var barBuffers: MutableList = mutableListOf() @JvmField protected var shadowPaint: Paint @@ -48,8 +48,8 @@ open class BarChartRenderer( private var roundedBarRadius = 0f constructor( - chart: BarDataProvider, animator: ChartAnimator?, - viewPortHandler: ViewPortHandler?, mDrawRoundedBars: Boolean, mRoundedBarRadius: Float + chart: BarDataProvider, animator: ChartAnimator, + viewPortHandler: ViewPortHandler, mDrawRoundedBars: Boolean, mRoundedBarRadius: Float ) : this(chart, animator, viewPortHandler) { this.drawRoundedBars = mDrawRoundedBars this.roundedBarRadius = mRoundedBarRadius @@ -59,7 +59,7 @@ open class BarChartRenderer( val barData = chart.barData barBuffers = mutableListOf() - barData.dataSets.forEach { + barData?.dataSets?.forEach { barBuffers.add( BarBuffer( it.entryCount * 4 * (if (it.isStacked) it.stackSize else 1), @@ -70,11 +70,11 @@ open class BarChartRenderer( } override fun drawData(c: Canvas) { - if (barBuffers.size == 0) { + if (barBuffers.isEmpty()) { initBuffers() } - val barData = chart.barData + val barData = chart.barData ?: return for (i in 0..= 0) (buffer.buffer[j + 1] + posOffset) else (buffer.buffer[j + 3] + negOffset) + icon?.let { + var px = x + var py = if (`val` >= 0) (buffer.buffer[j + 1] + posOffset) else (buffer.buffer[j + 3] + negOffset) - px += iconsOffset.x - py += iconsOffset.y + px += iconsOffset.x + py += iconsOffset.y - Utils.drawImage( - c, - icon, - px.toInt(), - py.toInt(), - icon!!.intrinsicWidth, - icon.intrinsicHeight - ) + Utils.drawImage( + c, + icon, + px.toInt(), + py.toInt(), + icon.intrinsicWidth, + icon.intrinsicHeight + ) + } } j += 4 } @@ -355,7 +356,7 @@ open class BarChartRenderer( val entry = dataSet.getEntryForIndex(index) val vals = entry.yVals - val x = (buffer!!.buffer[bufferIndex] + buffer.buffer[bufferIndex + 2]) / 2f + val x = (buffer.buffer[bufferIndex] + buffer.buffer[bufferIndex + 2]) / 2f val color = dataSet.getValueTextColor(index) @@ -382,24 +383,26 @@ open class BarChartRenderer( ) } - if (entry.icon != null && dataSet.isDrawIconsEnabled) { + if (dataSet.isDrawIconsEnabled) { val icon = entry.icon - var px = x - var py = buffer.buffer[bufferIndex + 1] + - (if (entry.y >= 0) posOffset else negOffset) + icon?.let { + var px = x + var py = buffer.buffer[bufferIndex + 1] + + (if (entry.y >= 0) posOffset else negOffset) - px += iconsOffset.x - py += iconsOffset.y + px += iconsOffset.x + py += iconsOffset.y - Utils.drawImage( - c, - icon, - px.toInt(), - py.toInt(), - icon!!.intrinsicWidth, - icon.intrinsicHeight - ) + Utils.drawImage( + c, + icon, + px.toInt(), + py.toInt(), + icon.intrinsicWidth, + icon.intrinsicHeight + ) + } } // draw stack values @@ -433,7 +436,7 @@ open class BarChartRenderer( } } - trans!!.pointValuesToPixel(transformed) + trans.pointValuesToPixel(transformed) var k = 0 while (k < transformed.size) { @@ -468,17 +471,19 @@ open class BarChartRenderer( ) } - if (entry.icon != null && dataSet.isDrawIconsEnabled) { + if (dataSet.isDrawIconsEnabled) { val icon = entry.icon - Utils.drawImage( - c, - icon, - (x + iconsOffset.x).toInt(), - (y + iconsOffset.y).toInt(), - icon!!.intrinsicWidth, - icon.intrinsicHeight - ) + icon?.let { + Utils.drawImage( + c, + icon, + (x + iconsOffset.x).toInt(), + (y + iconsOffset.y).toInt(), + icon.intrinsicWidth, + icon.intrinsicHeight + ) + } } k += 2 } @@ -495,18 +500,18 @@ open class BarChartRenderer( } override fun drawHighlighted(c: Canvas, indices: Array) { - val barData = chart.barData + val barData = chart.barData ?: return for (high in indices) { val set = barData.getDataSetByIndex(high.dataSetIndex) - if (set == null || !set.isHighlightEnabled) { + if (!set.isHighlightEnabled) { continue } val e = set.getEntryForXValue(high.x, high.y) - if (!isInBoundsX(e, set)) { + if (!isInBoundsX(e, set) || e == null) { continue } @@ -535,7 +540,7 @@ open class BarChartRenderer( y2 = 0f } - prepareBarHighlight(e.x, y1, y2, barData.barWidth / 2f, trans!!) + prepareBarHighlight(e.x, y1, y2, barData.barWidth / 2f, trans) setHighlightDrawPos(high, barRect) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarLineScatterCandleBubbleRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarLineScatterCandleBubbleRenderer.java deleted file mode 100644 index 6610748375..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarLineScatterCandleBubbleRenderer.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.github.mikephil.charting.renderer; - -import com.github.mikephil.charting.animation.ChartAnimator; -import com.github.mikephil.charting.data.DataSet; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.interfaces.dataprovider.BarLineScatterCandleBubbleDataProvider; -import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.utils.ViewPortHandler; - -public abstract class BarLineScatterCandleBubbleRenderer extends DataRenderer { - - /** - * buffer for storing the current minimum and maximum visible x - */ - protected XBounds xBounds = new XBounds(); - - public BarLineScatterCandleBubbleRenderer(ChartAnimator animator, ViewPortHandler viewPortHandler) { - super(animator, viewPortHandler); - } - - /** - * Returns true if the DataSet values should be drawn, false if not. - * - * @param set - * @return - */ - protected boolean shouldDrawValues(IDataSet set) { - return set.isVisible() && (set.isDrawValuesEnabled() || set.isDrawIconsEnabled()); - } - - /** - * Checks if the provided entry object is in bounds for drawing considering the current animation phase. - * - * @param e - * @param set - * @return - */ - protected boolean isInBoundsX(Entry e, IBarLineScatterCandleBubbleDataSet set) { - - if (e == null) - return false; - - float entryIndex = set.getEntryIndex(e); - - if (e == null || entryIndex >= set.getEntryCount() * animator.getPhaseX()) { - return false; - } else { - return true; - } - } - - /** - * Class representing the bounds of the current viewport in terms of indices in the values array of a DataSet. - */ - protected class XBounds { - - /** - * minimum visible entry index - */ - public int min; - - /** - * maximum visible entry index - */ - public int max; - - /** - * range of visible entry indices - */ - public int range; - - /** - * Calculates the minimum and maximum x values as well as the range between them. - * - * @param chart - * @param dataSet - */ - public void set(BarLineScatterCandleBubbleDataProvider chart, IBarLineScatterCandleBubbleDataSet dataSet) { - float phaseX = Math.max(0.f, Math.min(1.f, animator.getPhaseX())); - - float low = chart.getLowestVisibleX(); - float high = chart.getHighestVisibleX(); - - Entry entryFrom = dataSet.getEntryForXValue(low, Float.NaN, DataSet.Rounding.DOWN); - Entry entryTo = dataSet.getEntryForXValue(high, Float.NaN, DataSet.Rounding.UP); - - min = entryFrom == null ? 0 : dataSet.getEntryIndex(entryFrom); - max = entryTo == null ? 0 : dataSet.getEntryIndex(entryTo); - range = (int) ((max - min) * phaseX); - } - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarLineScatterCandleBubbleRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarLineScatterCandleBubbleRenderer.kt new file mode 100644 index 0000000000..fda168a3c9 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarLineScatterCandleBubbleRenderer.kt @@ -0,0 +1,87 @@ +package com.github.mikephil.charting.renderer + +import com.github.mikephil.charting.animation.ChartAnimator +import com.github.mikephil.charting.data.DataSet.Rounding +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.interfaces.dataprovider.BarLineScatterCandleBubbleDataProvider +import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet +import com.github.mikephil.charting.interfaces.datasets.IDataSet +import com.github.mikephil.charting.utils.ViewPortHandler +import kotlin.math.max +import kotlin.math.min + +abstract class BarLineScatterCandleBubbleRenderer(animator: ChartAnimator, viewPortHandler: ViewPortHandler) : DataRenderer(animator, viewPortHandler) { + /** + * buffer for storing the current minimum and maximum visible x + */ + protected var xBounds: XBounds = XBounds() + + /** + * Returns true if the DataSet values should be drawn, false if not. + * + * @param set + * @return + */ + protected fun shouldDrawValues(set: IDataSet<*>): Boolean { + return set.isVisible && (set.isDrawValuesEnabled || set.isDrawIconsEnabled) + } + + /** + * Checks if the provided entry object is in bounds for drawing considering the current animation phase. + * + * @param e + * @param set + * @return + */ + protected fun isInBoundsX(e: T?, set: IBarLineScatterCandleBubbleDataSet): Boolean { + if (e == null) return false + + val entryIndex = set.getEntryIndex(e).toFloat() + + return if (entryIndex >= set.entryCount * animator.phaseX) { + false + } else { + true + } + } + + /** + * Class representing the bounds of the current viewport in terms of indices in the values array of a DataSet. + */ + protected inner class XBounds { + /** + * minimum visible entry index + */ + var min: Int = 0 + + /** + * maximum visible entry index + */ + var max: Int = 0 + + /** + * range of visible entry indices + */ + var range: Int = 0 + + /** + * Calculates the minimum and maximum x values as well as the range between them. + * + * @param chart + * @param dataSet + */ + operator fun set(chart: BarLineScatterCandleBubbleDataProvider, dataSet: IBarLineScatterCandleBubbleDataSet) { + val phaseX = max(0f, min(1f, animator.phaseX)) + + val low = chart.lowestVisibleX + val high = chart.highestVisibleX + + val entryFrom = dataSet.getEntryForXValue(low, Float.Companion.NaN, Rounding.DOWN) + val entryTo = dataSet.getEntryForXValue(high, Float.Companion.NaN, Rounding.UP) + + min = if (entryFrom == null) 0 else dataSet.getEntryIndex(entryFrom) + max = if (entryTo == null) 0 else dataSet.getEntryIndex(entryTo) + range = ((max - min) * phaseX).toInt() + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BubbleChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BubbleChartRenderer.kt index 7f2dd4b7af..f17317a592 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BubbleChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BubbleChartRenderer.kt @@ -13,19 +13,20 @@ import com.github.mikephil.charting.utils.ViewPortHandler import kotlin.math.abs import kotlin.math.max import kotlin.math.min +import kotlin.math.roundToInt import kotlin.math.sqrt @Suppress("MemberVisibilityCanBePrivate") open class BubbleChartRenderer( @JvmField - var chart: BubbleDataProvider, animator: ChartAnimator?, - viewPortHandler: ViewPortHandler? + var chart: BubbleDataProvider, animator: ChartAnimator, + viewPortHandler: ViewPortHandler ) : BarLineScatterCandleBubbleRenderer(animator, viewPortHandler) { override fun initBuffers() { } override fun drawData(c: Canvas) { - val bubbleData = chart.bubbleData + val bubbleData = chart.bubbleData ?: return for (set in bubbleData.dataSets) { if (set.isVisible) drawDataSet(c, set) @@ -53,7 +54,7 @@ open class BubbleChartRenderer( sizeBuffer[0] = 0f sizeBuffer[2] = 1f - trans!!.pointValuesToPixel(sizeBuffer) + trans.pointValuesToPixel(sizeBuffer) val normalizeSize = dataSet.isNormalizeSizeEnabled @@ -112,7 +113,7 @@ open class BubbleChartRenderer( xBounds[chart] = dataSet - chart.getTransformer(dataSet.axisDependency)?.let { transformer -> + chart.getTransformer(dataSet.axisDependency).let { transformer -> val positions = transformer.generateTransformedValuesBubble(dataSet, phaseY, xBounds.min, xBounds.max) val alpha = if (phaseX == 1f) @@ -128,7 +129,7 @@ open class BubbleChartRenderer( while (j < positions.size) { var valueTextColor = dataSet.getValueTextColor(j / 2 + xBounds.min) valueTextColor = Color.argb( - Math.round(255f * alpha), Color.red(valueTextColor), + (255f * alpha).roundToInt(), Color.red(valueTextColor), Color.green(valueTextColor), Color.blue(valueTextColor) ) @@ -151,17 +152,19 @@ open class BubbleChartRenderer( ) } - if (entry.icon != null && dataSet.isDrawIconsEnabled) { + if (dataSet.isDrawIconsEnabled) { val icon = entry.icon - Utils.drawImage( - c, - icon, - (x + iconsOffset.x).toInt(), - (y + iconsOffset.y).toInt(), - icon!!.intrinsicWidth, - icon.intrinsicHeight - ) + icon?.let { + Utils.drawImage( + c, + icon, + (x + iconsOffset.x).toInt(), + (y + iconsOffset.y).toInt(), + icon.intrinsicWidth, + icon.intrinsicHeight + ) + } } j += 2 } @@ -185,16 +188,16 @@ open class BubbleChartRenderer( } override fun drawHighlighted(c: Canvas, indices: Array) { - val bubbleData = chart.bubbleData + val bubbleData = chart.bubbleData ?: return val phaseY = animator.phaseY for (high in indices) { val set = bubbleData.getDataSetByIndex(high.dataSetIndex) - if (set == null || !set.isHighlightEnabled) continue + if (!set.isHighlightEnabled) continue - val entry = set.getEntryForXValue(high.x, high.y) + val entry = set.getEntryForXValue(high.x, high.y) ?: continue if (entry.y != high.y) continue @@ -205,7 +208,7 @@ open class BubbleChartRenderer( sizeBuffer[0] = 0f sizeBuffer[2] = 1f - trans!!.pointValuesToPixel(sizeBuffer) + trans.pointValuesToPixel(sizeBuffer) val normalizeSize = set.isNormalizeSizeEnabled diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CandleStickChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CandleStickChartRenderer.kt index 15618802ed..e9af97ebe8 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CandleStickChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CandleStickChartRenderer.kt @@ -13,8 +13,8 @@ import com.github.mikephil.charting.utils.ViewPortHandler open class CandleStickChartRenderer( @JvmField - var chart: CandleDataProvider, animator: ChartAnimator?, - viewPortHandler: ViewPortHandler? + var chart: CandleDataProvider, animator: ChartAnimator, + viewPortHandler: ViewPortHandler ) : LineScatterCandleRadarRenderer(animator, viewPortHandler) { private val shadowBuffers = FloatArray(8) private val bodyBuffers = FloatArray(4) @@ -25,7 +25,7 @@ open class CandleStickChartRenderer( override fun initBuffers() = Unit override fun drawData(c: Canvas) { - val candleData = chart.candleData + val candleData = chart.candleData ?: return for (set in candleData.dataSets) { if (set.isVisible) drawDataSet(c, set) @@ -47,7 +47,7 @@ open class CandleStickChartRenderer( for (j in xBounds.min..xBounds.range + xBounds.min) { // get the entry - val e = dataSet.getEntryForIndex(j) ?: continue + val e = dataSet.getEntryForIndex(j) val xPos = e.x @@ -80,7 +80,7 @@ open class CandleStickChartRenderer( shadowBuffers[7] = shadowBuffers[3] } - trans!!.pointValuesToPixel(shadowBuffers) + trans.pointValuesToPixel(shadowBuffers) // draw the shadows if (dataSet.shadowColorSameAsCandle) { @@ -165,7 +165,7 @@ open class CandleStickChartRenderer( closeBuffers[2] = xPos closeBuffers[3] = close * phaseY - trans!!.pointValuesToPixel(rangeBuffers) + trans.pointValuesToPixel(rangeBuffers) trans.pointValuesToPixel(openBuffers) trans.pointValuesToPixel(closeBuffers) @@ -206,7 +206,7 @@ open class CandleStickChartRenderer( override fun drawValues(c: Canvas) { // if values are drawn if (isDrawingValuesAllowed(chart)) { - val dataSets = chart.candleData.dataSets + val dataSets = chart.candleData?.dataSets ?: return for (i in dataSets.indices) { val dataSet = dataSets[i] @@ -224,7 +224,7 @@ open class CandleStickChartRenderer( xBounds[chart] = dataSet - val positions = trans!!.generateTransformedValuesCandle( + val positions = trans.generateTransformedValuesCandle( dataSet, animator.phaseX, animator.phaseY, xBounds.min, xBounds.max ) @@ -285,14 +285,14 @@ open class CandleStickChartRenderer( override fun drawExtras(c: Canvas) = Unit override fun drawHighlighted(c: Canvas, indices: Array) { - val candleData = chart.candleData + val candleData = chart.candleData ?: return for (high in indices) { val set = candleData.getDataSetByIndex(high.dataSetIndex) - if (set == null || !set.isHighlightEnabled) continue + if (!set.isHighlightEnabled) continue - val e = set.getEntryForXValue(high.x, high.y) + val e = set.getEntryForXValue(high.x, high.y) ?: continue if (!isInBoundsX(e, set)) continue @@ -300,7 +300,7 @@ open class CandleStickChartRenderer( val highValue = e.high * animator.phaseY val y = (lowValue + highValue) / 2f - val pix = chart.getTransformer(set.axisDependency)!!.getPixelForValues(e.x, y) + val pix = chart.getTransformer(set.axisDependency).getPixelForValues(e.x, y) high.setDraw(pix.x.toFloat(), pix.y.toFloat()) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CombinedChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CombinedChartRenderer.kt index 18b80e47fa..420c0ee452 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CombinedChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CombinedChartRenderer.kt @@ -21,7 +21,7 @@ open class CombinedChartRenderer(chart: CombinedChart, animator: ChartAnimator, */ protected var dataRenderers: MutableList = ArrayList(5) - protected var weakChart: WeakReference> = WeakReference(chart) + protected var weakChart: WeakReference> = WeakReference(chart) /** * Creates the renderers needed for this combined-renderer in the required order. Also takes the DrawOrder into @@ -71,13 +71,15 @@ open class CombinedChartRenderer(chart: CombinedChart, animator: ChartAnimator, val chart = weakChart.get() ?: return for (renderer in dataRenderers) { - var data: ChartData<*>? = null - - if (renderer is BarChartRenderer) data = renderer.chart.barData - else if (renderer is LineChartRenderer) data = renderer.chart.lineData - else if (renderer is CandleStickChartRenderer) data = renderer.chart.candleData - else if (renderer is ScatterChartRenderer) data = renderer.chart.scatterData - else if (renderer is BubbleChartRenderer) data = renderer.chart.bubbleData + var data: ChartData<*, *>? = null + + when (renderer) { + is BarChartRenderer -> data = renderer.chart.barData + is LineChartRenderer -> data = renderer.chart.lineData + is CandleStickChartRenderer -> data = renderer.chart.candleData + is ScatterChartRenderer -> data = renderer.chart.scatterData + is BubbleChartRenderer -> data = renderer.chart.bubbleData + } val dataIndex = if (data == null) -1 diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.kt index 0b2ec45a2e..c0a8f04325 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.kt @@ -22,18 +22,19 @@ import kotlin.math.min */ @Suppress("MemberVisibilityCanBePrivate") open class HorizontalBarChartRenderer( - chart: BarDataProvider, animator: ChartAnimator?, - viewPortHandler: ViewPortHandler? + chart: BarDataProvider, animator: ChartAnimator, + viewPortHandler: ViewPortHandler ) : BarChartRenderer(chart, animator, viewPortHandler) { override fun initBuffers() { - val barData = chart.barData - barBuffers = arrayOfNulls(barData.dataSetCount).toMutableList() - - for (i in barBuffers.indices) { + barBuffers.clear() + val barData = chart.barData ?: return + for (i in 0 until barData.dataSetCount) { val set = barData.getDataSetByIndex(i) - barBuffers[i] = HorizontalBarBuffer( - set.entryCount * 4 * (if (set.isStacked) set.stackSize else 1), - barData.dataSetCount, set.isStacked + barBuffers.add( + HorizontalBarBuffer( + set.entryCount * 4 * (if (set.isStacked) set.stackSize else 1), + barData.dataSetCount, set.isStacked + ) ) } } @@ -55,20 +56,19 @@ open class HorizontalBarChartRenderer( val phaseX = animator.phaseX val phaseY = animator.phaseY + val barData = chart.barData ?: return + // draw the bar shadow before the values if (chart.isDrawBarShadowEnabled) { shadowPaint.color = dataSet.barShadowColor - val barData = chart.barData - val barWidth = barData.barWidth val barWidthHalf = barWidth / 2.0f var x: Float var i = 0 val count = min((ceil(((dataSet.entryCount).toFloat() * phaseX).toDouble())).toInt().toDouble(), dataSet.entryCount.toDouble()).toInt() - while (i < count - ) { + while (i < count) { val e = dataSet.getEntryForIndex(i) x = e.x @@ -76,7 +76,7 @@ open class HorizontalBarChartRenderer( mBarShadowRectBuffer.top = x - barWidthHalf mBarShadowRectBuffer.bottom = x + barWidthHalf - trans!!.rectValueToPixel(mBarShadowRectBuffer) + trans.rectValueToPixel(mBarShadowRectBuffer) if (!viewPortHandler.isInBoundsTop(mBarShadowRectBuffer.bottom)) { i++ @@ -96,17 +96,17 @@ open class HorizontalBarChartRenderer( } // initialize the buffer - val buffer = barBuffers[index]!! + val buffer = barBuffers[index] buffer.setPhases(phaseX, phaseY) buffer.setDataSet(index) buffer.setInverted(chart.isInverted(dataSet.axisDependency)) - buffer.setBarWidth(chart.barData.barWidth) + buffer.setBarWidth(barData.barWidth) buffer.feed(dataSet) - trans!!.pointValuesToPixel(buffer.buffer) + trans.pointValuesToPixel(buffer.buffer) - val isCustomFill = dataSet.fills != null && dataSet.fills.isNotEmpty() + val isCustomFill = dataSet.fills.isNotEmpty() val isSingleColor = dataSet.colors.size == 1 val isInverted = chart.isInverted(dataSet.axisDependency) @@ -134,16 +134,15 @@ open class HorizontalBarChartRenderer( } if (isCustomFill) { - dataSet.getFill(pos) - .fillRect( - c, paintRender, - buffer.buffer[j], - buffer.buffer[j + 1], - buffer.buffer[j + 2], - buffer.buffer[j + 3], - if (isInverted) Fill.Direction.LEFT else Fill.Direction.RIGHT, - 0f - ) + dataSet.getFill(pos).fillRect( + c, paintRender, + buffer.buffer[j], + buffer.buffer[j + 1], + buffer.buffer[j + 2], + buffer.buffer[j + 3], + if (isInverted) Fill.Direction.LEFT else Fill.Direction.RIGHT, + 0f + ) } else { c.drawRect( buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], @@ -165,14 +164,15 @@ open class HorizontalBarChartRenderer( override fun drawValues(c: Canvas) { // if values are drawn if (isDrawingValuesAllowed(chart)) { - val dataSets = chart.barData.dataSets + val barData = chart.barData ?: return + val dataSets = barData.dataSets val valueOffsetPlus = Utils.convertDpToPixel(5f) var posOffset: Float var negOffset: Float val drawValueAboveBar = chart.isDrawValueAboveBarEnabled - for (i in 0..) { + fun computeLegend(data: ChartData<*, *>) { if (!legend.isLegendCustom) { computedEntries.clear() // loop for building up the colors and labels used in the legend for (i in 0.. { @@ -316,11 +310,15 @@ open class LegendRenderer( if (!isStacked) { if (drawingForm) posX += if (direction == LegendDirection.RIGHT_TO_LEFT) -formToTextSpace else formToTextSpace - if (direction == LegendDirection.RIGHT_TO_LEFT) posX -= calculatedLabelSizes[i].width + calculatedLabelSizes[i].let { + if (direction == LegendDirection.RIGHT_TO_LEFT) posX -= it.width + } drawLabel(c, posX, posY + labelLineHeight, e.label) - if (direction == LegendDirection.LEFT_TO_RIGHT) posX += calculatedLabelSizes[i].width + calculatedLabelSizes[i].let { + if (direction == LegendDirection.LEFT_TO_RIGHT) posX += it.width + } posX += if (direction == LegendDirection.RIGHT_TO_LEFT) -xEntrySpace else xEntrySpace } else posX += if (direction == LegendDirection.RIGHT_TO_LEFT) -stackSpace else stackSpace @@ -332,7 +330,7 @@ open class LegendRenderer( // contains the stacked legend size in pixels var stack = 0f var wasStacked = false - var posY = 0f + var posY: Float when (verticalAlignment) { LegendVerticalAlignment.TOP -> { @@ -479,6 +477,8 @@ open class LegendRenderer( mLineFormPath.lineTo(x + formSize, y) c.drawPath(mLineFormPath, formPaint) } + + else -> {} } } @@ -492,7 +492,9 @@ open class LegendRenderer( * @param y * @param label the label to draw */ - protected fun drawLabel(c: Canvas, x: Float, y: Float, label: String) { - c.drawText(label, x, y, labelPaint) + protected fun drawLabel(c: Canvas, x: Float, y: Float, label: String?) { + label?.let { + c.drawText(label, x, y, labelPaint) + } } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineChartRenderer.kt index 195cb7ea4b..8aea1683bd 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineChartRenderer.kt @@ -22,9 +22,9 @@ import java.lang.ref.WeakReference import kotlin.math.max import kotlin.math.min -class LineChartRenderer( - @JvmField var chart: LineDataProvider, animator: ChartAnimator?, - viewPortHandler: ViewPortHandler? +open class LineChartRenderer( + @JvmField var chart: LineDataProvider, animator: ChartAnimator, + viewPortHandler: ViewPortHandler ) : LineRadarRenderer(animator, viewPortHandler) { /** * paint for the inner circle of the value indicators @@ -59,7 +59,7 @@ class LineChartRenderer( val width = viewPortHandler.chartWidth.toInt() val height = viewPortHandler.chartHeight.toInt() - var drawBitmapLocal = if (drawBitmap == null) null else drawBitmap!!.get() + var drawBitmapLocal = drawBitmap?.get() if (drawBitmapLocal == null || (drawBitmapLocal.width != width) || (drawBitmapLocal.height != height) @@ -74,7 +74,7 @@ class LineChartRenderer( drawBitmapLocal.eraseColor(Color.TRANSPARENT) - val lineData = chart.lineData + val lineData = chart.lineData ?: return for (set in lineData.dataSets) { if (set.isVisible) drawDataSet(c, set) @@ -135,16 +135,16 @@ class LineChartRenderer( cubicFillPath.reset() cubicFillPath.addPath(cubicPath) // create a new path, this is bad for performance - bitmapCanvas?.let { drawCubicFill(it, dataSet, cubicFillPath, trans!!, xBounds) } + bitmapCanvas?.let { drawCubicFill(it, dataSet, cubicFillPath, trans, xBounds) } } paintRender.color = dataSet.color paintRender.style = Paint.Style.STROKE - trans!!.pathValueToPixel(cubicPath) + trans.pathValueToPixel(cubicPath) - bitmapCanvas!!.drawPath(cubicPath, paintRender) + bitmapCanvas?.drawPath(cubicPath, paintRender) paintRender.setPathEffect(null) } @@ -179,8 +179,6 @@ class LineChartRenderer( var next = cur var nextIndex = -1 - if (cur == null) return - // let the spline start cubicPath.moveTo(cur.x, cur.y * phaseY) @@ -192,9 +190,9 @@ class LineChartRenderer( nextIndex = if (j + 1 < dataSet.entryCount) j + 1 else j next = dataSet.getEntryForIndex(nextIndex) - prevDx = (cur!!.x - prevPrev!!.x) * intensity + prevDx = (cur.x - prevPrev.x) * intensity prevDy = (cur.y - prevPrev.y) * intensity - curDx = (next.x - prev!!.x) * intensity + curDx = (next.x - prev.x) * intensity curDy = (next.y - prev.y) * intensity cubicPath.cubicTo( @@ -210,16 +208,16 @@ class LineChartRenderer( cubicFillPath.reset() cubicFillPath.addPath(cubicPath) - bitmapCanvas?.let { drawCubicFill(it, dataSet, cubicFillPath, trans!!, xBounds) } + bitmapCanvas?.let { drawCubicFill(it, dataSet, cubicFillPath, trans, xBounds) } } paintRender.color = dataSet.color paintRender.style = Paint.Style.STROKE - trans!!.pathValueToPixel(cubicPath) + trans.pathValueToPixel(cubicPath) - bitmapCanvas!!.drawPath(cubicPath, paintRender) + bitmapCanvas?.drawPath(cubicPath, paintRender) paintRender.setPathEffect(null) } @@ -267,7 +265,7 @@ class LineChartRenderer( // if drawing filled is enabled if (dataSet.isDrawFilledEnabled && entryCount > 0) { - drawLinearFill(c, dataSet, trans!!, xBounds) + drawLinearFill(c, dataSet, trans, xBounds) } // more than 1 color @@ -280,7 +278,7 @@ class LineChartRenderer( val max = xBounds.min + xBounds.range for (j in xBounds.min.. 0) { - trans!!.pointValuesToPixel(lineBuffer) + lineBuffer[j++] = e2.x + lineBuffer[j++] = e2.y * phaseY + } - val size = (max(((xBounds.range + 1) * pointsPerEntryPair).toDouble(), pointsPerEntryPair.toDouble()) * 2).toInt() + if (j > 0) { + trans.pointValuesToPixel(lineBuffer) - paintRender.color = dataSet.color + val size = (max(((xBounds.range + 1) * pointsPerEntryPair).toDouble(), pointsPerEntryPair.toDouble()) * 2).toInt() - canvas!!.drawLines(lineBuffer, 0, size, paintRender) - } + paintRender.color = dataSet.color + + canvas?.drawLines(lineBuffer, 0, size, paintRender) } } @@ -467,15 +459,13 @@ class LineChartRenderer( for (x in startIndex + 1..endIndex) { currentEntry = dataSet.getEntryForIndex(x) - if (currentEntry != null) { - if (isDrawSteppedEnabled) { - filled.lineTo(currentEntry.x, previousEntry.y * phaseY) - } + if (isDrawSteppedEnabled) { + filled.lineTo(currentEntry.x, previousEntry.y * phaseY) + } - filled.lineTo(currentEntry.x, currentEntry.y * phaseY) + filled.lineTo(currentEntry.x, currentEntry.y * phaseY) - previousEntry = currentEntry - } + previousEntry = currentEntry } // close up @@ -488,7 +478,7 @@ class LineChartRenderer( override fun drawValues(c: Canvas) { if (isDrawingValuesAllowed(chart)) { - val dataSets = chart.lineData.dataSets + val dataSets = chart.lineData?.dataSets ?: return for (i in dataSets.indices) { val dataSet = dataSets[i] @@ -511,7 +501,7 @@ class LineChartRenderer( xBounds[chart] = dataSet - val positions = trans!!.generateTransformedValuesLine( + val positions = trans.generateTransformedValuesLine( dataSet, animator.phaseX, animator .phaseY, xBounds.min, xBounds.max ) @@ -534,23 +524,23 @@ class LineChartRenderer( val entry = dataSet.getEntryForIndex(j / 2 + xBounds.min) - if (entry != null) { - if (dataSet.isDrawValuesEnabled) { - drawValue( - c, dataSet.valueFormatter, entry.y, entry, i, x, - y - valOffset, dataSet.getValueTextColor(j / 2) - ) - } + if (dataSet.isDrawValuesEnabled) { + drawValue( + c, dataSet.valueFormatter, entry.y, entry, i, x, + y - valOffset, dataSet.getValueTextColor(j / 2) + ) + } - if (entry.icon != null && dataSet.isDrawIconsEnabled) { - val icon = entry.icon + if (dataSet.isDrawIconsEnabled) { + val icon = entry.icon + icon?.let { Utils.drawImage( c, icon, (x + iconsOffset.x).toInt(), (y + iconsOffset.y).toInt(), - icon!!.intrinsicWidth, + icon.intrinsicWidth, icon.intrinsicHeight ) } @@ -590,7 +580,7 @@ class LineChartRenderer( mCirclesBuffer[0] = 0f mCirclesBuffer[1] = 0f - val dataSets = chart.lineData.dataSets + val dataSets = chart.lineData?.dataSets ?: return for (i in dataSets.indices) { val dataSet = dataSets[i] @@ -609,16 +599,9 @@ class LineChartRenderer( val drawTransparentCircleHole = drawCircleHole && dataSet.circleHoleColor == ColorTemplate.COLOR_NONE - val imageCache: DataSetImageCache? - - if (mImageCaches.containsKey(dataSet)) { - imageCache = mImageCaches[dataSet] - } else { - imageCache = DataSetImageCache() - mImageCaches[dataSet] = imageCache - } + val imageCache: DataSetImageCache = mImageCaches.getOrPut(dataSet) { DataSetImageCache() } - val changeRequired = imageCache!!.init(dataSet) + val changeRequired = imageCache.init(dataSet) // only fill the cache with new bitmaps if a change is required if (changeRequired) { @@ -628,12 +611,12 @@ class LineChartRenderer( val boundsRangeCount = xBounds.range + xBounds.min for (j in xBounds.min..boundsRangeCount) { - val e = dataSet.getEntryForIndex(j) ?: break + val e = dataSet.getEntryForIndex(j) mCirclesBuffer[0] = e.x mCirclesBuffer[1] = e.y * phaseY - trans!!.pointValuesToPixel(mCirclesBuffer) + trans.pointValuesToPixel(mCirclesBuffer) if (!viewPortHandler.isInBoundsRight(mCirclesBuffer[0])) break @@ -654,15 +637,15 @@ class LineChartRenderer( val lineData = chart.lineData for (high in indices) { - val set = lineData.getDataSetByIndex(high.dataSetIndex) + val set = lineData?.getDataSetByIndex(high.dataSetIndex) if (set == null || !set.isHighlightEnabled) continue val e = set.getEntryForXValue(high.x, high.y) - if (!isInBoundsX(e, set)) continue + if (!isInBoundsX(e, set) || e == null) continue - val pix = chart.getTransformer(set.axisDependency)!!.getPixelForValues( + val pix = chart.getTransformer(set.axisDependency).getPixelForValues( e.x, e.y * animator .phaseY ) @@ -710,7 +693,7 @@ class LineChartRenderer( private inner class DataSetImageCache { private val mCirclePathBuffer = Path() - private var circleBitmaps: Array? = null + private val circleBitmaps: MutableList = mutableListOf() /** * Sets up the cache, returns true if a change of cache was required. @@ -722,11 +705,8 @@ class LineChartRenderer( val size = set.circleColorCount var changeRequired = false - if (circleBitmaps == null) { - circleBitmaps = arrayOfNulls(size) - changeRequired = true - } else if (circleBitmaps!!.size != size) { - circleBitmaps = arrayOfNulls(size) + if (circleBitmaps.size != size) { + circleBitmaps.clear() changeRequired = true } @@ -745,12 +725,14 @@ class LineChartRenderer( val circleRadius = set.circleRadius val circleHoleRadius = set.circleHoleRadius + circleBitmaps.clear() + for (i in 0..= 18 + return Utils.sDKInt >= 18 } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineScatterCandleRadarRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineScatterCandleRadarRenderer.kt index 5616e3a8b2..5ea5cf8f04 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineScatterCandleRadarRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/LineScatterCandleRadarRenderer.kt @@ -6,7 +6,7 @@ import com.github.mikephil.charting.animation.ChartAnimator import com.github.mikephil.charting.interfaces.datasets.ILineScatterCandleRadarDataSet import com.github.mikephil.charting.utils.ViewPortHandler -abstract class LineScatterCandleRadarRenderer(animator: ChartAnimator?, viewPortHandler: ViewPortHandler?) : +abstract class LineScatterCandleRadarRenderer(animator: ChartAnimator, viewPortHandler: ViewPortHandler) : BarLineScatterCandleBubbleRenderer(animator, viewPortHandler) { /** * path that is used for drawing highlight-lines (drawLines(...) cannot be used because of dashes) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.kt index 5043bef5ff..ef0f7df61c 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.kt @@ -7,7 +7,6 @@ import android.graphics.Paint import android.graphics.Paint.Align import android.graphics.Path import android.graphics.RectF -import android.os.Build import android.text.Layout import android.text.StaticLayout import android.text.TextPaint @@ -31,6 +30,8 @@ import kotlin.math.sin import kotlin.math.sqrt import kotlin.math.tan import androidx.core.graphics.withSave +import com.github.mikephil.charting.data.PieData +import androidx.core.graphics.createBitmap open class PieChartRenderer( protected var chart: PieChart, animator: ChartAnimator, @@ -86,16 +87,13 @@ open class PieChartRenderer( val width = viewPortHandler.chartWidth.toInt() val height = viewPortHandler.chartHeight.toInt() - var drawBitmap = if (mDrawBitmap == null) - null - else - mDrawBitmap!!.get() + var drawBitmap = mDrawBitmap?.get() if (drawBitmap == null || (drawBitmap.width != width) || (drawBitmap.height != height) ) { if (width > 0 && height > 0) { - drawBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_4444) + drawBitmap = createBitmap(width, height, Bitmap.Config.ARGB_4444) mDrawBitmap = WeakReference(drawBitmap) bitmapCanvas = Canvas(drawBitmap) } else return @@ -103,9 +101,9 @@ open class PieChartRenderer( drawBitmap.eraseColor(Color.TRANSPARENT) - val pieData = chart.data + val pieData = chart.data ?: return - for (set in pieData!!.dataSets) { + for (set in pieData.dataSets) { if (set.isVisible && set.entryCount > 0) drawDataSet(c, set) } } @@ -164,7 +162,7 @@ open class PieChartRenderer( if (!dataSet.isAutomaticallyDisableSliceSpacingEnabled) return dataSet.sliceSpace val spaceSizeRatio = dataSet.sliceSpace / viewPortHandler.smallestContentExtension - val minValueRatio = dataSet.yMin / chart.data!!.yValueSum * 2 + val minValueRatio = dataSet.yMin / (chart.data as PieData).yValueSum * 2 val sliceSpace = if (spaceSizeRatio > minValueRatio) 0f else dataSet.sliceSpace @@ -349,11 +347,11 @@ open class PieChartRenderer( mPathBuffer.close() - bitmapCanvas!!.drawPath(mPathBuffer, paintRender) + bitmapCanvas?.drawPath(mPathBuffer, paintRender) // Draw rounded corner path with paint object slice with the given radius if (roundedCornerRadius > 0) { - bitmapCanvas!!.drawPath(mPathBuffer, roundedCornerPaint) + bitmapCanvas?.drawPath(mPathBuffer, roundedCornerPaint) } angle += sliceAngle * phaseX @@ -390,7 +388,7 @@ open class PieChartRenderer( val labelRadius = radius - labelRadiusOffset val data = chart.data - val dataSets = data!!.dataSets + val dataSets = data?.dataSets ?: return val yValueSum = data.yValueSum @@ -576,26 +574,28 @@ open class PieChartRenderer( if (j < data.entryCount && entryLabel != null) { drawEntryLabel(this, entryLabel, x, y + lineHeight / 2f) } - } else if (drawYInside) { + } else { drawValue(this, formatter, value, entry, 0, x, y + lineHeight / 2f, dataSet.getValueTextColor(j)) } } - if (entry.icon != null && dataSet.isDrawIconsEnabled) { + if (dataSet.isDrawIconsEnabled) { val icon = entry.icon - val x = (labelRadius + iconsOffset.y) * sliceXBase + center.x - var y = (labelRadius + iconsOffset.y) * sliceYBase + center.y - y += iconsOffset.x - - Utils.drawImage( - this, - icon, - x.toInt(), - y.toInt(), - icon!!.intrinsicWidth, - icon.intrinsicHeight - ) + icon?.let { + val x = (labelRadius + iconsOffset.y) * sliceXBase + center.x + var y = (labelRadius + iconsOffset.y) * sliceYBase + center.y + y += iconsOffset.x + + Utils.drawImage( + this, + icon, + x.toInt(), + y.toInt(), + icon.intrinsicWidth, + icon.intrinsicHeight + ) + } } xIndex++ @@ -621,7 +621,7 @@ open class PieChartRenderer( override fun drawExtras(c: Canvas) { drawHole(c) - c.drawBitmap(mDrawBitmap!!.get()!!, 0f, 0f, null) + mDrawBitmap?.get()?.let { c.drawBitmap(it, 0f, 0f, null) } drawCenterText(c) } @@ -639,7 +639,7 @@ open class PieChartRenderer( if (Color.alpha(paintHole.color) > 0) { // draw the hole-circle - bitmapCanvas!!.drawCircle( + bitmapCanvas?.drawCircle( center.x, center.y, holeRadius, paintHole ) @@ -658,7 +658,7 @@ open class PieChartRenderer( mHoleCirclePath.reset() mHoleCirclePath.addCircle(center.x, center.y, secondHoleRadius, Path.Direction.CW) mHoleCirclePath.addCircle(center.x, center.y, holeRadius, Path.Direction.CCW) - bitmapCanvas!!.drawPath(mHoleCirclePath, paintTransparentCircle) + bitmapCanvas?.drawPath(mHoleCirclePath, paintTransparentCircle) // reset alpha paintTransparentCircle.alpha = alpha @@ -722,20 +722,18 @@ open class PieChartRenderer( } //float layoutWidth = Utils.getStaticLayoutMaxWidth(mCenterTextLayout); - val layoutHeight = centerTextLayout!!.height.toFloat() + val layoutHeight = centerTextLayout?.height?.toFloat() ?: 0f - c.save() - if (Build.VERSION.SDK_INT >= 18) { + c.withSave { val path = mDrawCenterTextPathBuffer path.reset() path.addOval(holeRect, Path.Direction.CW) - c.clipPath(path) - } + clipPath(path) - c.translate(boundingRect.left, boundingRect.top + (boundingRect.height() - layoutHeight) / 2f) - centerTextLayout!!.draw(c) + translate(boundingRect.left, boundingRect.top + (boundingRect.height() - layoutHeight) / 2f) + centerTextLayout?.draw(this) - c.restore() + } MPPointF.recycleInstance(center) MPPointF.recycleInstance(offset) @@ -950,7 +948,7 @@ open class PieChartRenderer( mPathBuffer.close() - bitmapCanvas!!.drawPath(mPathBuffer, paintRender) + bitmapCanvas?.drawPath(mPathBuffer, paintRender) } MPPointF.recycleInstance(center) @@ -964,7 +962,7 @@ open class PieChartRenderer( protected fun drawRoundedSlices(c: Canvas?) { if (!chart.isDrawRoundedSlicesEnabled) return - val dataSet = chart.data!!.dataSet + val dataSet = (chart.data as PieData).dataSet if (!dataSet.isVisible) return @@ -997,7 +995,7 @@ open class PieChartRenderer( * sin(v) + center.y).toFloat() paintRender.color = dataSet.getColor(j) - bitmapCanvas!!.drawCircle(x, y, circleRadius, paintRender) + bitmapCanvas?.drawCircle(x, y, circleRadius, paintRender) } angle += sliceAngle * phaseX @@ -1009,12 +1007,10 @@ open class PieChartRenderer( * Releases the drawing bitmap. This should be called when . */ fun releaseBitmap() { - if (bitmapCanvas != null) { - bitmapCanvas!!.setBitmap(null) - bitmapCanvas = null - } + bitmapCanvas?.setBitmap(null) + bitmapCanvas = null if (mDrawBitmap != null) { - val drawBitmap = mDrawBitmap!!.get() + val drawBitmap = mDrawBitmap?.get() drawBitmap?.recycle() mDrawBitmap?.clear() mDrawBitmap = null diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RadarChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RadarChartRenderer.kt index 99bf27ac59..e74948e132 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RadarChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RadarChartRenderer.kt @@ -15,8 +15,8 @@ import com.github.mikephil.charting.utils.ViewPortHandler import androidx.core.graphics.withSave open class RadarChartRenderer( - protected var chart: RadarChart, animator: ChartAnimator?, - viewPortHandler: ViewPortHandler? + protected var chart: RadarChart, animator: ChartAnimator, + viewPortHandler: ViewPortHandler ) : LineRadarRenderer(animator, viewPortHandler) { var webPaint: Paint protected set @@ -31,9 +31,9 @@ open class RadarChartRenderer( override fun initBuffers() = Unit override fun drawData(c: Canvas) { - val radarData = chart.data + val radarData = chart.data ?: return - val mostEntries = radarData!!.maxEntryCountSet.entryCount + val mostEntries = radarData.maxEntryCountSet?.entryCount ?: return for (set in radarData.dataSets) { if (set.isVisible) { @@ -118,6 +118,7 @@ open class RadarChartRenderer( val phaseY = animator.phaseY val sliceangle = chart.sliceAngle + val chartData = chart.data ?: return // calculate the factor that is needed for transforming the value to // pixels @@ -129,8 +130,8 @@ open class RadarChartRenderer( val yoffset = Utils.convertDpToPixel(5f) - for (i in 0.. 1) { @@ -235,7 +235,7 @@ class RoundedBarChartRenderer(chart: BarDataProvider, animator: ChartAnimator?, RectF( buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3] - ), roundedNegativeDataSetRadius, roundedNegativeDataSetRadius, true, true, true, true + ), roundedNegativeDataSetRadius, roundedNegativeDataSetRadius, tl = true, tr = true, br = true, bl = true ) c.drawPath(path2, paintRender) } else if ((dataSet.getEntryForIndex(j / 4).y > 0 && roundedPositiveDataSetRadius > 0)) { @@ -243,7 +243,7 @@ class RoundedBarChartRenderer(chart: BarDataProvider, animator: ChartAnimator?, RectF( buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3] - ), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, true, true, true, true + ), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, tl = true, tr = true, br = true, bl = true ) c.drawPath(path2, paintRender) } else { @@ -261,15 +261,15 @@ class RoundedBarChartRenderer(chart: BarDataProvider, animator: ChartAnimator?, val barData = chart.barData for (high in indices) { - val set = barData.getDataSetByIndex(high.dataSetIndex) + val set = barData?.getDataSetByIndex(high.dataSetIndex) ?: continue - if (set == null || !set.isHighlightEnabled) { + if (!set.isHighlightEnabled) { continue } val e = set.getEntryForXValue(high.x, high.y) - if (!isInBoundsX(e, set)) { + if (!isInBoundsX(e, set) || e == null) { continue } @@ -298,7 +298,7 @@ class RoundedBarChartRenderer(chart: BarDataProvider, animator: ChartAnimator?, y2 = 0f } - prepareBarHighlight(e.x, y1, y2, barData.barWidth / 2f, trans!!) + prepareBarHighlight(e.x, y1, y2, barData.barWidth / 2f, trans) setHighlightDrawPos(high, barRect) @@ -306,7 +306,7 @@ class RoundedBarChartRenderer(chart: BarDataProvider, animator: ChartAnimator?, RectF( barRect.left, barRect.top, barRect.right, barRect.bottom - ), mRadius, mRadius, true, true, true, true + ), mRadius, mRadius, tl = true, tr = true, br = true, bl = true ) c.drawPath(path2, paintHighlight) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedHorizontalBarChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedHorizontalBarChartRenderer.kt index b169a2202f..ed5aa7e2c6 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedHorizontalBarChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RoundedHorizontalBarChartRenderer.kt @@ -14,7 +14,7 @@ import kotlin.math.min /** * @noinspection unused */ -class RoundedHorizontalBarChartRenderer(chart: BarDataProvider, animator: ChartAnimator?, viewPortHandler: ViewPortHandler?) : +class RoundedHorizontalBarChartRenderer(chart: BarDataProvider, animator: ChartAnimator, viewPortHandler: ViewPortHandler) : HorizontalBarChartRenderer(chart, animator, viewPortHandler) { private val mBarShadowRectBuffer = RectF() private var roundedShadowRadius = 0f @@ -49,7 +49,7 @@ class RoundedHorizontalBarChartRenderer(chart: BarDataProvider, animator: ChartA if (chart.isDrawBarShadowEnabled) { shadowPaint.color = dataSet.barShadowColor - val barData = chart.barData + val barData = chart.barData ?: return val barWidth = barData.barWidth val barWidthHalf = barWidth / 2.0f var x: Float @@ -60,7 +60,7 @@ class RoundedHorizontalBarChartRenderer(chart: BarDataProvider, animator: ChartA x = e.x mBarShadowRectBuffer.top = x - barWidthHalf mBarShadowRectBuffer.bottom = x + barWidthHalf - trans!!.rectValueToPixel(mBarShadowRectBuffer) + trans.rectValueToPixel(mBarShadowRectBuffer) if (!viewPortHandler.isInBoundsTop(mBarShadowRectBuffer.bottom)) { i++ continue @@ -80,13 +80,13 @@ class RoundedHorizontalBarChartRenderer(chart: BarDataProvider, animator: ChartA } } - val buffer = barBuffers[index]!! + val buffer = barBuffers[index] buffer.setPhases(phaseX, phaseY) buffer.setDataSet(index) buffer.setInverted(chart.isInverted(dataSet.axisDependency)) - buffer.setBarWidth(chart.barData.barWidth) + chart.barData?.barWidth?.let { buffer.setBarWidth(it) } buffer.feed(dataSet) - trans!!.pointValuesToPixel(buffer.buffer) + trans.pointValuesToPixel(buffer.buffer) // if multiple colors has been assigned to Bar Chart if (dataSet.colors.size > 1) { @@ -210,7 +210,7 @@ class RoundedHorizontalBarChartRenderer(chart: BarDataProvider, animator: ChartA RectF( buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3] - ), roundedNegativeDataSetRadius, roundedNegativeDataSetRadius, true, true, true, true + ), roundedNegativeDataSetRadius, roundedNegativeDataSetRadius, tl = true, tr = true, br = true, bl = true ) c.drawPath(path2, paintRender) } else if ((dataSet.getEntryForIndex(j / 4).y > 0 && roundedPositiveDataSetRadius > 0)) { @@ -218,7 +218,7 @@ class RoundedHorizontalBarChartRenderer(chart: BarDataProvider, animator: ChartA RectF( buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3] - ), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, true, true, true, true + ), roundedPositiveDataSetRadius, roundedPositiveDataSetRadius, tl = true, tr = true, br = true, bl = true ) c.drawPath(path2, paintRender) } else { diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/ScatterChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/ScatterChartRenderer.kt index 931dca35d0..19ddd599d7 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/ScatterChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/ScatterChartRenderer.kt @@ -12,13 +12,13 @@ import com.github.mikephil.charting.utils.ViewPortHandler import kotlin.math.ceil import kotlin.math.min -open class ScatterChartRenderer(@JvmField var chart: ScatterDataProvider, animator: ChartAnimator?, viewPortHandler: ViewPortHandler?) : +open class ScatterChartRenderer(@JvmField var chart: ScatterDataProvider, animator: ChartAnimator, viewPortHandler: ViewPortHandler) : LineScatterCandleRadarRenderer(animator, viewPortHandler) { override fun initBuffers() { } override fun drawData(c: Canvas) { - val scatterData = chart.scatterData + val scatterData = chart.scatterData ?: return for (set in scatterData.dataSets) { if (set.isVisible) drawDataSet(c, set) @@ -53,7 +53,7 @@ open class ScatterChartRenderer(@JvmField var chart: ScatterDataProvider, animat pixelBuffer[0] = e.x pixelBuffer[1] = e.y * phaseY - trans!!.pointValuesToPixel(pixelBuffer) + trans.pointValuesToPixel(pixelBuffer) if (!viewPortHandler.isInBoundsRight(pixelBuffer[0])) break @@ -74,9 +74,9 @@ open class ScatterChartRenderer(@JvmField var chart: ScatterDataProvider, animat // if values are drawn if (isDrawingValuesAllowed(chart)) { - val dataSets = chart.scatterData.dataSets + val dataSets = chart.scatterData?.dataSets ?: return - for (i in 0.. 10 && !viewPortHandler.isFullyZoomedOutX) { - val p1 = transformer!!.getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentTop()) - val p2 = transformer!!.getValuesByTouchPoint(viewPortHandler.contentRight(), viewPortHandler.contentTop()) + transformer?.let { + val p1 = it.getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentTop()) + val p2 = it.getValuesByTouchPoint(viewPortHandler.contentRight(), viewPortHandler.contentTop()) - if (inverted) { - minLocal = p2.x.toFloat() - maxLocal = p1.x.toFloat() - } else { - minLocal = p1.x.toFloat() - maxLocal = p2.x.toFloat() - } + if (inverted) { + minLocal = p2.x.toFloat() + maxLocal = p1.x.toFloat() + } else { + minLocal = p1.x.toFloat() + maxLocal = p2.x.toFloat() + } - MPPointD.recycleInstance(p1) - MPPointD.recycleInstance(p2) + MPPointD.recycleInstance(p1) + MPPointD.recycleInstance(p2) + } } computeAxisValues(minLocal, maxLocal) @@ -80,8 +83,8 @@ open class XAxisRenderer( ) - xAxis.mLabelWidth = Math.round(labelRotatedSize.width) - xAxis.mLabelHeight = Math.round(labelRotatedSize.height) + xAxis.mLabelWidth = labelRotatedSize.width.roundToInt() + xAxis.mLabelHeight = labelRotatedSize.height.roundToInt() FSize.recycleInstance(labelRotatedSize) FSize.recycleInstance(labelSize) @@ -157,7 +160,7 @@ open class XAxisRenderer( * * @param pos */ - protected open fun drawLabels(c: Canvas?, pos: Float, anchor: MPPointF?) { + protected open fun drawLabels(c: Canvas?, pos: Float, anchor: MPPointF) { val labelRotationAngleDegrees = xAxis.labelRotationAngle val centeringEnabled = xAxis.isCenterAxisLabelsEnabled @@ -184,7 +187,7 @@ open class XAxisRenderer( } } - transformer!!.pointValuesToPixel(positions) + transformer?.pointValuesToPixel(positions) var i = 0 while (i < positions.size) { @@ -219,7 +222,7 @@ open class XAxisRenderer( } } - protected fun drawLabel(c: Canvas?, formattedLabel: String?, x: Float, y: Float, anchor: MPPointF?, angleDegrees: Float) { + protected fun drawLabel(c: Canvas?, formattedLabel: String, x: Float, y: Float, anchor: MPPointF, angleDegrees: Float) { Utils.drawXAxisValue(c, formattedLabel, x, y, paintAxisLabels, anchor, angleDegrees) } @@ -228,7 +231,7 @@ open class XAxisRenderer( override fun renderGridLines(c: Canvas) { if (!xAxis.isDrawGridLinesEnabled || !xAxis.isEnabled) return - c.withClip(gridClippingRect!!) { + c.withClip(gridClippingRect) { if (axis.isShowSpecificPositions) { if (mRenderGridLinesBuffer.size != axis.specificPositions.size * 2) { mRenderGridLinesBuffer = FloatArray(xAxis.specificPositions.size * 2) @@ -254,7 +257,7 @@ open class XAxisRenderer( } } - transformer!!.pointValuesToPixel(positions) + transformer?.pointValuesToPixel(positions) setupGridPaint() @@ -273,7 +276,7 @@ open class XAxisRenderer( @JvmField protected var mGridClippingRect: RectF = RectF() - open val gridClippingRect: RectF? + open val gridClippingRect: RectF get() { mGridClippingRect.set(viewPortHandler.contentRect) mGridClippingRect.inset(-axis.gridLineWidth, 0f) @@ -339,7 +342,7 @@ open class XAxisRenderer( override fun renderLimitLines(c: Canvas) { val limitLines = xAxis.limitLines - if (limitLines == null || limitLines.size <= 0) return + if (limitLines.isEmpty()) return val position = mRenderLimitLinesBuffer position[0] = 0f @@ -358,7 +361,7 @@ open class XAxisRenderer( position[0] = l.limit position[1] = 0f - transformer!!.pointValuesToPixel(position) + transformer?.pointValuesToPixel(position) renderLimitLineLine(c, l, position) renderLimitLineLabel(c, l, position, 2f + l.yOffset) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/XAxisRendererHorizontalBarChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/XAxisRendererHorizontalBarChart.kt index b1b0cf124c..d28671341a 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/XAxisRendererHorizontalBarChart.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/XAxisRendererHorizontalBarChart.kt @@ -15,6 +15,7 @@ import com.github.mikephil.charting.utils.Transformer import com.github.mikephil.charting.utils.Utils import com.github.mikephil.charting.utils.ViewPortHandler import androidx.core.graphics.withSave +import kotlin.math.roundToInt @Suppress("MemberVisibilityCanBePrivate") open class XAxisRendererHorizontalBarChart( @@ -31,27 +32,29 @@ open class XAxisRendererHorizontalBarChart( var minLocal = min var maxLocal = max if (viewPortHandler.contentWidth() > 10 && !viewPortHandler.isFullyZoomedOutY) { - val p1 = transformer!!.getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentBottom()) - val p2 = transformer!!.getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentTop()) - - if (inverted) { - minLocal = p2.y.toFloat() - maxLocal = p1.y.toFloat() - } else { - minLocal = p1.y.toFloat() - maxLocal = p2.y.toFloat() - } + transformer?.let { + val p1 = it.getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentBottom()) + val p2 = it.getValuesByTouchPoint(viewPortHandler.contentLeft(), viewPortHandler.contentTop()) - MPPointD.recycleInstance(p1) - MPPointD.recycleInstance(p2) + if (inverted) { + minLocal = p2.y.toFloat() + maxLocal = p1.y.toFloat() + } else { + minLocal = p1.y.toFloat() + maxLocal = p2.y.toFloat() + } + + MPPointD.recycleInstance(p1) + MPPointD.recycleInstance(p2) + } } computeAxisValues(minLocal, maxLocal) } override fun computeSize() { - paintAxisLabels!!.setTypeface(xAxis.typeface) - paintAxisLabels!!.textSize = xAxis.textSize + paintAxisLabels.setTypeface(xAxis.typeface) + paintAxisLabels.textSize = xAxis.textSize val longest = xAxis.longestLabel @@ -66,8 +69,8 @@ open class XAxisRendererHorizontalBarChart( xAxis.labelRotationAngle ) - xAxis.mLabelWidth = Math.round(labelRotatedSize.width) - xAxis.mLabelHeight = Math.round(labelRotatedSize.height) + xAxis.mLabelWidth = labelRotatedSize.width.roundToInt() + xAxis.mLabelHeight = labelRotatedSize.height.roundToInt() FSize.recycleInstance(labelRotatedSize) } @@ -77,9 +80,9 @@ open class XAxisRendererHorizontalBarChart( val xOffset = xAxis.xOffset - paintAxisLabels!!.setTypeface(xAxis.typeface) - paintAxisLabels!!.textSize = xAxis.textSize - paintAxisLabels!!.color = xAxis.textColor + paintAxisLabels.setTypeface(xAxis.typeface) + paintAxisLabels.textSize = xAxis.textSize + paintAxisLabels.color = xAxis.textColor val pointF = MPPointF.getInstance(0f, 0f) @@ -117,7 +120,7 @@ open class XAxisRendererHorizontalBarChart( MPPointF.recycleInstance(pointF) } - override fun drawLabels(c: Canvas?, pos: Float, anchor: MPPointF?) { + override fun drawLabels(c: Canvas?, pos: Float, anchor: MPPointF) { val labelRotationAngleDegrees = xAxis.labelRotationAngle val centeringEnabled = xAxis.isCenterAxisLabelsEnabled @@ -136,7 +139,7 @@ open class XAxisRendererHorizontalBarChart( } } - transformer!!.pointValuesToPixel(positions) + transformer?.pointValuesToPixel(positions) var i = 0 while (i < positions.size) { @@ -162,7 +165,7 @@ open class XAxisRendererHorizontalBarChart( gridLinePath.lineTo(viewPortHandler.contentLeft(), y) // draw a path because lines don't support dashing on lower android versions - c.drawPath(gridLinePath, paintGrid!!) + c.drawPath(gridLinePath, paintGrid) gridLinePath.reset() } @@ -170,14 +173,14 @@ open class XAxisRendererHorizontalBarChart( override fun renderAxisLine(c: Canvas) { if (!xAxis.isDrawAxisLineEnabled || !xAxis.isEnabled) return - paintAxisLine!!.color = xAxis.axisLineColor - paintAxisLine!!.strokeWidth = xAxis.axisLineWidth + paintAxisLine.color = xAxis.axisLineColor + paintAxisLine.strokeWidth = xAxis.axisLineWidth if (xAxis.position == XAxisPosition.TOP || xAxis.position == XAxisPosition.TOP_INSIDE || xAxis.position == XAxisPosition.BOTH_SIDED) { c.drawLine( viewPortHandler.contentRight(), viewPortHandler.contentTop(), viewPortHandler.contentRight(), - viewPortHandler.contentBottom(), paintAxisLine!! + viewPortHandler.contentBottom(), paintAxisLine ) } @@ -185,7 +188,7 @@ open class XAxisRendererHorizontalBarChart( c.drawLine( viewPortHandler.contentLeft(), viewPortHandler.contentTop(), viewPortHandler.contentLeft(), - viewPortHandler.contentBottom(), paintAxisLine!! + viewPortHandler.contentBottom(), paintAxisLine ) } } @@ -199,7 +202,7 @@ open class XAxisRendererHorizontalBarChart( override fun renderLimitLines(c: Canvas) { val limitLines = xAxis.limitLines - if (limitLines == null || limitLines.size <= 0) return + if (limitLines.isEmpty()) return val pts = mRenderLimitLinesBuffer pts[0] = 0f @@ -218,19 +221,19 @@ open class XAxisRendererHorizontalBarChart( mLimitLineClippingRect.inset(0f, -l.lineWidth) c.clipRect(mLimitLineClippingRect) - limitLinePaint!!.style = Paint.Style.STROKE - limitLinePaint!!.color = l.lineColor - limitLinePaint!!.strokeWidth = l.lineWidth - limitLinePaint!!.setPathEffect(l.dashPathEffect) + limitLinePaint.style = Paint.Style.STROKE + limitLinePaint.color = l.lineColor + limitLinePaint.strokeWidth = l.lineWidth + limitLinePaint.setPathEffect(l.dashPathEffect) pts[1] = l.limit - transformer!!.pointValuesToPixel(pts) + transformer?.pointValuesToPixel(pts) limitLinePath.moveTo(viewPortHandler.contentLeft(), pts[1]) limitLinePath.lineTo(viewPortHandler.contentRight(), pts[1]) - c.drawPath(limitLinePath, limitLinePaint!!) + c.drawPath(limitLinePath, limitLinePaint) limitLinePath.reset() // c.drawLines(pts, mLimitLinePaint); @@ -238,11 +241,11 @@ open class XAxisRendererHorizontalBarChart( // if drawing the limit-value label is enabled if (label != null && label != "") { - limitLinePaint!!.style = l.textStyle - limitLinePaint!!.setPathEffect(null) - limitLinePaint!!.color = l.textColor - limitLinePaint!!.strokeWidth = 0.5f - limitLinePaint!!.textSize = l.textSize + limitLinePaint.style = l.textStyle + limitLinePaint.setPathEffect(null) + limitLinePaint.color = l.textColor + limitLinePaint.strokeWidth = 0.5f + limitLinePaint.textSize = l.textSize val labelLineHeight = Utils.calcTextHeight(limitLinePaint, label).toFloat() val xOffset = Utils.convertDpToPixel(4f) + l.xOffset @@ -252,35 +255,35 @@ open class XAxisRendererHorizontalBarChart( when (position) { LimitLabelPosition.RIGHT_TOP -> { - limitLinePaint!!.textAlign = Align.RIGHT + limitLinePaint.textAlign = Align.RIGHT c.drawText( label, viewPortHandler.contentRight() - xOffset, - pts[1] - yOffset + labelLineHeight, limitLinePaint!! + pts[1] - yOffset + labelLineHeight, limitLinePaint ) } LimitLabelPosition.RIGHT_BOTTOM -> { - limitLinePaint!!.textAlign = Align.RIGHT + limitLinePaint.textAlign = Align.RIGHT c.drawText( label, viewPortHandler.contentRight() - xOffset, - pts[1] + yOffset, limitLinePaint!! + pts[1] + yOffset, limitLinePaint ) } LimitLabelPosition.LEFT_TOP -> { - limitLinePaint!!.textAlign = Align.LEFT + limitLinePaint.textAlign = Align.LEFT c.drawText( label, viewPortHandler.contentLeft() + xOffset, - pts[1] - yOffset + labelLineHeight, limitLinePaint!! + pts[1] - yOffset + labelLineHeight, limitLinePaint ) } else -> { - limitLinePaint!!.textAlign = Align.LEFT + limitLinePaint.textAlign = Align.LEFT c.drawText( label, viewPortHandler.offsetLeft() + xOffset, - pts[1] + yOffset, limitLinePaint!! + pts[1] + yOffset, limitLinePaint ) } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/XAxisRendererRadarChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/XAxisRendererRadarChart.kt index 98c5829aeb..9b89a5fb91 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/XAxisRendererRadarChart.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/XAxisRendererRadarChart.kt @@ -15,9 +15,9 @@ class XAxisRendererRadarChart(viewPortHandler: ViewPortHandler, xAxis: XAxis, pr val labelRotationAngleDegrees = xAxis.labelRotationAngle val drawLabelAnchor = MPPointF.getInstance(0.5f, 0.25f) - paintAxisLabels!!.setTypeface(xAxis.typeface) - paintAxisLabels!!.textSize = xAxis.textSize - paintAxisLabels!!.color = xAxis.textColor + paintAxisLabels.setTypeface(xAxis.typeface) + paintAxisLabels.textSize = xAxis.textSize + paintAxisLabels.color = xAxis.textColor val sliceAngle = chart.sliceAngle @@ -27,7 +27,7 @@ class XAxisRendererRadarChart(viewPortHandler: ViewPortHandler, xAxis: XAxis, pr val center = chart.centerOffsets val pOut = MPPointF.getInstance(0f, 0f) - for (i in 0.. 0) { + if (limitLines.isNotEmpty()) { val pts = renderLimitLinesBuffer pts[0] = 0f pts[1] = 0f @@ -319,34 +319,39 @@ open class YAxisRenderer(viewPortHandler: ViewPortHandler, @JvmField protected v val position = limitLine.labelPosition - if (position == LimitLabelPosition.RIGHT_TOP) { - limitLinePaint.textAlign = Align.RIGHT - c.drawText( - label, - viewPortHandler.contentRight() - xOffset, - pts[1] - yOffset + labelLineHeight, limitLinePaint - ) - } else if (position == LimitLabelPosition.RIGHT_BOTTOM) { - limitLinePaint.textAlign = Align.RIGHT - c.drawText( - label, - viewPortHandler.contentRight() - xOffset, - pts[1] + yOffset, limitLinePaint - ) - } else if (position == LimitLabelPosition.LEFT_TOP) { - limitLinePaint.textAlign = Align.LEFT - c.drawText( - label, - viewPortHandler.contentLeft() + xOffset, - pts[1] - yOffset + labelLineHeight, limitLinePaint - ) - } else { - limitLinePaint.textAlign = Align.LEFT - c.drawText( - label, - viewPortHandler.offsetLeft() + xOffset, - pts[1] + yOffset, limitLinePaint - ) + when (position) { + LimitLabelPosition.RIGHT_TOP -> { + limitLinePaint.textAlign = Align.RIGHT + c.drawText( + label, + viewPortHandler.contentRight() - xOffset, + pts[1] - yOffset + labelLineHeight, limitLinePaint + ) + } + LimitLabelPosition.RIGHT_BOTTOM -> { + limitLinePaint.textAlign = Align.RIGHT + c.drawText( + label, + viewPortHandler.contentRight() - xOffset, + pts[1] + yOffset, limitLinePaint + ) + } + LimitLabelPosition.LEFT_TOP -> { + limitLinePaint.textAlign = Align.LEFT + c.drawText( + label, + viewPortHandler.contentLeft() + xOffset, + pts[1] - yOffset + labelLineHeight, limitLinePaint + ) + } + else -> { + limitLinePaint.textAlign = Align.LEFT + c.drawText( + label, + viewPortHandler.offsetLeft() + xOffset, + pts[1] + yOffset, limitLinePaint + ) + } } } @@ -356,7 +361,7 @@ open class YAxisRenderer(viewPortHandler: ViewPortHandler, @JvmField protected v // Now the ranges val limitRanges = yAxis.limitRanges - if (limitRanges != null && limitRanges.size > 0) { + if (limitRanges.isNotEmpty()) { val ptsr = FloatArray(2) ptsr[0] = 0f ptsr[1] = 0f @@ -374,105 +379,105 @@ open class YAxisRenderer(viewPortHandler: ViewPortHandler, @JvmField protected v if (!limitRange.isEnabled) continue - val clipRestoreCount = c.save() - limitLineClippingRect.set(viewPortHandler.contentRect) - limitLineClippingRect.inset(0f, -limitRange.lineWidth) - c.clipRect(limitLineClippingRect) - - limitRangePaint.style = Paint.Style.STROKE - limitRangePaint.color = limitRange.lineColor - limitRangePaint.strokeWidth = limitRange.lineWidth - limitRangePaint.setPathEffect(limitRange.dashPathEffect) - - limitRangePaintFill.style = Paint.Style.FILL - limitRangePaintFill.color = limitRange.rangeColor - - ptsr[1] = limitRange.limit.high - ptsr2[1] = limitRange.limit.low - - transformer?.pointValuesToPixel(ptsr) - transformer?.pointValuesToPixel(ptsr2) - - limitRangePathFill.moveTo(viewPortHandler.contentLeft(), ptsr[1]) - limitRangePathFill.addRect( - viewPortHandler.contentLeft(), - ptsr[1], - viewPortHandler.contentRight(), - ptsr2[1], - Path.Direction.CW - ) - c.drawPath(limitRangePathFill, limitRangePaintFill) - limitRangePathFill.reset() - - if (limitRange.lineWidth > 0) { - limitRangePath.moveTo(viewPortHandler.contentLeft(), ptsr[1]) - limitRangePath.lineTo(viewPortHandler.contentRight(), ptsr[1]) - c.drawPath(limitRangePath, limitRangePaint) - - limitRangePath.moveTo(viewPortHandler.contentLeft(), ptsr2[1]) - limitRangePath.lineTo(viewPortHandler.contentRight(), ptsr2[1]) - c.drawPath(limitRangePath, limitRangePaint) - } + c.withSave { + limitLineClippingRect.set(viewPortHandler.contentRect) + limitLineClippingRect.inset(0f, -limitRange.lineWidth) + c.clipRect(limitLineClippingRect) - limitRangePath.reset() - - val label = limitRange.label - - // if drawing the limit-value label is enabled - if (label != null && label != "") { - limitRangePaint.style = limitRange.textStyle - limitRangePaint.setPathEffect(null) - limitRangePaint.color = limitRange.textColor - limitRangePaint.setTypeface(limitRange.typeface) - limitRangePaint.strokeWidth = 0.5f - limitRangePaint.textSize = limitRange.textSize - - val labelLineHeight = Utils.calcTextHeight(limitRangePaint, label).toFloat() - val xOffset = Utils.convertDpToPixel(4f) + limitRange.xOffset - val yOffset = limitRange.lineWidth + labelLineHeight + limitRange.yOffset - - val position = limitRange.labelPosition - - when (position) { - LimitLabelPosition.RIGHT_TOP -> { - limitRangePaint.textAlign = Align.RIGHT - c.drawText( - label, - viewPortHandler.contentRight() - xOffset, - ptsr[1] - yOffset + labelLineHeight, limitRangePaint - ) - } + limitRangePaint.style = Paint.Style.STROKE + limitRangePaint.color = limitRange.lineColor + limitRangePaint.strokeWidth = limitRange.lineWidth + limitRangePaint.setPathEffect(limitRange.dashPathEffect) + + limitRangePaintFill.style = Paint.Style.FILL + limitRangePaintFill.color = limitRange.rangeColor + + ptsr[1] = limitRange.limit.high + ptsr2[1] = limitRange.limit.low + + transformer?.pointValuesToPixel(ptsr) + transformer?.pointValuesToPixel(ptsr2) + + limitRangePathFill.moveTo(viewPortHandler.contentLeft(), ptsr[1]) + limitRangePathFill.addRect( + viewPortHandler.contentLeft(), + ptsr[1], + viewPortHandler.contentRight(), + ptsr2[1], + Path.Direction.CW + ) + c.drawPath(limitRangePathFill, limitRangePaintFill) + limitRangePathFill.reset() + + if (limitRange.lineWidth > 0) { + limitRangePath.moveTo(viewPortHandler.contentLeft(), ptsr[1]) + limitRangePath.lineTo(viewPortHandler.contentRight(), ptsr[1]) + c.drawPath(limitRangePath, limitRangePaint) + + limitRangePath.moveTo(viewPortHandler.contentLeft(), ptsr2[1]) + limitRangePath.lineTo(viewPortHandler.contentRight(), ptsr2[1]) + c.drawPath(limitRangePath, limitRangePaint) + } - LimitLabelPosition.RIGHT_BOTTOM -> { - limitRangePaint.textAlign = Align.RIGHT - c.drawText( - label, - viewPortHandler.contentRight() - xOffset, - ptsr[1] + yOffset, limitRangePaint - ) - } + limitRangePath.reset() - LimitLabelPosition.LEFT_TOP -> { - limitRangePaint.textAlign = Align.LEFT - c.drawText( - label, - viewPortHandler.contentLeft() + xOffset, - ptsr[1] - yOffset + labelLineHeight, limitRangePaint - ) - } + val label = limitRange.label - else -> { - limitRangePaint.textAlign = Align.LEFT - c.drawText( - label, - viewPortHandler.offsetLeft() + xOffset, - ptsr[1] + yOffset, limitRangePaint - ) + // if drawing the limit-value label is enabled + if (label != null && label != "") { + limitRangePaint.style = limitRange.textStyle + limitRangePaint.setPathEffect(null) + limitRangePaint.color = limitRange.textColor + limitRangePaint.setTypeface(limitRange.typeface) + limitRangePaint.strokeWidth = 0.5f + limitRangePaint.textSize = limitRange.textSize + + val labelLineHeight = Utils.calcTextHeight(limitRangePaint, label).toFloat() + val xOffset = Utils.convertDpToPixel(4f) + limitRange.xOffset + val yOffset = limitRange.lineWidth + labelLineHeight + limitRange.yOffset + + val position = limitRange.labelPosition + + when (position) { + LimitLabelPosition.RIGHT_TOP -> { + limitRangePaint.textAlign = Align.RIGHT + c.drawText( + label, + viewPortHandler.contentRight() - xOffset, + ptsr[1] - yOffset + labelLineHeight, limitRangePaint + ) + } + + LimitLabelPosition.RIGHT_BOTTOM -> { + limitRangePaint.textAlign = Align.RIGHT + c.drawText( + label, + viewPortHandler.contentRight() - xOffset, + ptsr[1] + yOffset, limitRangePaint + ) + } + + LimitLabelPosition.LEFT_TOP -> { + limitRangePaint.textAlign = Align.LEFT + c.drawText( + label, + viewPortHandler.contentLeft() + xOffset, + ptsr[1] - yOffset + labelLineHeight, limitRangePaint + ) + } + + else -> { + limitRangePaint.textAlign = Align.LEFT + c.drawText( + label, + viewPortHandler.offsetLeft() + xOffset, + ptsr[1] + yOffset, limitRangePaint + ) + } } } - } - c.restoreToCount(clipRestoreCount) + } } } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererHorizontalBarChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererHorizontalBarChart.kt index 89563449a2..78931dff30 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererHorizontalBarChart.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererHorizontalBarChart.kt @@ -220,7 +220,7 @@ open class YAxisRendererHorizontalBarChart( override fun renderLimitLines(c: Canvas) { val limitLines = yAxis.limitLines - if (limitLines == null || limitLines.size <= 0) return + if (limitLines.isEmpty()) return val pts = renderLimitLinesBuffer pts[0] = 0f diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererRadarChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererRadarChart.kt index e2a0afcbfd..df965e76e2 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererRadarChart.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/YAxisRendererRadarChart.kt @@ -137,9 +137,9 @@ class YAxisRendererRadarChart(viewPortHandler: ViewPortHandler, yAxis: YAxis, pr override fun renderAxisLabels(c: Canvas) { if (!yAxis.isEnabled || !yAxis.isDrawLabelsEnabled) return - paintAxisLabels!!.setTypeface(yAxis.typeface) - paintAxisLabels!!.textSize = yAxis.textSize - paintAxisLabels!!.color = yAxis.textColor + paintAxisLabels.setTypeface(yAxis.typeface) + paintAxisLabels.textSize = yAxis.textSize + paintAxisLabels.color = yAxis.textColor val center = chart.centerOffsets val pOut = MPPointF.getInstance(0f, 0f) @@ -160,14 +160,14 @@ class YAxisRendererRadarChart(viewPortHandler: ViewPortHandler, yAxis: YAxis, pr val label = yAxis.getFormattedLabel(j) - c.drawText(label, pOut.x + xOffset, pOut.y, paintAxisLabels!!) + c.drawText(label, pOut.x + xOffset, pOut.y, paintAxisLabels) } MPPointF.recycleInstance(center) MPPointF.recycleInstance(pOut) } override fun renderLimitLines(c: Canvas) { - val limitLines = yAxis.limitLines ?: return + val limitLines = yAxis.limitLines val sliceAngle = chart.sliceAngle @@ -182,9 +182,9 @@ class YAxisRendererRadarChart(viewPortHandler: ViewPortHandler, yAxis: YAxis, pr if (!l.isEnabled) continue - limitLinePaint!!.color = l.lineColor - limitLinePaint!!.setPathEffect(l.dashPathEffect) - limitLinePaint!!.strokeWidth = l.lineWidth + limitLinePaint.color = l.lineColor + limitLinePaint.setPathEffect(l.dashPathEffect) + limitLinePaint.strokeWidth = l.lineWidth val r = (l.limit - chart.yChartMin) * factor @@ -192,7 +192,7 @@ class YAxisRendererRadarChart(viewPortHandler: ViewPortHandler, yAxis: YAxis, pr limitPath.reset() - for (j in 0.. 0.0) { - renderPaint.setStyle(Paint.Style.STROKE); - renderPaint.setStrokeWidth(shapeStrokeSize); - - c.drawCircle( - posX, - posY, - shapeHoleSizeHalf + shapeStrokeSizeHalf, - renderPaint); - - if (shapeHoleColor != ColorTemplate.COLOR_NONE) { - renderPaint.setStyle(Paint.Style.FILL); - - renderPaint.setColor(shapeHoleColor); - c.drawCircle( - posX, - posY, - shapeHoleSizeHalf, - renderPaint); - } - } else { - renderPaint.setStyle(Paint.Style.FILL); - - c.drawCircle( - posX, - posY, - shapeHalf, - renderPaint); - } - - } - -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CircleShapeRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CircleShapeRenderer.kt new file mode 100644 index 0000000000..e08eac574e --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CircleShapeRenderer.kt @@ -0,0 +1,61 @@ +package com.github.mikephil.charting.renderer.scatter + +import android.graphics.Canvas +import android.graphics.Paint +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.Utils +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by wajdic on 15/06/2016. + * Created at Time 09:08 + */ +class CircleShapeRenderer : IShapeRenderer { + override fun renderShape( + c: Canvas?, dataSet: IScatterDataSet, viewPortHandler: ViewPortHandler, + posX: Float, posY: Float, renderPaint: Paint + ) { + val shapeSize = Utils.convertDpToPixel(dataSet.scatterShapeSize) + val shapeHalf = shapeSize / 2f + val shapeHoleSizeHalf = Utils.convertDpToPixel(dataSet.scatterShapeHoleRadius) + val shapeHoleSize = shapeHoleSizeHalf * 2f + val shapeStrokeSize = (shapeSize - shapeHoleSize) / 2f + val shapeStrokeSizeHalf = shapeStrokeSize / 2f + + val shapeHoleColor = dataSet.scatterShapeHoleColor + + if (shapeSize > 0.0) { + renderPaint.style = Paint.Style.STROKE + renderPaint.strokeWidth = shapeStrokeSize + + c?.drawCircle( + posX, + posY, + shapeHoleSizeHalf + shapeStrokeSizeHalf, + renderPaint + ) + + if (shapeHoleColor != ColorTemplate.COLOR_NONE) { + renderPaint.style = Paint.Style.FILL + + renderPaint.setColor(shapeHoleColor) + c?.drawCircle( + posX, + posY, + shapeHoleSizeHalf, + renderPaint + ) + } + } else { + renderPaint.style = Paint.Style.FILL + + c?.drawCircle( + posX, + posY, + shapeHalf, + renderPaint + ) + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CrossShapeRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CrossShapeRenderer.java deleted file mode 100644 index 2a62bffd82..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CrossShapeRenderer.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.github.mikephil.charting.renderer.scatter; - -import android.graphics.Canvas; -import android.graphics.Paint; - -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.utils.Utils; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by wajdic on 15/06/2016. - * Created at Time 09:08 - */ -public class CrossShapeRenderer implements IShapeRenderer -{ - - - @Override - public void renderShape(Canvas c, IScatterDataSet dataSet, ViewPortHandler viewPortHandler, - float posX, float posY, Paint renderPaint) { - - final float shapeHalf = Utils.convertDpToPixel(dataSet.getScatterShapeSize()) / 2f; - - renderPaint.setStyle(Paint.Style.STROKE); - renderPaint.setStrokeWidth(Utils.convertDpToPixel(1f)); - - c.drawLine( - posX - shapeHalf, - posY, - posX + shapeHalf, - posY, - renderPaint); - c.drawLine( - posX, - posY - shapeHalf, - posX, - posY + shapeHalf, - renderPaint); - - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CrossShapeRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CrossShapeRenderer.kt new file mode 100644 index 0000000000..676b0d9dcf --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/CrossShapeRenderer.kt @@ -0,0 +1,38 @@ +package com.github.mikephil.charting.renderer.scatter + +import android.graphics.Canvas +import android.graphics.Paint +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.utils.Utils +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by wajdic on 15/06/2016. + * Created at Time 09:08 + */ +class CrossShapeRenderer : IShapeRenderer { + override fun renderShape( + c: Canvas?, dataSet: IScatterDataSet, viewPortHandler: ViewPortHandler, + posX: Float, posY: Float, renderPaint: Paint + ) { + val shapeHalf = Utils.convertDpToPixel(dataSet.scatterShapeSize) / 2f + + renderPaint.style = Paint.Style.STROKE + renderPaint.strokeWidth = Utils.convertDpToPixel(1f) + + c?.drawLine( + posX - shapeHalf, + posY, + posX + shapeHalf, + posY, + renderPaint + ) + c?.drawLine( + posX, + posY - shapeHalf, + posX, + posY + shapeHalf, + renderPaint + ) + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/IShapeRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/IShapeRenderer.kt similarity index 62% rename from MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/IShapeRenderer.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/IShapeRenderer.kt index 20b57a900d..543d48d454 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/IShapeRenderer.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/IShapeRenderer.kt @@ -1,18 +1,15 @@ -package com.github.mikephil.charting.renderer.scatter; +package com.github.mikephil.charting.renderer.scatter -import android.graphics.Canvas; -import android.graphics.Paint; - -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.utils.ViewPortHandler; +import android.graphics.Canvas +import android.graphics.Paint +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.utils.ViewPortHandler /** * Created by wajdic on 15/06/2016. * Created at Time 09:07 */ -public interface IShapeRenderer -{ - +interface IShapeRenderer { /** * Renders the provided ScatterDataSet with a shape. * @@ -23,6 +20,8 @@ public interface IShapeRenderer * @param posY Position to draw the shape at * @param renderPaint Paint object used for styling and drawing */ - void renderShape(Canvas c, IScatterDataSet dataSet, ViewPortHandler viewPortHandler, - float posX, float posY, Paint renderPaint); + fun renderShape( + c: Canvas?, dataSet: IScatterDataSet, viewPortHandler: ViewPortHandler, + posX: Float, posY: Float, renderPaint: Paint, + ) } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/SquareShapeRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/SquareShapeRenderer.java deleted file mode 100644 index c6a4bf1350..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/SquareShapeRenderer.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.github.mikephil.charting.renderer.scatter; - -import android.graphics.Canvas; -import android.graphics.Paint; - -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.Utils; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by wajdic on 15/06/2016. - * Created at Time 09:08 - */ -public class SquareShapeRenderer implements IShapeRenderer -{ - - - @Override - public void renderShape(Canvas c, IScatterDataSet dataSet, ViewPortHandler viewPortHandler, - float posX, float posY, Paint renderPaint) { - - final float shapeSize = Utils.convertDpToPixel(dataSet.getScatterShapeSize()); - final float shapeHalf = shapeSize / 2f; - final float shapeHoleSizeHalf = Utils.convertDpToPixel(dataSet.getScatterShapeHoleRadius()); - final float shapeHoleSize = shapeHoleSizeHalf * 2.f; - final float shapeStrokeSize = (shapeSize - shapeHoleSize) / 2.f; - final float shapeStrokeSizeHalf = shapeStrokeSize / 2.f; - - final int shapeHoleColor = dataSet.getScatterShapeHoleColor(); - - if (shapeSize > 0.0) { - renderPaint.setStyle(Paint.Style.STROKE); - renderPaint.setStrokeWidth(shapeStrokeSize); - - c.drawRect(posX - shapeHoleSizeHalf - shapeStrokeSizeHalf, - posY - shapeHoleSizeHalf - shapeStrokeSizeHalf, - posX + shapeHoleSizeHalf + shapeStrokeSizeHalf, - posY + shapeHoleSizeHalf + shapeStrokeSizeHalf, - renderPaint); - - if (shapeHoleColor != ColorTemplate.COLOR_NONE) { - renderPaint.setStyle(Paint.Style.FILL); - - renderPaint.setColor(shapeHoleColor); - c.drawRect(posX - shapeHoleSizeHalf, - posY - shapeHoleSizeHalf, - posX + shapeHoleSizeHalf, - posY + shapeHoleSizeHalf, - renderPaint); - } - - } else { - renderPaint.setStyle(Paint.Style.FILL); - - c.drawRect(posX - shapeHalf, - posY - shapeHalf, - posX + shapeHalf, - posY + shapeHalf, - renderPaint); - } - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/SquareShapeRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/SquareShapeRenderer.kt new file mode 100644 index 0000000000..2e5734dcfb --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/SquareShapeRenderer.kt @@ -0,0 +1,64 @@ +package com.github.mikephil.charting.renderer.scatter + +import android.graphics.Canvas +import android.graphics.Paint +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.Utils +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by wajdic on 15/06/2016. + * Created at Time 09:08 + */ +class SquareShapeRenderer : IShapeRenderer { + override fun renderShape( + c: Canvas?, dataSet: IScatterDataSet, viewPortHandler: ViewPortHandler, + posX: Float, posY: Float, renderPaint: Paint + ) { + val shapeSize = Utils.convertDpToPixel(dataSet.scatterShapeSize) + val shapeHalf = shapeSize / 2f + val shapeHoleSizeHalf = Utils.convertDpToPixel(dataSet.scatterShapeHoleRadius) + val shapeHoleSize = shapeHoleSizeHalf * 2f + val shapeStrokeSize = (shapeSize - shapeHoleSize) / 2f + val shapeStrokeSizeHalf = shapeStrokeSize / 2f + + val shapeHoleColor = dataSet.scatterShapeHoleColor + + if (shapeSize > 0.0) { + renderPaint.style = Paint.Style.STROKE + renderPaint.strokeWidth = shapeStrokeSize + + c?.drawRect( + posX - shapeHoleSizeHalf - shapeStrokeSizeHalf, + posY - shapeHoleSizeHalf - shapeStrokeSizeHalf, + posX + shapeHoleSizeHalf + shapeStrokeSizeHalf, + posY + shapeHoleSizeHalf + shapeStrokeSizeHalf, + renderPaint + ) + + if (shapeHoleColor != ColorTemplate.COLOR_NONE) { + renderPaint.style = Paint.Style.FILL + + renderPaint.setColor(shapeHoleColor) + c?.drawRect( + posX - shapeHoleSizeHalf, + posY - shapeHoleSizeHalf, + posX + shapeHoleSizeHalf, + posY + shapeHoleSizeHalf, + renderPaint + ) + } + } else { + renderPaint.style = Paint.Style.FILL + + c?.drawRect( + posX - shapeHalf, + posY - shapeHalf, + posX + shapeHalf, + posY + shapeHalf, + renderPaint + ) + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/TriangleShapeRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/TriangleShapeRenderer.java deleted file mode 100644 index c355c0da17..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/TriangleShapeRenderer.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.github.mikephil.charting.renderer.scatter; - -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Path; - -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.Utils; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by wajdic on 15/06/2016. - * Created at Time 09:08 - */ -public class TriangleShapeRenderer implements IShapeRenderer -{ - - protected Path mTrianglePathBuffer = new Path(); - - @Override - public void renderShape(Canvas c, IScatterDataSet dataSet, ViewPortHandler viewPortHandler, - float posX, float posY, Paint renderPaint) { - - final float shapeSize = Utils.convertDpToPixel(dataSet.getScatterShapeSize()); - final float shapeHalf = shapeSize / 2f; - final float shapeHoleSizeHalf = Utils.convertDpToPixel(dataSet.getScatterShapeHoleRadius()); - final float shapeHoleSize = shapeHoleSizeHalf * 2.f; - final float shapeStrokeSize = (shapeSize - shapeHoleSize) / 2.f; - - final int shapeHoleColor = dataSet.getScatterShapeHoleColor(); - - renderPaint.setStyle(Paint.Style.FILL); - - // create a triangle path - Path tri = mTrianglePathBuffer; - tri.reset(); - - tri.moveTo(posX, posY - shapeHalf); - tri.lineTo(posX + shapeHalf, posY + shapeHalf); - tri.lineTo(posX - shapeHalf, posY + shapeHalf); - - if (shapeSize > 0.0) { - tri.lineTo(posX, posY - shapeHalf); - - tri.moveTo(posX - shapeHalf + shapeStrokeSize, - posY + shapeHalf - shapeStrokeSize); - tri.lineTo(posX + shapeHalf - shapeStrokeSize, - posY + shapeHalf - shapeStrokeSize); - tri.lineTo(posX, - posY - shapeHalf + shapeStrokeSize); - tri.lineTo(posX - shapeHalf + shapeStrokeSize, - posY + shapeHalf - shapeStrokeSize); - } - - tri.close(); - - c.drawPath(tri, renderPaint); - tri.reset(); - - if (shapeSize > 0.0 && - shapeHoleColor != ColorTemplate.COLOR_NONE) { - - renderPaint.setColor(shapeHoleColor); - - tri.moveTo(posX, - posY - shapeHalf + shapeStrokeSize); - tri.lineTo(posX + shapeHalf - shapeStrokeSize, - posY + shapeHalf - shapeStrokeSize); - tri.lineTo(posX - shapeHalf + shapeStrokeSize, - posY + shapeHalf - shapeStrokeSize); - tri.close(); - - c.drawPath(tri, renderPaint); - tri.reset(); - } - - } - -} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/TriangleShapeRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/TriangleShapeRenderer.kt new file mode 100644 index 0000000000..4e9fbce920 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/TriangleShapeRenderer.kt @@ -0,0 +1,89 @@ +package com.github.mikephil.charting.renderer.scatter + +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.Utils +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by wajdic on 15/06/2016. + * Created at Time 09:08 + */ +open class TriangleShapeRenderer : IShapeRenderer { + protected var mTrianglePathBuffer: Path = Path() + + override fun renderShape( + c: Canvas?, dataSet: IScatterDataSet, viewPortHandler: ViewPortHandler, + posX: Float, posY: Float, renderPaint: Paint + ) { + val shapeSize = Utils.convertDpToPixel(dataSet.scatterShapeSize) + val shapeHalf = shapeSize / 2f + val shapeHoleSizeHalf = Utils.convertDpToPixel(dataSet.scatterShapeHoleRadius) + val shapeHoleSize = shapeHoleSizeHalf * 2f + val shapeStrokeSize = (shapeSize - shapeHoleSize) / 2f + + val shapeHoleColor = dataSet.scatterShapeHoleColor + + renderPaint.style = Paint.Style.FILL + + // create a triangle path + val tri = mTrianglePathBuffer + tri.reset() + + tri.moveTo(posX, posY - shapeHalf) + tri.lineTo(posX + shapeHalf, posY + shapeHalf) + tri.lineTo(posX - shapeHalf, posY + shapeHalf) + + if (shapeSize > 0.0) { + tri.lineTo(posX, posY - shapeHalf) + + tri.moveTo( + posX - shapeHalf + shapeStrokeSize, + posY + shapeHalf - shapeStrokeSize + ) + tri.lineTo( + posX + shapeHalf - shapeStrokeSize, + posY + shapeHalf - shapeStrokeSize + ) + tri.lineTo( + posX, + posY - shapeHalf + shapeStrokeSize + ) + tri.lineTo( + posX - shapeHalf + shapeStrokeSize, + posY + shapeHalf - shapeStrokeSize + ) + } + + tri.close() + + c?.drawPath(tri, renderPaint) + tri.reset() + + if (shapeSize > 0.0 && + shapeHoleColor != ColorTemplate.COLOR_NONE + ) { + renderPaint.setColor(shapeHoleColor) + + tri.moveTo( + posX, + posY - shapeHalf + shapeStrokeSize + ) + tri.lineTo( + posX + shapeHalf - shapeStrokeSize, + posY + shapeHalf - shapeStrokeSize + ) + tri.lineTo( + posX - shapeHalf + shapeStrokeSize, + posY + shapeHalf - shapeStrokeSize + ) + tri.close() + + c?.drawPath(tri, renderPaint) + tri.reset() + } + } +} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/XShapeRenderer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/XShapeRenderer.java deleted file mode 100644 index f4d1a40736..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/XShapeRenderer.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.github.mikephil.charting.renderer.scatter; - -import android.graphics.Canvas; -import android.graphics.Paint; - -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.utils.Utils; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Created by wajdic on 15/06/2016. - * Created at Time 09:08 - */ -public class XShapeRenderer implements IShapeRenderer -{ - - - @Override - public void renderShape(Canvas c, IScatterDataSet dataSet, ViewPortHandler viewPortHandler, - float posX, float posY, Paint renderPaint) { - - final float shapeHalf = Utils.convertDpToPixel(dataSet.getScatterShapeSize()) / 2f; - - renderPaint.setStyle(Paint.Style.STROKE); - renderPaint.setStrokeWidth(Utils.convertDpToPixel(1f)); - - c.drawLine( - posX - shapeHalf, - posY - shapeHalf, - posX + shapeHalf, - posY + shapeHalf, - renderPaint); - c.drawLine( - posX + shapeHalf, - posY - shapeHalf, - posX - shapeHalf, - posY + shapeHalf, - renderPaint); - - } - -} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/XShapeRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/XShapeRenderer.kt new file mode 100644 index 0000000000..782fda1326 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/scatter/XShapeRenderer.kt @@ -0,0 +1,38 @@ +package com.github.mikephil.charting.renderer.scatter + +import android.graphics.Canvas +import android.graphics.Paint +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.utils.Utils +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Created by wajdic on 15/06/2016. + * Created at Time 09:08 + */ +class XShapeRenderer : IShapeRenderer { + override fun renderShape( + c: Canvas?, dataSet: IScatterDataSet, viewPortHandler: ViewPortHandler, + posX: Float, posY: Float, renderPaint: Paint + ) { + val shapeHalf = Utils.convertDpToPixel(dataSet.scatterShapeSize) / 2f + + renderPaint.style = Paint.Style.STROKE + renderPaint.strokeWidth = Utils.convertDpToPixel(1f) + + c?.drawLine( + posX - shapeHalf, + posY - shapeHalf, + posX + shapeHalf, + posY + shapeHalf, + renderPaint + ) + c?.drawLine( + posX + shapeHalf, + posY - shapeHalf, + posX - shapeHalf, + posY + shapeHalf, + renderPaint + ) + } +} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ColorTemplate.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ColorTemplate.java deleted file mode 100644 index 4d9c1de790..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ColorTemplate.java +++ /dev/null @@ -1,128 +0,0 @@ - -package com.github.mikephil.charting.utils; - -import android.content.res.Resources; -import android.graphics.Color; - -import java.util.ArrayList; -import java.util.List; - -/** - * Class that holds predefined color integer arrays (e.g. - * ColorTemplate.VORDIPLOM_COLORS) and convenience methods for loading colors - * from resources. - * - * @author Philipp Jahoda - */ -public class ColorTemplate { - - /** - * an "invalid" color that indicates that no color is set - */ - public static final int COLOR_NONE = 0x00112233; - - /** - * this "color" is used for the Legend creation and indicates that the next - * form should be skipped - */ - public static final int COLOR_SKIP = 0x00112234; - - /** - * THE COLOR THEMES ARE PREDEFINED (predefined color integer arrays), FEEL - * FREE TO CREATE YOUR OWN WITH AS MANY DIFFERENT COLORS AS YOU WANT - */ - public static final int[] LIBERTY_COLORS = { - Color.rgb(207, 248, 246), Color.rgb(148, 212, 212), Color.rgb(136, 180, 187), - Color.rgb(118, 174, 175), Color.rgb(42, 109, 130) - }; - public static final int[] JOYFUL_COLORS = { - Color.rgb(217, 80, 138), Color.rgb(254, 149, 7), Color.rgb(254, 247, 120), - Color.rgb(106, 167, 134), Color.rgb(53, 194, 209) - }; - public static final int[] PASTEL_COLORS = { - Color.rgb(64, 89, 128), Color.rgb(149, 165, 124), Color.rgb(217, 184, 162), - Color.rgb(191, 134, 134), Color.rgb(179, 48, 80) - }; - public static final int[] COLORFUL_COLORS = { - Color.rgb(193, 37, 82), Color.rgb(255, 102, 0), Color.rgb(245, 199, 0), - Color.rgb(106, 150, 31), Color.rgb(179, 100, 53) - }; - public static final int[] VORDIPLOM_COLORS = { - Color.rgb(192, 255, 140), Color.rgb(255, 247, 140), Color.rgb(255, 208, 140), - Color.rgb(140, 234, 255), Color.rgb(255, 140, 157) - }; - public static final int[] MATERIAL_COLORS = { - rgb("#2ecc71"), rgb("#f1c40f"), rgb("#e74c3c"), rgb("#3498db") - }; - - /** - * Converts the given hex-color-string to rgb. - * - * @param hex - * @return - */ - public static int rgb(String hex) { - int color = (int) Long.parseLong(hex.replace("#", ""), 16); - int r = (color >> 16) & 0xFF; - int g = (color >> 8) & 0xFF; - int b = (color >> 0) & 0xFF; - return Color.rgb(r, g, b); - } - - /** - * Returns the Android ICS holo blue light color. - * - * @return - */ - public static int getHoloBlue() { - return Color.rgb(51, 181, 229); - } - - /** - * Sets the alpha component of the given color. - * - * @param color - * @param alpha 0 - 255 - * @return - */ - public static int colorWithAlpha(int color, int alpha) { - return (color & 0xffffff) | ((alpha & 0xff) << 24); - } - - /** - * turn an array of resource-colors (contains resource-id integers) into an - * array list of actual color integers - * - * @param r - * @param colors an integer array of resource id's of colors - * @return - */ - public static List createColors(Resources r, int[] colors) { - - List result = new ArrayList(); - - for (int i : colors) { - result.add(r.getColor(i)); - } - - return result; - } - - /** - * Turns an array of colors (integer color values) into an ArrayList of - * colors. - * - * @param colors - * @return - */ - public static List createColors(int[] colors) { - - List result = new ArrayList(); - - for (int i : colors) { - result.add(i); - } - - return result; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ColorTemplate.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ColorTemplate.kt new file mode 100644 index 0000000000..bf14a16514 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ColorTemplate.kt @@ -0,0 +1,129 @@ +package com.github.mikephil.charting.utils + +import android.content.res.Resources +import android.graphics.Color +import androidx.core.content.res.ResourcesCompat + +/** + * Class that holds predefined color integer arrays (e.g. + * ColorTemplate.VORDIPLOM_COLORS) and convenience methods for loading colors + * from resources. + * + * @author Philipp Jahoda + */ +object ColorTemplate { + /** + * an "invalid" color that indicates that no color is set + */ + const val COLOR_NONE: Int = 0x00112233 + + /** + * this "color" is used for the Legend creation and indicates that the next + * form should be skipped + */ + const val COLOR_SKIP: Int = 0x00112234 + + /** + * THE COLOR THEMES ARE PREDEFINED (predefined color integer arrays), FEEL + * FREE TO CREATE YOUR OWN WITH AS MANY DIFFERENT COLORS AS YOU WANT + */ + @JvmField + val LIBERTY_COLORS: IntArray = intArrayOf( + Color.rgb(207, 248, 246), Color.rgb(148, 212, 212), Color.rgb(136, 180, 187), + Color.rgb(118, 174, 175), Color.rgb(42, 109, 130) + ) + @JvmField + val JOYFUL_COLORS: IntArray = intArrayOf( + Color.rgb(217, 80, 138), Color.rgb(254, 149, 7), Color.rgb(254, 247, 120), + Color.rgb(106, 167, 134), Color.rgb(53, 194, 209) + ) + @JvmField + val PASTEL_COLORS: IntArray = intArrayOf( + Color.rgb(64, 89, 128), Color.rgb(149, 165, 124), Color.rgb(217, 184, 162), + Color.rgb(191, 134, 134), Color.rgb(179, 48, 80) + ) + @JvmField + val COLORFUL_COLORS: IntArray = intArrayOf( + Color.rgb(193, 37, 82), Color.rgb(255, 102, 0), Color.rgb(245, 199, 0), + Color.rgb(106, 150, 31), Color.rgb(179, 100, 53) + ) + @JvmField + val VORDIPLOM_COLORS: IntArray = intArrayOf( + Color.rgb(192, 255, 140), Color.rgb(255, 247, 140), Color.rgb(255, 208, 140), + Color.rgb(140, 234, 255), Color.rgb(255, 140, 157) + ) + @JvmField + val MATERIAL_COLORS: IntArray = intArrayOf( + rgb("#2ecc71"), rgb("#f1c40f"), rgb("#e74c3c"), rgb("#3498db") + ) + + /** + * Converts the given hex-color-string to rgb. + * + * @param hex + * @return + */ + fun rgb(hex: String): Int { + val color = hex.replace("#", "").toLong(16).toInt() + val r = (color shr 16) and 0xFF + val g = (color shr 8) and 0xFF + val b = (color shr 0) and 0xFF + return Color.rgb(r, g, b) + } + + @JvmStatic + val holoBlue: Int + /** + * Returns the Android ICS holo blue light color. + * + * @return + */ + get() = Color.rgb(51, 181, 229) + + /** + * Sets the alpha component of the given color. + * + * @param color + * @param alpha 0 - 255 + * @return + */ + @JvmStatic + fun colorWithAlpha(color: Int, alpha: Int): Int { + return (color and 0xffffff) or ((alpha and 0xff) shl 24) + } + + /** + * turn an array of resource-colors (contains resource-id integers) into an + * array list of actual color integers + * + * @param r + * @param colors an integer array of resource id's of colors + * @return + */ + fun createColors(r: Resources, colors: IntArray): MutableList { + val result: MutableList = ArrayList() + + for (i in colors) { + result.add(ResourcesCompat.getColor(r, i, r.newTheme())) + } + + return result + } + + /** + * Turns an array of colors (integer color values) into an ArrayList of + * colors. + * + * @param colors + * @return + */ + fun createColors(colors: IntArray): MutableList { + val result: MutableList = ArrayList() + + for (i in colors) { + result.add(i) + } + + return result + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/EntryXComparator.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/EntryXComparator.java deleted file mode 100644 index 8f59c12d07..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/EntryXComparator.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.mikephil.charting.utils; - -import com.github.mikephil.charting.data.Entry; - -import java.util.Comparator; - -/** - * Comparator for comparing Entry-objects by their x-value. - * Created by philipp on 17/06/15. - */ -public class EntryXComparator implements Comparator { - @Override - public int compare(Entry entry1, Entry entry2) { - float diff = entry1.getX() - entry2.getX(); - - if (diff == 0f) return 0; - else { - if (diff > 0f) return 1; - else return -1; - } - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/EntryXComparator.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/EntryXComparator.kt new file mode 100644 index 0000000000..8f6e147a99 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/EntryXComparator.kt @@ -0,0 +1,19 @@ +package com.github.mikephil.charting.utils + +import com.github.mikephil.charting.data.Entry + +/** + * Comparator for comparing Entry-objects by their x-value. + * Created by philipp on 17/06/15. + */ +class EntryXComparator : Comparator { + override fun compare(entry1: Entry, entry2: Entry): Int { + val diff = entry1.x - entry2.x + + return if (diff == 0f) 0 + else { + if (diff > 0f) 1 + else -1 + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FSize.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FSize.java deleted file mode 100644 index a12bc45918..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FSize.java +++ /dev/null @@ -1,79 +0,0 @@ - -package com.github.mikephil.charting.utils; - -import java.util.List; - -/** - * Class for describing width and height dimensions in some arbitrary - * unit. Replacement for the android.Util.SizeF which is available only on API >= 21. - */ -public final class FSize extends ObjectPool.Poolable{ - - // TODO : Encapsulate width & height - - public float width; - public float height; - - private static ObjectPool pool; - - static { - pool = ObjectPool.create(256, new FSize(0,0)); - pool.setReplenishPercentage(0.5f); - } - - - protected ObjectPool.Poolable instantiate(){ - return new FSize(0,0); - } - - public static FSize getInstance(final float width, final float height){ - FSize result = pool.get(); - result.width = width; - result.height = height; - return result; - } - - public static void recycleInstance(FSize instance){ - pool.recycle(instance); - } - - public static void recycleInstances(List instances){ - pool.recycle(instances); - } - - public FSize() { - } - - public FSize(final float width, final float height) { - this.width = width; - this.height = height; - } - - @Override - public boolean equals(final Object obj) { - if (obj == null) { - return false; - } - if (this == obj) { - return true; - } - if (obj instanceof FSize) { - final FSize other = (FSize) obj; - return width == other.width && height == other.height; - } - return false; - } - - @Override - public String toString() { - return width + "x" + height; - } - - /** - * {@inheritDoc} - */ - @Override - public int hashCode() { - return Float.floatToIntBits(width) ^ Float.floatToIntBits(height); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FSize.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FSize.kt new file mode 100644 index 0000000000..1cfb52c626 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FSize.kt @@ -0,0 +1,76 @@ +package com.github.mikephil.charting.utils + +import com.github.mikephil.charting.utils.ObjectPool.Poolable +import kotlin.Any +import kotlin.Boolean +import kotlin.Int +import kotlin.String + +/** + * Class for describing width and height dimensions in some arbitrary + * unit. Replacement for the android.Util.SizeF which is available only on API >= 21. + */ +open class FSize : Poolable { + // TODO : Encapsulate width & height + var width: Float = 0f + var height: Float = 0f + + override fun instantiate(): FSize { + return FSize(0f, 0f) + } + + constructor() + + constructor(width: Float, height: Float) { + this.width = width + this.height = height + } + + override fun equals(other: Any?): Boolean { + if (other == null) { + return false + } + if (this === other) { + return true + } + if (other is FSize) { + return width == other.width && height == other.height + } + return false + } + + override fun toString(): String { + return width.toString() + "x" + height + } + + /** + * {@inheritDoc} + */ + override fun hashCode(): Int { + return width.toBits() xor height.toBits() + } + + companion object { + private val pool: ObjectPool = ObjectPool.create(256, FSize(0f, 0f)) + + init { + pool.replenishPercentage = 0.5f + } + + + fun getInstance(width: Float, height: Float): FSize { + val result: FSize = pool.get() + result.width = width + result.height = height + return result + } + + fun recycleInstance(instance: FSize) { + pool.recycle(instance) + } + + fun recycleInstances(instances: MutableList) { + pool.recycle(instances) + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FileUtils.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FileUtils.kt similarity index 52% rename from MPChartLib/src/main/java/com/github/mikephil/charting/utils/FileUtils.java rename to MPChartLib/src/main/java/com/github/mikephil/charting/utils/FileUtils.kt index 5aff51ff84..485e1df033 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FileUtils.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/FileUtils.kt @@ -1,74 +1,65 @@ - -package com.github.mikephil.charting.utils; - -import android.content.res.AssetManager; -import android.os.Environment; -import android.util.Log; - -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; - -import java.io.BufferedReader; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStreamReader; -import java.util.ArrayList; -import java.util.List; +package com.github.mikephil.charting.utils + +import android.content.res.AssetManager +import android.os.Environment +import android.util.Log +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import java.io.BufferedReader +import java.io.BufferedWriter +import java.io.File +import java.io.FileReader +import java.io.FileWriter +import java.io.IOException +import java.io.InputStreamReader /** * Utilities class for interacting with the assets and the devices storage to * load and save DataSet objects from and to .txt files. - * + * * @author Philipp Jahoda */ -public class FileUtils { - - private static final String LOG = "MPChart-FileUtils"; +object FileUtils { + private const val LOG = "MPChart-FileUtils" /** * Loads a an Array of Entries from a textfile from the sd-card. - * + * * @param path the name of the file on the sd-card (+ path if needed) * @return */ - public static List loadEntriesFromFile(String path) { - - File sdcard = Environment.getExternalStorageDirectory(); + fun loadEntriesFromFile(path: String): MutableList { + val sdcard = Environment.getExternalStorageDirectory() // Get the text file - File file = new File(sdcard, path); + val file = File(sdcard, path) - List entries = new ArrayList(); + val entries: MutableList = ArrayList() try { - @SuppressWarnings("resource") - BufferedReader br = new BufferedReader(new FileReader(file)); - String line; + val br = BufferedReader(FileReader(file)) + var line: String? - while ((line = br.readLine()) != null) { - String[] split = line.split("#"); + while ((br.readLine().also { line = it }) != null) { + val split: Array = line!!.split("#".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (split.length <= 2) { - entries.add(new Entry(Float.parseFloat(split[0]), Integer.parseInt(split[1]))); + if (split.size <= 2) { + entries.add(Entry(split[0]!!.toFloat(), split[1]!!.toInt().toFloat())) } else { + val vals = FloatArray(split.size - 1) - float[] vals = new float[split.length - 1]; - - for (int i = 0; i < vals.length; i++) { - vals[i] = Float.parseFloat(split[i]); + for (i in vals.indices) { + vals[i] = split[i]!!.toFloat() } - entries.add(new BarEntry(Integer.parseInt(split[split.length - 1]), vals)); + entries.add(BarEntry(split[split.size - 1]!!.toInt().toFloat(), vals)) } } - } catch (IOException e) { - Log.e(LOG, e.toString()); + } catch (e: IOException) { + Log.e(LOG, e.toString()) } - return entries; + return entries // File sdcard = Environment.getExternalStorageDirectory(); // @@ -101,55 +92,53 @@ public static List loadEntriesFromFile(String path) { /** * Loads an array of Entries from a textfile from the assets folder. - * + * * @param am * @param path the name of the file in the assets folder (+ path if needed) * @return */ - public static List loadEntriesFromAssets(AssetManager am, String path) { - - List entries = new ArrayList(); + @JvmStatic + fun loadEntriesFromAssets(am: AssetManager, path: String): MutableList { + val entries: MutableList = ArrayList() - BufferedReader reader = null; + var reader: BufferedReader? = null try { - reader = new BufferedReader( - new InputStreamReader(am.open(path), "UTF-8")); + reader = BufferedReader( + InputStreamReader(am.open(path), "UTF-8") + ) - String line = reader.readLine(); + var line = reader.readLine() while (line != null) { // process line - String[] split = line.split("#"); + val split: Array = line.split("#".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - if (split.length <= 2) { - entries.add(new Entry(Float.parseFloat(split[1]), Float.parseFloat(split[0]))); + if (split.size <= 2) { + entries.add(Entry(split[1]!!.toFloat(), split[0]!!.toFloat())) } else { + val vals = FloatArray(split.size - 1) - float[] vals = new float[split.length - 1]; - - for (int i = 0; i < vals.length; i++) { - vals[i] = Float.parseFloat(split[i]); + for (i in vals.indices) { + vals[i] = split[i]!!.toFloat() } - entries.add(new BarEntry(Integer.parseInt(split[split.length - 1]), vals)); + entries.add(BarEntry(split[split.size - 1]!!.toInt().toFloat(), vals)) } - line = reader.readLine(); + line = reader.readLine() } - } catch (IOException e) { - Log.e(LOG, e.toString()); - + } catch (e: IOException) { + Log.e(LOG, e.toString()) } finally { - if (reader != null) { try { - reader.close(); - } catch (IOException e) { - Log.e(LOG, e.toString()); + reader.close() + } catch (e: IOException) { + Log.e(LOG, e.toString()) } } } - return entries; + return entries // String label = null; // List entries = new ArrayList(); @@ -190,77 +179,69 @@ public static List loadEntriesFromAssets(AssetManager am, String path) { /** * Saves an Array of Entries to the specified location on the sdcard - * + * * @param entries * @param path */ - public static void saveToSdCard(List entries, String path) { - - File sdcard = Environment.getExternalStorageDirectory(); - - File saved = new File(sdcard, path); - if (!saved.exists()) - { - try - { - saved.createNewFile(); - } catch (IOException e) - { - Log.e(LOG, e.toString()); + fun saveToSdCard(entries: MutableList, path: String) { + val sdcard = Environment.getExternalStorageDirectory() + + val saved = File(sdcard, path) + if (!saved.exists()) { + try { + saved.createNewFile() + } catch (e: IOException) { + Log.e(LOG, e.toString()) } } - try - { + try { // BufferedWriter for performance, true to set append to file flag - BufferedWriter buf = new BufferedWriter(new FileWriter(saved, true)); + val buf = BufferedWriter(FileWriter(saved, true)) - for (Entry e : entries) { - - buf.append(e.getY() + "#" + e.getX()); - buf.newLine(); + for (e in entries) { + buf.append(e.y.toString() + "#" + e.x) + buf.newLine() } - buf.close(); - } catch (IOException e) - { - Log.e(LOG, e.toString()); + buf.close() + } catch (e: IOException) { + Log.e(LOG, e.toString()) } } - public static List loadBarEntriesFromAssets(AssetManager am, String path) { - - List entries = new ArrayList(); + @JvmStatic + fun loadBarEntriesFromAssets(am: AssetManager, path: String): MutableList { + val entries: MutableList = ArrayList() - BufferedReader reader = null; + var reader: BufferedReader? = null try { - reader = new BufferedReader( - new InputStreamReader(am.open(path), "UTF-8")); + reader = BufferedReader( + InputStreamReader(am.open(path), "UTF-8") + ) - String line = reader.readLine(); + var line = reader.readLine() while (line != null) { // process line - String[] split = line.split("#"); + val split: Array = line.split("#".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - entries.add(new BarEntry(Float.parseFloat(split[1]), Float.parseFloat(split[0]))); + entries.add(BarEntry(split[1]!!.toFloat(), split[0]!!.toFloat())) - line = reader.readLine(); + line = reader.readLine() } - } catch (IOException e) { - Log.e(LOG, e.toString()); - + } catch (e: IOException) { + Log.e(LOG, e.toString()) } finally { - if (reader != null) { try { - reader.close(); - } catch (IOException e) { - Log.e(LOG, e.toString()); + reader.close() + } catch (e: IOException) { + Log.e(LOG, e.toString()) } } } - return entries; + return entries // String label = null; // ArrayList entries = new ArrayList(); diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Fill.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Fill.java deleted file mode 100644 index b0699cedba..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Fill.java +++ /dev/null @@ -1,312 +0,0 @@ -package com.github.mikephil.charting.utils; - -import android.graphics.Canvas; -import android.graphics.LinearGradient; -import android.graphics.Paint; -import android.graphics.Path; -import android.graphics.RectF; -import android.graphics.drawable.Drawable; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class Fill { - public enum Type { - EMPTY, COLOR, LINEAR_GRADIENT, DRAWABLE - } - - public enum Direction { - DOWN, UP, RIGHT, LEFT - } - - /** - * the type of fill - */ - private Type mType = Type.EMPTY; - - /** - * the color that is used for filling - */ - @Nullable - private Integer mColor = null; - - private Integer mFinalColor = null; - - /** - * the drawable to be used for filling - */ - @Nullable - protected Drawable mDrawable; - - @Nullable - private int[] mGradientColors; - - @Nullable - private float[] mGradientPositions; - - /** - * transparency used for filling - */ - private int mAlpha = 255; - - public Fill() { - } - - public Fill(int color) { - this.mType = Type.COLOR; - this.mColor = color; - calculateFinalColor(); - } - - public Fill(int startColor, int endColor) { - this.mType = Type.LINEAR_GRADIENT; - this.mGradientColors = new int[]{startColor, endColor}; - } - - public Fill(@NonNull int[] gradientColors) { - this.mType = Type.LINEAR_GRADIENT; - this.mGradientColors = gradientColors; - } - - public Fill(@NonNull int[] gradientColors, @NonNull float[] gradientPositions) { - this.mType = Type.LINEAR_GRADIENT; - this.mGradientColors = gradientColors; - this.mGradientPositions = gradientPositions; - } - - public Fill(@NonNull Drawable drawable) { - this.mType = Type.DRAWABLE; - this.mDrawable = drawable; - } - - public Type getType() { - return mType; - } - - public void setType(Type type) { - this.mType = type; - } - - @Nullable - public Integer getColor() { - return mColor; - } - - public void setColor(int color) { - this.mColor = color; - calculateFinalColor(); - } - - public int[] getGradientColors() { - return mGradientColors; - } - - public void setGradientColors(int[] colors) { - this.mGradientColors = colors; - } - - public float[] getGradientPositions() { - return mGradientPositions; - } - - public void setGradientPositions(float[] positions) { - this.mGradientPositions = positions; - } - - public void setGradientColors(int startColor, int endColor) { - this.mGradientColors = new int[]{startColor, endColor}; - } - - public int getAlpha() { - return mAlpha; - } - - public void setAlpha(int alpha) { - this.mAlpha = alpha; - calculateFinalColor(); - } - - private void calculateFinalColor() { - if (mColor == null) { - mFinalColor = null; - } else { - int alpha = (int) Math.floor(((mColor >> 24) / 255.0) * (mAlpha / 255.0) * 255.0); - mFinalColor = (alpha << 24) | (mColor & 0xffffff); - } - } - - public void fillRect(Canvas c, Paint paint, - float left, float top, float right, float bottom, - Direction gradientDirection, float mRoundedBarRadius) { - switch (mType) { - case EMPTY: - return; - - case COLOR: { - if (mFinalColor == null) { - return; - } - - if (isClipPathSupported()) { - int save = c.save(); - - c.clipRect(left, top, right, bottom); - c.drawColor(mFinalColor); - - c.restoreToCount(save); - } else { - // save - Paint.Style previous = paint.getStyle(); - int previousColor = paint.getColor(); - - // set - paint.setStyle(Paint.Style.FILL); - paint.setColor(mFinalColor); - - c.drawRoundRect(new RectF(left, top, right, bottom), mRoundedBarRadius, mRoundedBarRadius, paint); - - // restore - paint.setColor(previousColor); - paint.setStyle(previous); - } - } - break; - - case LINEAR_GRADIENT: { - if (mGradientColors == null) { - return; - } - - LinearGradient gradient = new LinearGradient( - (int) (gradientDirection == Direction.RIGHT - ? right - : gradientDirection == Direction.LEFT - ? left - : left), - (int) (gradientDirection == Direction.UP - ? bottom - : gradientDirection == Direction.DOWN - ? top - : top), - (int) (gradientDirection == Direction.RIGHT - ? left - : gradientDirection == Direction.LEFT - ? right - : left), - (int) (gradientDirection == Direction.UP - ? top - : gradientDirection == Direction.DOWN - ? bottom - : top), - mGradientColors, - mGradientPositions, - android.graphics.Shader.TileMode.MIRROR); - - paint.setShader(gradient); - - c.drawRoundRect(new RectF(left, top, right, bottom), mRoundedBarRadius, mRoundedBarRadius, paint); - } - break; - - case DRAWABLE: { - if (mDrawable == null) { - return; - } - - mDrawable.setBounds((int) left, (int) top, (int) right, (int) bottom); - mDrawable.draw(c); - } - break; - } - } - - public void fillPath(Canvas c, Path path, Paint paint, - @Nullable RectF clipRect) { - switch (mType) { - case EMPTY: - return; - - case COLOR: { - if (mFinalColor == null) { - return; - } - - if (clipRect != null && isClipPathSupported()) { - int save = c.save(); - - c.clipPath(path); - c.drawColor(mFinalColor); - - c.restoreToCount(save); - } else { - // save - Paint.Style previous = paint.getStyle(); - int previousColor = paint.getColor(); - - // set - paint.setStyle(Paint.Style.FILL); - paint.setColor(mFinalColor); - - c.drawPath(path, paint); - - // restore - paint.setColor(previousColor); - paint.setStyle(previous); - } - } - break; - - case LINEAR_GRADIENT: { - if (mGradientColors == null) { - return; - } - - LinearGradient gradient = new LinearGradient( - 0, - 0, - c.getWidth(), - c.getHeight(), - mGradientColors, - mGradientPositions, - android.graphics.Shader.TileMode.MIRROR); - - paint.setShader(gradient); - - c.drawPath(path, paint); - } - break; - - case DRAWABLE: { - if (mDrawable == null) { - return; - } - - ensureClipPathSupported(); - - int save = c.save(); - c.clipPath(path); - - mDrawable.setBounds( - clipRect == null ? 0 : (int) clipRect.left, - clipRect == null ? 0 : (int) clipRect.top, - clipRect == null ? c.getWidth() : (int) clipRect.right, - clipRect == null ? c.getHeight() : (int) clipRect.bottom); - mDrawable.draw(c); - - c.restoreToCount(save); - } - break; - } - } - - private boolean isClipPathSupported() { - return Utils.getSDKInt() >= 18; - } - - private void ensureClipPathSupported() { - if (Utils.getSDKInt() < 18) { - throw new RuntimeException("Fill-drawables not (yet) supported below API level 18, " + - "this code was run on API level " + Utils.getSDKInt() + "."); - } - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Fill.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Fill.kt new file mode 100644 index 0000000000..21cbd3f239 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Fill.kt @@ -0,0 +1,285 @@ +package com.github.mikephil.charting.utils + +import android.graphics.Canvas +import android.graphics.LinearGradient +import android.graphics.Paint +import android.graphics.Path +import android.graphics.RectF +import android.graphics.Shader +import android.graphics.drawable.Drawable +import kotlin.math.floor +import androidx.core.graphics.withClip + +open class Fill { + enum class Type { + EMPTY, COLOR, LINEAR_GRADIENT, DRAWABLE + } + + enum class Direction { + DOWN, UP, RIGHT, LEFT + } + + /** + * the type of fill + */ + var type: Type = Type.EMPTY + + /** + * the color that is used for filling + */ + private var mColor: Int? = null + + private var mFinalColor: Int? = null + + /** + * the drawable to be used for filling + */ + protected var mDrawable: Drawable? = null + + var gradientColors: IntArray? + + val gradientPositions: FloatArray? + + /** + * transparency used for filling + */ + private var mAlpha = 255 + + constructor() { + gradientColors = null + gradientPositions = null + } + + constructor(color: Int) { + this.type = Type.COLOR + this.mColor = color + this.gradientColors = null + this.gradientPositions = null + calculateFinalColor() + } + + constructor(startColor: Int, endColor: Int) { + this.type = Type.LINEAR_GRADIENT + this.gradientColors = intArrayOf(startColor, endColor) + this.gradientPositions = null + } + + constructor(gradientColors: IntArray) { + this.type = Type.LINEAR_GRADIENT + this.gradientColors = gradientColors + this.gradientPositions = null + } + + constructor(gradientColors: IntArray, gradientPositions: FloatArray) { + this.type = Type.LINEAR_GRADIENT + this.gradientColors = gradientColors + this.gradientPositions = gradientPositions + } + + constructor(drawable: Drawable) { + this.type = Type.DRAWABLE + this.mDrawable = drawable + this.gradientColors = null + this.gradientPositions = null + } + + var color: Int? + get() = mColor + set(color) { + this.mColor = color + calculateFinalColor() + } + + fun setGradientColors(startColor: Int, endColor: Int) { + this.gradientColors = intArrayOf(startColor, endColor) + } + + var alpha: Int + get() = mAlpha + set(alpha) { + this.mAlpha = alpha + calculateFinalColor() + } + + private fun calculateFinalColor() { + val color = mColor + + if (color == null) { + mFinalColor = null + } else { + val alpha = floor(((color shr 24) / 255.0) * (mAlpha / 255.0) * 255.0).toInt() + mFinalColor = (alpha shl 24) or (color and 0xffffff) + } + } + + fun fillRect( + c: Canvas, paint: Paint, + left: Float, top: Float, right: Float, bottom: Float, + gradientDirection: Direction?, mRoundedBarRadius: Float + ) { + when (this.type) { + Type.EMPTY -> return + + Type.COLOR -> { + val finalColor = mFinalColor + if (finalColor == null) { + return + } + + if (this.isClipPathSupported) { + c.withClip(left, top, right, bottom) { + c.drawColor(finalColor) + } + } else { + // save + val previous = paint.style + val previousColor = paint.color + + // set + paint.style = Paint.Style.FILL + paint.setColor(finalColor) + + c.drawRoundRect(RectF(left, top, right, bottom), mRoundedBarRadius, mRoundedBarRadius, paint) + + // restore + paint.setColor(previousColor) + paint.style = previous + } + } + + Type.LINEAR_GRADIENT -> { + val gradientColors = this.gradientColors + if (gradientColors == null) { + return + } + + val gradient = LinearGradient( + (when (gradientDirection) { + Direction.RIGHT -> right + Direction.LEFT -> left + else -> left + }).toInt().toFloat(), + (when (gradientDirection) { + Direction.UP -> bottom + Direction.DOWN -> top + else -> top + }).toInt().toFloat(), + (when (gradientDirection) { + Direction.RIGHT -> left + Direction.LEFT -> right + else -> left + }).toInt().toFloat(), + (when (gradientDirection) { + Direction.UP -> top + Direction.DOWN -> bottom + else -> top + }).toInt().toFloat(), + gradientColors, + this.gradientPositions, + Shader.TileMode.MIRROR + ) + + paint.setShader(gradient) + + c.drawRoundRect(RectF(left, top, right, bottom), mRoundedBarRadius, mRoundedBarRadius, paint) + } + + Type.DRAWABLE -> { + mDrawable?.setBounds(left.toInt(), top.toInt(), right.toInt(), bottom.toInt()) + mDrawable?.draw(c) + } + } + } + + fun fillPath( + c: Canvas, path: Path, paint: Paint, + clipRect: RectF? + ) { + when (this.type) { + Type.EMPTY -> { + return + } + + Type.COLOR -> { + val finalColor = mFinalColor + if (finalColor == null) { + return + } + + if (clipRect != null && this.isClipPathSupported) { + c.withClip(path) { + c.drawColor(finalColor) + } + } else { + // save + val previous = paint.style + val previousColor = paint.color + + // set + paint.style = Paint.Style.FILL + paint.setColor(finalColor) + + c.drawPath(path, paint) + + // restore + paint.setColor(previousColor) + paint.style = previous + } + } + + Type.LINEAR_GRADIENT -> { + val gradientColors = this.gradientColors + if (gradientColors == null) { + return + } + + val gradient = LinearGradient( + 0f, + 0f, + c.width.toFloat(), + c.height.toFloat(), + gradientColors, + this.gradientPositions, + Shader.TileMode.MIRROR + ) + + paint.setShader(gradient) + + c.drawPath(path, paint) + } + + Type.DRAWABLE -> { + if (mDrawable == null) { + return + } + + ensureClipPathSupported() + + val save = c.save() + c.clipPath(path) + + mDrawable?.setBounds( + clipRect?.left?.toInt() ?: 0, + clipRect?.top?.toInt() ?: 0, + clipRect?.right?.toInt() ?: c.width, + clipRect?.bottom?.toInt() ?: c.height + ) + mDrawable?.draw(c) + + c.restoreToCount(save) + } + } + } + + private val isClipPathSupported: Boolean + get() = Utils.sDKInt >= 18 + + private fun ensureClipPathSupported() { + if (Utils.sDKInt < 18) { + throw RuntimeException( + "Fill-drawables not (yet) supported below API level 18, " + + "this code was run on API level " + Utils.sDKInt + "." + ) + } + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/HorizontalViewPortHandler.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/HorizontalViewPortHandler.java deleted file mode 100644 index 5a415b0477..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/HorizontalViewPortHandler.java +++ /dev/null @@ -1,29 +0,0 @@ - -package com.github.mikephil.charting.utils; - -/** - * ViewPortHandler for HorizontalBarChart. - */ -public class HorizontalViewPortHandler extends ViewPortHandler { - - -// @Override -// public void setMinimumScaleX(float xScale) { -// setMinimumScaleY(xScale); -// } -// -// @Override -// public void setMinimumScaleY(float yScale) { -// setMinimumScaleX(yScale); -// } -// -// @Override -// public void setMinMaxScaleX(float minScaleX, float maxScaleX) { -// setMinMaxScaleY(minScaleX, maxScaleX); -// } -// -// @Override -// public void setMinMaxScaleY(float minScaleY, float maxScaleY) { -// setMinMaxScaleX(minScaleY, maxScaleY); -// } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/HorizontalViewPortHandler.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/HorizontalViewPortHandler.kt new file mode 100644 index 0000000000..3a79c48984 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/HorizontalViewPortHandler.kt @@ -0,0 +1,6 @@ +package com.github.mikephil.charting.utils + +/** + * ViewPortHandler for HorizontalBarChart. + */ +class HorizontalViewPortHandler : ViewPortHandler() diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointD.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointD.java deleted file mode 100644 index f6220a72e9..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointD.java +++ /dev/null @@ -1,53 +0,0 @@ - -package com.github.mikephil.charting.utils; - -import java.util.List; - -/** - * Point encapsulating two double values. - * - * @author Philipp Jahoda - */ -public class MPPointD extends ObjectPool.Poolable { - - private static ObjectPool pool; - - static { - pool = ObjectPool.create(64, new MPPointD(0,0)); - pool.setReplenishPercentage(0.5f); - } - - public static MPPointD getInstance(double x, double y){ - MPPointD result = pool.get(); - result.x = x; - result.y = y; - return result; - } - - public static void recycleInstance(MPPointD instance){ - pool.recycle(instance); - } - - public static void recycleInstances(List instances){ - pool.recycle(instances); - } - - public double x; - public double y; - - protected ObjectPool.Poolable instantiate(){ - return new MPPointD(0,0); - } - - private MPPointD(double x, double y) { - this.x = x; - this.y = y; - } - - /** - * returns a string representation of the object - */ - public String toString() { - return "MPPointD, x: " + x + ", y: " + y; - } -} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointD.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointD.kt new file mode 100644 index 0000000000..cf4095ce9d --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointD.kt @@ -0,0 +1,44 @@ +package com.github.mikephil.charting.utils + +import com.github.mikephil.charting.utils.ObjectPool.Poolable + +/** + * Point encapsulating two double values. + * + * @author Philipp Jahoda + */ +class MPPointD private constructor(var x: Double, var y: Double) : Poolable() { + override fun instantiate(): MPPointD { + return MPPointD(0.0, 0.0) + } + + /** + * returns a string representation of the object + */ + override fun toString(): String { + return "MPPointD, x: $x, y: $y" + } + + companion object { + private val pool: ObjectPool = ObjectPool.Companion.create(64, MPPointD(0.0, 0.0)) + + init { + pool.replenishPercentage = 0.5f + } + + fun getInstance(x: Double, y: Double): MPPointD { + val result: MPPointD = pool.get() + result.x = x + result.y = y + return result + } + + fun recycleInstance(instance: MPPointD) { + pool.recycle(instance) + } + + fun recycleInstances(instances: MutableList) { + pool.recycle(instances) + } + } +} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointF.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointF.java deleted file mode 100644 index fb5a00f508..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointF.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.github.mikephil.charting.utils; - -import android.os.Parcel; -import android.os.Parcelable; - -import java.util.List; - -/** - * Created by Tony Patino on 6/24/16. - */ -public class MPPointF extends ObjectPool.Poolable { - - private static ObjectPool pool; - - public float x; - public float y; - - static { - pool = ObjectPool.create(32, new MPPointF(0,0)); - pool.setReplenishPercentage(0.5f); - } - - public MPPointF() { - } - - public MPPointF(float x, float y) { - this.x = x; - this.y = y; - } - - public static MPPointF getInstance(float x, float y) { - MPPointF result = pool.get(); - result.x = x; - result.y = y; - return result; - } - - public static MPPointF getInstance() { - return pool.get(); - } - - public static MPPointF getInstance(MPPointF copy) { - MPPointF result = pool.get(); - result.x = copy.x; - result.y = copy.y; - return result; - } - - public static void recycleInstance(MPPointF instance){ - pool.recycle(instance); - } - - public static void recycleInstances(List instances){ - pool.recycle(instances); - } - - public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { - /** - * Return a new point from the data in the specified parcel. - */ - public MPPointF createFromParcel(Parcel in) { - MPPointF r = new MPPointF(0,0); - r.my_readFromParcel(in); - return r; - } - - /** - * Return an array of rectangles of the specified size. - */ - public MPPointF[] newArray(int size) { - return new MPPointF[size]; - } - }; - - /** - * Set the point's coordinates from the data stored in the specified - * parcel. To write a point to a parcel, call writeToParcel(). - * Provided to support older Android devices. - * - * @param in The parcel to read the point's coordinates from - */ - public void my_readFromParcel(Parcel in) { - x = in.readFloat(); - y = in.readFloat(); - } - - public float getX(){ - return this.x; - } - - public float getY(){ - return this.y; - } - - @Override - protected ObjectPool.Poolable instantiate() { - return new MPPointF(0,0); - } -} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointF.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointF.kt new file mode 100644 index 0000000000..84868f2d5b --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/MPPointF.kt @@ -0,0 +1,88 @@ +package com.github.mikephil.charting.utils + +import android.os.Parcel +import android.os.Parcelable +import com.github.mikephil.charting.utils.ObjectPool.Poolable + +/** + * Created by Tony Patino on 6/24/16. + */ +class MPPointF : Poolable { + var x: Float = 0f + var y: Float = 0f + + constructor() + + constructor(x: Float, y: Float) { + this.x = x + this.y = y + } + + /** + * Set the point's coordinates from the data stored in the specified + * parcel. To write a point to a parcel, call writeToParcel(). + * Provided to support older Android devices. + * + * @param in The parcel to read the point's coordinates from + */ + fun my_readFromParcel(`in`: Parcel) { + x = `in`.readFloat() + y = `in`.readFloat() + } + + override fun instantiate(): MPPointF { + return MPPointF(0f, 0f) + } + + companion object { + private var pool: ObjectPool = ObjectPool.Companion.create(32, MPPointF(0f, 0f)) + + init { + pool.replenishPercentage = 0.5f + } + + fun getInstance(x: Float, y: Float): MPPointF { + val result: MPPointF = pool.get() + result.x = x + result.y = y + return result + } + + val instance: MPPointF + get() = pool.get() + + fun getInstance(copy: MPPointF): MPPointF { + val result: MPPointF = pool.get() + result.x = copy.x + result.y = copy.y + return result + } + + @JvmStatic + fun recycleInstance(instance: MPPointF?) { + pool.recycle(instance) + } + + fun recycleInstances(instances: MutableList) { + pool.recycle(instances) + } + + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + /** + * Return a new point from the data in the specified parcel. + */ + override fun createFromParcel(`in`: Parcel): MPPointF { + val r = MPPointF(0f, 0f) + r.my_readFromParcel(`in`) + return r + } + + /** + * Return an array of rectangles of the specified size. + */ + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } +} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ObjectPool.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ObjectPool.java deleted file mode 100644 index 15e7fc0b88..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ObjectPool.java +++ /dev/null @@ -1,219 +0,0 @@ -package com.github.mikephil.charting.utils; - -import java.util.List; - -/** - * An object pool for recycling of object instances extending Poolable. - * - * - * Cost/Benefit : - * Cost - The pool can only contain objects extending Poolable. - * Benefit - The pool can very quickly determine if an object is elligable for storage without iteration. - * Benefit - The pool can also know if an instance of Poolable is already stored in a different pool instance. - * Benefit - The pool can grow as needed, if it is empty - * Cost - However, refilling the pool when it is empty might incur a time cost with sufficiently large capacity. Set the replenishPercentage to a lower number if this is a concern. - * - * Created by Tony Patino on 6/20/16. - */ -public class ObjectPool { - - private static int ids = 0; - - private int poolId; - private int desiredCapacity; - private Object[] objects; - private int objectsPointer; - private T modelObject; - private float replenishPercentage; - - - /** - * Returns the id of the given pool instance. - * - * @return an integer ID belonging to this pool instance. - */ - public int getPoolId(){ - return poolId; - } - - /** - * Returns an ObjectPool instance, of a given starting capacity, that recycles instances of a given Poolable object. - * - * @param withCapacity A positive integer value. - * @param object An instance of the object that the pool should recycle. - * @return - */ - public static synchronized ObjectPool create(int withCapacity, Poolable object){ - ObjectPool result = new ObjectPool(withCapacity, object); - result.poolId = ids; - ids++; - - return result; - } - - private ObjectPool(int withCapacity, T object){ - if(withCapacity <= 0){ - throw new IllegalArgumentException("Object Pool must be instantiated with a capacity greater than 0!"); - } - this.desiredCapacity = withCapacity; - this.objects = new Object[this.desiredCapacity]; - this.objectsPointer = 0; - this.modelObject = object; - this.replenishPercentage = 1.0f; - this.refillPool(); - } - - /** - * Set the percentage of the pool to replenish on empty. Valid values are between - * 0.00f and 1.00f - * - * @param percentage a value between 0 and 1, representing the percentage of the pool to replenish. - */ - public void setReplenishPercentage(float percentage){ - float p = percentage; - if(p > 1){ - p = 1; - } - else if(p < 0f){ - p = 0f; - } - this.replenishPercentage = p; - } - - public float getReplenishPercentage(){ - return replenishPercentage; - } - - private void refillPool(){ - this.refillPool(this.replenishPercentage); - } - - private void refillPool(float percentage){ - int portionOfCapacity = (int) (desiredCapacity * percentage); - - if(portionOfCapacity < 1){ - portionOfCapacity = 1; - }else if(portionOfCapacity > desiredCapacity){ - portionOfCapacity = desiredCapacity; - } - - for(int i = 0 ; i < portionOfCapacity ; i++){ - this.objects[i] = modelObject.instantiate(); - } - objectsPointer = portionOfCapacity - 1; - } - - /** - * Returns an instance of Poolable. If get() is called with an empty pool, the pool will be - * replenished. If the pool capacity is sufficiently large, this could come at a performance - * cost. - * - * @return An instance of Poolable object T - */ - public synchronized T get(){ - - if(this.objectsPointer == -1 && this.replenishPercentage > 0.0f){ - this.refillPool(); - } - - T result = (T)objects[this.objectsPointer]; - objects[this.objectsPointer] = null; - result.currentOwnerId = Poolable.NO_OWNER; - this.objectsPointer--; - - return result; - } - - /** - * Recycle an instance of Poolable that this pool is capable of generating. - * The T instance passed must not already exist inside this or any other ObjectPool instance. - * - * @param object An object of type T to recycle - */ - public synchronized void recycle(T object){ - if(object.currentOwnerId != Poolable.NO_OWNER){ - if(object.currentOwnerId == this.poolId){ - throw new IllegalArgumentException("The object passed is already stored in this pool!"); - }else { - throw new IllegalArgumentException("The object to recycle already belongs to poolId " + object.currentOwnerId + ". Object cannot belong to two different pool instances simultaneously!"); - } - } - - this.objectsPointer++; - if(this.objectsPointer >= objects.length){ - this.resizePool(); - } - - object.currentOwnerId = this.poolId; - objects[this.objectsPointer] = object; - - } - - /** - * Recycle a List of Poolables that this pool is capable of generating. - * The T instances passed must not already exist inside this or any other ObjectPool instance. - * - * @param objects A list of objects of type T to recycle - */ - public synchronized void recycle(List objects){ - while(objects.size() + this.objectsPointer + 1 > this.desiredCapacity){ - this.resizePool(); - } - final int objectsListSize = objects.size(); - - // Not relying on recycle(T object) because this is more performant. - for(int i = 0 ; i < objectsListSize ; i++){ - T object = objects.get(i); - if(object.currentOwnerId != Poolable.NO_OWNER){ - if(object.currentOwnerId == this.poolId){ - throw new IllegalArgumentException("The object passed is already stored in this pool!"); - }else { - throw new IllegalArgumentException("The object to recycle already belongs to poolId " + object.currentOwnerId + ". Object cannot belong to two different pool instances simultaneously!"); - } - } - object.currentOwnerId = this.poolId; - this.objects[this.objectsPointer + 1 + i] = object; - } - this.objectsPointer += objectsListSize; - } - - private void resizePool() { - final int oldCapacity = this.desiredCapacity; - this.desiredCapacity *= 2; - Object[] temp = new Object[this.desiredCapacity]; - for(int i = 0 ; i < oldCapacity ; i++){ - temp[i] = this.objects[i]; - } - this.objects = temp; - } - - /** - * Returns the capacity of this object pool. Note : The pool will automatically resize - * to contain additional objects if the user tries to add more objects than the pool's - * capacity allows, but this comes at a performance cost. - * - * @return The capacity of the pool. - */ - public int getPoolCapacity(){ - return this.objects.length; - } - - /** - * Returns the number of objects remaining in the pool, for diagnostic purposes. - * - * @return The number of objects remaining in the pool. - */ - public int getPoolCount(){ - return this.objectsPointer + 1; - } - - - public static abstract class Poolable{ - - public static int NO_OWNER = -1; - int currentOwnerId = NO_OWNER; - - protected abstract Poolable instantiate(); - - } -} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ObjectPool.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ObjectPool.kt new file mode 100644 index 0000000000..cbe6955ea0 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ObjectPool.kt @@ -0,0 +1,184 @@ +package com.github.mikephil.charting.utils + +import com.github.mikephil.charting.utils.ObjectPool.Poolable + +/** + * An object pool for recycling of object instances extending Poolable. + * + * + * Cost/Benefit : + * Cost - The pool can only contain objects extending Poolable. + * Benefit - The pool can very quickly determine if an object is elligable for storage without iteration. + * Benefit - The pool can also know if an instance of Poolable is already stored in a different pool instance. + * Benefit - The pool can grow as needed, if it is empty + * Cost - However, refilling the pool when it is empty might incur a time cost with sufficiently large capacity. Set the replenishPercentage to a lower number if this is a concern. + * + * Created by Tony Patino on 6/20/16. + */ +class ObjectPool> private constructor(withCapacity: Int, `object`: T?) { + /** + * Returns the id of the given pool instance. + * + * @return an integer ID belonging to this pool instance. + */ + var poolId: Int = 0 + private set + private var desiredCapacity: Int + private val objects = ArrayList(withCapacity) + private val modelObject: T? + var replenishPercentage: Float = 1.0f + set(value) { + var p = value + if (p > 1) { + p = 1f + } else if (p < 0f) { + p = 0f + } + field = p + } + + + init { + require(withCapacity > 0) { "Object Pool must be instantiated with a capacity greater than 0!" } + this.desiredCapacity = withCapacity + this.modelObject = `object` + this.refillPool() + } + + private fun refillPool(percentage: Float = this.replenishPercentage) { + var portionOfCapacity = (desiredCapacity * percentage).toInt() + + if (portionOfCapacity < 1) { + portionOfCapacity = 1 + } else if (portionOfCapacity > desiredCapacity) { + portionOfCapacity = desiredCapacity + } + + this.objects.clear() + + repeat(portionOfCapacity) { + this.objects.add(modelObject!!.instantiate()) + } + } + + /** + * Returns an instance of Poolable. If get() is called with an empty pool, the pool will be + * replenished. If the pool capacity is sufficiently large, this could come at a performance + * cost. + * + * @return An instance of Poolable object T + */ + @Synchronized + fun get(): T { + if (objects.isEmpty() && this.replenishPercentage > 0.0f) { + this.refillPool() + } + + val result = objects.removeAt(objects.lastIndex) + result.currentOwnerId = Poolable.Companion.NO_OWNER + + return result + } + + /** + * Recycle an instance of Poolable that this pool is capable of generating. + * The T instance passed must not already exist inside this or any other ObjectPool instance. + * + * @param object An object of type T to recycle + */ + @Synchronized + fun recycle(`object`: T?) { + if (`object` == null) return + + if (`object`.currentOwnerId != Poolable.Companion.NO_OWNER) { + require(`object`.currentOwnerId != this.poolId) { "The object passed is already stored in this pool!" } + throw IllegalArgumentException("The object to recycle already belongs to poolId " + `object`.currentOwnerId + ". Object cannot belong to two different pool instances simultaneously!") + } + + `object`.currentOwnerId = this.poolId + objects.add(`object`) + + if (objects.size > desiredCapacity) { + resizePool() + } + } + + /** + * Recycle a List of Poolables that this pool is capable of generating. + * The T instances passed must not already exist inside this or any other ObjectPool instance. + * + * @param objects A list of objects of type T to recycle + */ + @Synchronized + fun recycle(objects: List) { + val objectsListSize = objects.size + + while (objectsListSize + this.objects.size > this.desiredCapacity) { + resizePool() + } + + // Not relying on recycle(T object) because this is more performant. + for (i in 0..> { + var currentOwnerId: Int = NO_OWNER + + abstract fun instantiate(): T + + companion object { + var NO_OWNER: Int = -1 + } + } + + companion object { + private var ids = 0 + + /** + * Returns an ObjectPool instance, of a given starting capacity, that recycles instances of a given Poolable object. + * + * @param withCapacity A positive integer value. + * @param object An instance of the object that the pool should recycle. + * @return + */ + @Synchronized + fun > create(withCapacity: Int, `object`: T): ObjectPool { + val result: ObjectPool = ObjectPool(withCapacity, `object`) + result.poolId = ids + ids++ + + return result + } + } +} \ No newline at end of file diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Transformer.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Transformer.java deleted file mode 100644 index 8a4371802a..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Transformer.java +++ /dev/null @@ -1,461 +0,0 @@ - -package com.github.mikephil.charting.utils; - -import android.graphics.Matrix; -import android.graphics.Path; -import android.graphics.RectF; - -import com.github.mikephil.charting.data.CandleEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet; -import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; - -import java.util.List; - -/** - * Transformer class that contains all matrices and is responsible for - * transforming values into pixels on the screen and backwards. - * - * @author Philipp Jahoda - */ -public class Transformer { - - /** - * matrix to map the values to the screen pixels - */ - protected Matrix mMatrixValueToPx = new Matrix(); - - /** - * matrix for handling the different offsets of the chart - */ - protected Matrix mMatrixOffset = new Matrix(); - - protected ViewPortHandler mViewPortHandler; - - public Transformer(ViewPortHandler viewPortHandler) { - this.mViewPortHandler = viewPortHandler; - } - - /** - * Prepares the matrix that transforms values to pixels. Calculates the - * scale factors from the charts size and offsets. - * - * @param xChartMin - * @param deltaX - * @param deltaY - * @param yChartMin - */ - public void prepareMatrixValuePx(float xChartMin, float deltaX, float deltaY, float yChartMin) { - - float scaleX = mViewPortHandler.contentWidth() / deltaX; - float scaleY = mViewPortHandler.contentHeight() / deltaY; - - if (Float.isInfinite(scaleX)) { - scaleX = 0; - } - if (Float.isInfinite(scaleY)) { - scaleY = 0; - } - - // setup all matrices - mMatrixValueToPx.reset(); - mMatrixValueToPx.postTranslate(-xChartMin, -yChartMin); - mMatrixValueToPx.postScale(scaleX, -scaleY); - } - - /** - * Prepares the matrix that contains all offsets. - * - * @param inverted - */ - public void prepareMatrixOffset(boolean inverted) { - - mMatrixOffset.reset(); - - // offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); - - if (!inverted) - mMatrixOffset.postTranslate(mViewPortHandler.offsetLeft(), - mViewPortHandler.getChartHeight() - mViewPortHandler.offsetBottom()); - else { - mMatrixOffset - .setTranslate(mViewPortHandler.offsetLeft(), -mViewPortHandler.offsetTop()); - mMatrixOffset.postScale(1.0f, -1.0f); - } - } - - protected float[] valuePointsForGenerateTransformedValuesScatter = new float[1]; - - /** - * Transforms an List of Entry into a float array containing the x and - * y values transformed with all matrices for the SCATTERCHART. - * - * @param data - * @return - */ - public float[] generateTransformedValuesScatter(IScatterDataSet data, float phaseX, - float phaseY, int from, int to) { - - final int count = (int) ((to - from) * phaseX + 1) * 2; - - if (valuePointsForGenerateTransformedValuesScatter.length != count) { - valuePointsForGenerateTransformedValuesScatter = new float[count]; - } - float[] valuePoints = valuePointsForGenerateTransformedValuesScatter; - - for (int j = 0; j < count; j += 2) { - - Entry e = data.getEntryForIndex(j / 2 + from); - - if (e != null) { - valuePoints[j] = e.getX(); - valuePoints[j + 1] = e.getY() * phaseY; - } else { - valuePoints[j] = 0; - valuePoints[j + 1] = 0; - } - } - - getValueToPixelMatrix().mapPoints(valuePoints); - - return valuePoints; - } - - protected float[] valuePointsForGenerateTransformedValuesBubble = new float[1]; - - /** - * Transforms an List of Entry into a float array containing the x and - * y values transformed with all matrices for the BUBBLECHART. - * - * @param data - * @return - */ - public float[] generateTransformedValuesBubble(IBubbleDataSet data, float phaseY, int from, int to) { - - final int count = (to - from + 1) * 2; // (int) Math.ceil((to - from) * phaseX) * 2; - - if (valuePointsForGenerateTransformedValuesBubble.length != count) { - valuePointsForGenerateTransformedValuesBubble = new float[count]; - } - float[] valuePoints = valuePointsForGenerateTransformedValuesBubble; - - for (int j = 0; j < count; j += 2) { - - Entry e = data.getEntryForIndex(j / 2 + from); - - if (e != null) { - valuePoints[j] = e.getX(); - valuePoints[j + 1] = e.getY() * phaseY; - } else { - valuePoints[j] = 0; - valuePoints[j + 1] = 0; - } - } - - getValueToPixelMatrix().mapPoints(valuePoints); - - return valuePoints; - } - - protected float[] valuePointsForGenerateTransformedValuesLine = new float[1]; - - /** - * Transforms an List of Entry into a float array containing the x and - * y values transformed with all matrices for the LINECHART. - * - * @param data - * @return - */ - public float[] generateTransformedValuesLine(ILineDataSet data, - float phaseX, float phaseY, - int min, int max) { - - int count = ((int) ((max - min) * phaseX) + 1) * 2; - if (count < 0) - count = 0; - - if (valuePointsForGenerateTransformedValuesLine.length != count) { - valuePointsForGenerateTransformedValuesLine = new float[count]; - } - float[] valuePoints = valuePointsForGenerateTransformedValuesLine; - - for (int j = 0; j < count; j += 2) { - - Entry e = data.getEntryForIndex(j / 2 + min); - - if (e != null) { - valuePoints[j] = e.getX(); - valuePoints[j + 1] = e.getY() * phaseY; - } else { - valuePoints[j] = 0; - valuePoints[j + 1] = 0; - } - } - - getValueToPixelMatrix().mapPoints(valuePoints); - - return valuePoints; - } - - protected float[] valuePointsForGenerateTransformedValuesCandle = new float[1]; - - /** - * Transforms an List of Entry into a float array containing the x and - * y values transformed with all matrices for the CANDLESTICKCHART. - * - * @param data - * @return - */ - public float[] generateTransformedValuesCandle(ICandleDataSet data, - float phaseX, float phaseY, int from, int to) { - - final int count = (int) ((to - from) * phaseX + 1) * 2; - - if (valuePointsForGenerateTransformedValuesCandle.length != count) { - valuePointsForGenerateTransformedValuesCandle = new float[count]; - } - float[] valuePoints = valuePointsForGenerateTransformedValuesCandle; - - for (int j = 0; j < count; j += 2) { - - CandleEntry e = data.getEntryForIndex(j / 2 + from); - - if (e != null) { - valuePoints[j] = e.getX(); - valuePoints[j + 1] = e.getHigh() * phaseY; - } else { - valuePoints[j] = 0; - valuePoints[j + 1] = 0; - } - } - - getValueToPixelMatrix().mapPoints(valuePoints); - - return valuePoints; - } - - /** - * transform a path with all the given matrices VERY IMPORTANT: keep order - * to value-touch-offset - * - * @param path - */ - public void pathValueToPixel(Path path) { - - path.transform(mMatrixValueToPx); - path.transform(mViewPortHandler.getMatrixTouch()); - path.transform(mMatrixOffset); - } - - /** - * Transforms multiple paths will all matrices. - * - * @param paths - */ - public void pathValuesToPixel(List paths) { - - for (int i = 0; i < paths.size(); i++) { - pathValueToPixel(paths.get(i)); - } - } - - /** - * Transform an array of points with all matrices. VERY IMPORTANT: Keep - * matrix order "value-touch-offset" when transforming. - * - * @param pts - */ - public void pointValuesToPixel(float[] pts) { - - mMatrixValueToPx.mapPoints(pts); - mViewPortHandler.getMatrixTouch().mapPoints(pts); - mMatrixOffset.mapPoints(pts); - } - - /** - * Transform a rectangle with all matrices. - * - * @param r - */ - public void rectValueToPixel(RectF r) { - - mMatrixValueToPx.mapRect(r); - mViewPortHandler.getMatrixTouch().mapRect(r); - mMatrixOffset.mapRect(r); - } - - /** - * Transform a rectangle with all matrices with potential animation phases. - * - * @param r - * @param phaseY - */ - public void rectToPixelPhase(RectF r, float phaseY) { - - // multiply the height of the rect with the phase - r.top *= phaseY; - r.bottom *= phaseY; - - mMatrixValueToPx.mapRect(r); - mViewPortHandler.getMatrixTouch().mapRect(r); - mMatrixOffset.mapRect(r); - } - - public void rectToPixelPhaseHorizontal(RectF r, float phaseY) { - - // multiply the height of the rect with the phase - r.left *= phaseY; - r.right *= phaseY; - - mMatrixValueToPx.mapRect(r); - mViewPortHandler.getMatrixTouch().mapRect(r); - mMatrixOffset.mapRect(r); - } - - /** - * Transform a rectangle with all matrices with potential animation phases. - * - * @param r - */ - public void rectValueToPixelHorizontal(RectF r) { - - mMatrixValueToPx.mapRect(r); - mViewPortHandler.getMatrixTouch().mapRect(r); - mMatrixOffset.mapRect(r); - } - - /** - * Transform a rectangle with all matrices with potential animation phases. - * - * @param r - * @param phaseY - */ - public void rectValueToPixelHorizontal(RectF r, float phaseY) { - - // multiply the height of the rect with the phase - r.left *= phaseY; - r.right *= phaseY; - - mMatrixValueToPx.mapRect(r); - mViewPortHandler.getMatrixTouch().mapRect(r); - mMatrixOffset.mapRect(r); - } - - /** - * transforms multiple rects with all matrices - * - * @param rects - */ - public void rectValuesToPixel(List rects) { - - Matrix m = getValueToPixelMatrix(); - - for (int i = 0; i < rects.size(); i++) - m.mapRect(rects.get(i)); - } - - protected Matrix mPixelToValueMatrixBuffer = new Matrix(); - - /** - * Transforms the given array of touch positions (pixels) (x, y, x, y, ...) - * into values on the chart. - * - * @param pixels - */ - public void pixelsToValue(float[] pixels) { - - Matrix tmp = mPixelToValueMatrixBuffer; - tmp.reset(); - - // invert all matrixes to convert back to the original value - mMatrixOffset.invert(tmp); - tmp.mapPoints(pixels); - - mViewPortHandler.getMatrixTouch().invert(tmp); - tmp.mapPoints(pixels); - - mMatrixValueToPx.invert(tmp); - tmp.mapPoints(pixels); - } - - /** - * buffer for performance - */ - float[] ptsBuffer = new float[2]; - - /** - * Returns a recyclable MPPointD instance. - * returns the x and y values in the chart at the given touch point - * (encapsulated in a MPPointD). This method transforms pixel coordinates to - * coordinates / values in the chart. This is the opposite method to - * getPixelForValues(...). - * - * @param x - * @param y - * @return - */ - public MPPointD getValuesByTouchPoint(float x, float y) { - - MPPointD result = MPPointD.getInstance(0, 0); - getValuesByTouchPoint(x, y, result); - return result; - } - - public void getValuesByTouchPoint(float x, float y, MPPointD outputPoint) { - - ptsBuffer[0] = x; - ptsBuffer[1] = y; - - pixelsToValue(ptsBuffer); - - outputPoint.x = ptsBuffer[0]; - outputPoint.y = ptsBuffer[1]; - } - - /** - * Returns a recyclable MPPointD instance. - * Returns the x and y coordinates (pixels) for a given x and y value in the chart. - * - * @param x - * @param y - * @return - */ - public MPPointD getPixelForValues(float x, float y) { - - ptsBuffer[0] = x; - ptsBuffer[1] = y; - - pointValuesToPixel(ptsBuffer); - - double xPx = ptsBuffer[0]; - double yPx = ptsBuffer[1]; - - return MPPointD.getInstance(xPx, yPx); - } - - public Matrix getValueMatrix() { - return mMatrixValueToPx; - } - - public Matrix getOffsetMatrix() { - return mMatrixOffset; - } - - private final Matrix mMBuffer1 = new Matrix(); - - public Matrix getValueToPixelMatrix() { - mMBuffer1.set(mMatrixValueToPx); - mMBuffer1.postConcat(mViewPortHandler.getMatrixTouch()); - mMBuffer1.postConcat(mMatrixOffset); - return mMBuffer1; - } - - private final Matrix mMBuffer2 = new Matrix(); - - public Matrix getPixelToValueMatrix() { - getValueToPixelMatrix().invert(mMBuffer2); - return mMBuffer2; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Transformer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Transformer.kt new file mode 100644 index 0000000000..a2100201ee --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Transformer.kt @@ -0,0 +1,419 @@ +package com.github.mikephil.charting.utils + +import android.graphics.Matrix +import android.graphics.Path +import android.graphics.RectF +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet +import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import kotlin.Boolean +import kotlin.Int + +/** + * Transformer class that contains all matrices and is responsible for + * transforming values into pixels on the screen and backwards. + * + * @author Philipp Jahoda + */ +open class Transformer(protected var mViewPortHandler: ViewPortHandler) { + /** + * matrix to map the values to the screen pixels + */ + var valueMatrix: Matrix = Matrix() + protected set + + /** + * matrix for handling the different offsets of the chart + */ + var offsetMatrix: Matrix = Matrix() + protected set + + /** + * Prepares the matrix that transforms values to pixels. Calculates the + * scale factors from the charts size and offsets. + * + * @param xChartMin + * @param deltaX + * @param deltaY + * @param yChartMin + */ + fun prepareMatrixValuePx(xChartMin: Float, deltaX: Float, deltaY: Float, yChartMin: Float) { + var scaleX = mViewPortHandler.contentWidth() / deltaX + var scaleY = mViewPortHandler.contentHeight() / deltaY + + if (scaleX.isInfinite()) { + scaleX = 0f + } + if (scaleY.isInfinite()) { + scaleY = 0f + } + + // setup all matrices + valueMatrix.reset() + valueMatrix.postTranslate(-xChartMin, -yChartMin) + valueMatrix.postScale(scaleX, -scaleY) + } + + /** + * Prepares the matrix that contains all offsets. + * + * @param inverted + */ + open fun prepareMatrixOffset(inverted: Boolean) { + offsetMatrix.reset() + + // offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); + if (!inverted) offsetMatrix.postTranslate( + mViewPortHandler.offsetLeft(), + mViewPortHandler.chartHeight - mViewPortHandler.offsetBottom() + ) + else { + offsetMatrix + .setTranslate(mViewPortHandler.offsetLeft(), -mViewPortHandler.offsetTop()) + offsetMatrix.postScale(1.0f, -1.0f) + } + } + + protected var valuePointsForGenerateTransformedValuesScatter: FloatArray = FloatArray(1) + + /** + * Transforms an List of Entry into a float array containing the x and + * y values transformed with all matrices for the SCATTERCHART. + * + * @param data + * @return + */ + fun generateTransformedValuesScatter( + data: IScatterDataSet, phaseX: Float, + phaseY: Float, from: Int, to: Int + ): FloatArray { + val count = ((to - from) * phaseX + 1).toInt() * 2 + + if (valuePointsForGenerateTransformedValuesScatter.size != count) { + valuePointsForGenerateTransformedValuesScatter = FloatArray(count) + } + val valuePoints = valuePointsForGenerateTransformedValuesScatter + + var j = 0 + while (j < count) { + val e = data.getEntryForIndex(j / 2 + from) + + valuePoints[j] = e.x + valuePoints[j + 1] = e.y * phaseY + j += 2 + } + + this.valueToPixelMatrix.mapPoints(valuePoints) + + return valuePoints + } + + protected var valuePointsForGenerateTransformedValuesBubble: FloatArray = FloatArray(1) + + /** + * Transforms an List of Entry into a float array containing the x and + * y values transformed with all matrices for the BUBBLECHART. + * + * @param data + * @return + */ + fun generateTransformedValuesBubble(data: IBubbleDataSet, phaseY: Float, from: Int, to: Int): FloatArray { + val count = (to - from + 1) * 2 // (int) Math.ceil((to - from) * phaseX) * 2; + + if (valuePointsForGenerateTransformedValuesBubble.size != count) { + valuePointsForGenerateTransformedValuesBubble = FloatArray(count) + } + val valuePoints = valuePointsForGenerateTransformedValuesBubble + + var j = 0 + while (j < count) { + val e: Entry = data.getEntryForIndex(j / 2 + from) + + valuePoints[j] = e.x + valuePoints[j + 1] = e.y * phaseY + j += 2 + } + + this.valueToPixelMatrix.mapPoints(valuePoints) + + return valuePoints + } + + protected var valuePointsForGenerateTransformedValuesLine: FloatArray = FloatArray(1) + + /** + * Transforms an List of Entry into a float array containing the x and + * y values transformed with all matrices for the LINECHART. + * + * @param data + * @return + */ + fun generateTransformedValuesLine( + data: ILineDataSet, + phaseX: Float, phaseY: Float, + min: Int, max: Int + ): FloatArray { + var count = (((max - min) * phaseX).toInt() + 1) * 2 + if (count < 0) count = 0 + + if (valuePointsForGenerateTransformedValuesLine.size != count) { + valuePointsForGenerateTransformedValuesLine = FloatArray(count) + } + val valuePoints = valuePointsForGenerateTransformedValuesLine + + var j = 0 + while (j < count) { + val e = data.getEntryForIndex(j / 2 + min) + + valuePoints[j] = e.x + valuePoints[j + 1] = e.y * phaseY + j += 2 + } + + this.valueToPixelMatrix.mapPoints(valuePoints) + + return valuePoints + } + + protected var valuePointsForGenerateTransformedValuesCandle: FloatArray = FloatArray(1) + + /** + * Transforms an List of Entry into a float array containing the x and + * y values transformed with all matrices for the CANDLESTICKCHART. + * + * @param data + * @return + */ + fun generateTransformedValuesCandle( + data: ICandleDataSet, + phaseX: Float, phaseY: Float, from: Int, to: Int + ): FloatArray { + val count = ((to - from) * phaseX + 1).toInt() * 2 + + if (valuePointsForGenerateTransformedValuesCandle.size != count) { + valuePointsForGenerateTransformedValuesCandle = FloatArray(count) + } + val valuePoints = valuePointsForGenerateTransformedValuesCandle + + var j = 0 + while (j < count) { + val e = data.getEntryForIndex(j / 2 + from) + + valuePoints[j] = e.x + valuePoints[j + 1] = e.high * phaseY + j += 2 + } + + this.valueToPixelMatrix.mapPoints(valuePoints) + + return valuePoints + } + + /** + * transform a path with all the given matrices VERY IMPORTANT: keep order + * to value-touch-offset + * + * @param path + */ + fun pathValueToPixel(path: Path) { + path.transform(this.valueMatrix) + path.transform(mViewPortHandler.matrixTouch) + path.transform(this.offsetMatrix) + } + + /** + * Transforms multiple paths will all matrices. + * + * @param paths + */ + fun pathValuesToPixel(paths: MutableList) { + for (i in paths.indices) { + pathValueToPixel(paths[i]) + } + } + + /** + * Transform an array of points with all matrices. VERY IMPORTANT: Keep + * matrix order "value-touch-offset" when transforming. + * + * @param pts + */ + fun pointValuesToPixel(pts: FloatArray?) { + valueMatrix.mapPoints(pts) + mViewPortHandler.matrixTouch.mapPoints(pts) + offsetMatrix.mapPoints(pts) + } + + /** + * Transform a rectangle with all matrices. + * + * @param r + */ + fun rectValueToPixel(r: RectF?) { + valueMatrix.mapRect(r) + mViewPortHandler.matrixTouch.mapRect(r) + offsetMatrix.mapRect(r) + } + + /** + * Transform a rectangle with all matrices with potential animation phases. + * + * @param r + * @param phaseY + */ + fun rectToPixelPhase(r: RectF, phaseY: Float) { + // multiply the height of the rect with the phase + + r.top *= phaseY + r.bottom *= phaseY + + valueMatrix.mapRect(r) + mViewPortHandler.matrixTouch.mapRect(r) + offsetMatrix.mapRect(r) + } + + fun rectToPixelPhaseHorizontal(r: RectF, phaseY: Float) { + // multiply the height of the rect with the phase + + r.left *= phaseY + r.right *= phaseY + + valueMatrix.mapRect(r) + mViewPortHandler.matrixTouch.mapRect(r) + offsetMatrix.mapRect(r) + } + + /** + * Transform a rectangle with all matrices with potential animation phases. + * + * @param r + */ + fun rectValueToPixelHorizontal(r: RectF?) { + valueMatrix.mapRect(r) + mViewPortHandler.matrixTouch.mapRect(r) + offsetMatrix.mapRect(r) + } + + /** + * Transform a rectangle with all matrices with potential animation phases. + * + * @param r + * @param phaseY + */ + fun rectValueToPixelHorizontal(r: RectF, phaseY: Float) { + // multiply the height of the rect with the phase + + r.left *= phaseY + r.right *= phaseY + + valueMatrix.mapRect(r) + mViewPortHandler.matrixTouch.mapRect(r) + offsetMatrix.mapRect(r) + } + + /** + * transforms multiple rects with all matrices + * + * @param rects + */ + fun rectValuesToPixel(rects: MutableList) { + val m = this.valueToPixelMatrix + + for (i in rects.indices) m.mapRect(rects[i]) + } + + protected var mPixelToValueMatrixBuffer: Matrix = Matrix() + + /** + * Transforms the given array of touch positions (pixels) (x, y, x, y, ...) + * into values on the chart. + * + * @param pixels + */ + fun pixelsToValue(pixels: FloatArray?) { + val tmp = mPixelToValueMatrixBuffer + tmp.reset() + + // invert all matrixes to convert back to the original value + offsetMatrix.invert(tmp) + tmp.mapPoints(pixels) + + mViewPortHandler.matrixTouch.invert(tmp) + tmp.mapPoints(pixels) + + valueMatrix.invert(tmp) + tmp.mapPoints(pixels) + } + + /** + * buffer for performance + */ + var ptsBuffer: FloatArray = FloatArray(2) + + /** + * Returns a recyclable MPPointD instance. + * returns the x and y values in the chart at the given touch point + * (encapsulated in a MPPointD). This method transforms pixel coordinates to + * coordinates / values in the chart. This is the opposite method to + * getPixelForValues(...). + * + * @param x + * @param y + * @return + */ + fun getValuesByTouchPoint(x: Float, y: Float): MPPointD { + val result: MPPointD = MPPointD.Companion.getInstance(0.0, 0.0) + getValuesByTouchPoint(x, y, result) + return result + } + + fun getValuesByTouchPoint(x: Float, y: Float, outputPoint: MPPointD) { + ptsBuffer[0] = x + ptsBuffer[1] = y + + pixelsToValue(ptsBuffer) + + outputPoint.x = ptsBuffer[0].toDouble() + outputPoint.y = ptsBuffer[1].toDouble() + } + + /** + * Returns a recyclable MPPointD instance. + * Returns the x and y coordinates (pixels) for a given x and y value in the chart. + * + * @param x + * @param y + * @return + */ + fun getPixelForValues(x: Float, y: Float): MPPointD { + ptsBuffer[0] = x + ptsBuffer[1] = y + + pointValuesToPixel(ptsBuffer) + + val xPx = ptsBuffer[0].toDouble() + val yPx = ptsBuffer[1].toDouble() + + return MPPointD.Companion.getInstance(xPx, yPx) + } + + private val mMBuffer1 = Matrix() + + val valueToPixelMatrix: Matrix + get() { + mMBuffer1.set(this.valueMatrix) + mMBuffer1.postConcat(mViewPortHandler.matrixTouch) + mMBuffer1.postConcat(this.offsetMatrix) + return mMBuffer1 + } + + private val mMBuffer2 = Matrix() + + val pixelToValueMatrix: Matrix + get() { + this.valueToPixelMatrix.invert(mMBuffer2) + return mMBuffer2 + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/TransformerHorizontalBarChart.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/TransformerHorizontalBarChart.java deleted file mode 100644 index 05fa82ae29..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/TransformerHorizontalBarChart.java +++ /dev/null @@ -1,44 +0,0 @@ - -package com.github.mikephil.charting.utils; - -/** - * Transformer class for the HorizontalBarChart. - * - * @author Philipp Jahoda - */ -public class TransformerHorizontalBarChart extends Transformer { - - public TransformerHorizontalBarChart(ViewPortHandler viewPortHandler) { - super(viewPortHandler); - } - - /** - * Prepares the matrix that contains all offsets. - * - * @param inverted - */ - public void prepareMatrixOffset(boolean inverted) { - - mMatrixOffset.reset(); - - // offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); - - if (!inverted) - mMatrixOffset.postTranslate(mViewPortHandler.offsetLeft(), - mViewPortHandler.getChartHeight() - mViewPortHandler.offsetBottom()); - else { - mMatrixOffset - .setTranslate( - -(mViewPortHandler.getChartWidth() - mViewPortHandler.offsetRight()), - mViewPortHandler.getChartHeight() - mViewPortHandler.offsetBottom()); - mMatrixOffset.postScale(-1.0f, 1.0f); - } - - // mMatrixOffset.set(offset); - - // mMatrixOffset.reset(); - // - // mMatrixOffset.postTranslate(mOffsetLeft, getHeight() - - // mOffsetBottom); - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/TransformerHorizontalBarChart.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/TransformerHorizontalBarChart.kt new file mode 100644 index 0000000000..5802d25e8d --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/TransformerHorizontalBarChart.kt @@ -0,0 +1,38 @@ +package com.github.mikephil.charting.utils + +/** + * Transformer class for the HorizontalBarChart. + * + * @author Philipp Jahoda + */ +class TransformerHorizontalBarChart(viewPortHandler: ViewPortHandler) : Transformer(viewPortHandler) { + /** + * Prepares the matrix that contains all offsets. + * + * @param inverted + */ + override fun prepareMatrixOffset(inverted: Boolean) { + offsetMatrix.reset() + + // offset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); + if (!inverted) offsetMatrix.postTranslate( + mViewPortHandler.offsetLeft(), + mViewPortHandler.chartHeight - mViewPortHandler.offsetBottom() + ) + else { + offsetMatrix + .setTranslate( + -(mViewPortHandler.chartWidth - mViewPortHandler.offsetRight()), + mViewPortHandler.chartHeight - mViewPortHandler.offsetBottom() + ) + offsetMatrix.postScale(-1.0f, 1.0f) + } + + // mMatrixOffset.set(offset); + + // mMatrixOffset.reset(); + // + // mMatrixOffset.postTranslate(mOffsetLeft, getHeight() - + // mOffsetBottom); + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Utils.java b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Utils.java deleted file mode 100644 index 0f2296da45..0000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Utils.java +++ /dev/null @@ -1,761 +0,0 @@ - -package com.github.mikephil.charting.utils; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.text.Layout; -import android.text.StaticLayout; -import android.text.TextPaint; -import android.util.DisplayMetrics; -import android.util.Log; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; - -import com.github.mikephil.charting.formatter.DefaultValueFormatter; -import com.github.mikephil.charting.formatter.IValueFormatter; - -import java.util.List; - -/** - * Utilities class that has some helper methods. Needs to be initialized by - * calling Utils.init(...) before usage. Inside the Chart.init() method, this is - * done, if the Utils are used before that, Utils.init(...) needs to be called - * manually. - * - * @author Philipp Jahoda - */ -@SuppressWarnings("JavaDoc") -public abstract class Utils { - - private static DisplayMetrics mMetrics; - private static int mMinimumFlingVelocity = 50; - private static int mMaximumFlingVelocity = 8000; - public final static double DEG2RAD = (Math.PI / 180.0); - public final static float FDEG2RAD = ((float) Math.PI / 180.f); - - @SuppressWarnings("unused") - public final static double DOUBLE_EPSILON = Double.longBitsToDouble(1); - - @SuppressWarnings("unused") - public final static float FLOAT_EPSILON = Float.intBitsToFloat(1); - - /** - * initialize method, called inside the Chart.init() method. - */ - @SuppressWarnings("deprecation") - public static void init(Context context) { - - if (context == null) { - // noinspection deprecation - mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); - // noinspection deprecation - mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); - - Log.e("MPChartLib-Utils" - , "Utils.init(...) PROVIDED CONTEXT OBJECT IS NULL"); - - } else { - ViewConfiguration viewConfiguration = ViewConfiguration.get(context); - mMinimumFlingVelocity = viewConfiguration.getScaledMinimumFlingVelocity(); - mMaximumFlingVelocity = viewConfiguration.getScaledMaximumFlingVelocity(); - - Resources res = context.getResources(); - mMetrics = res.getDisplayMetrics(); - } - } - - /** - * initialize method, called inside the Chart.init() method. backwards - * compatibility - to not break existing code - * - * @param res - */ - @Deprecated - public static void init(Resources res) { - - mMetrics = res.getDisplayMetrics(); - - // noinspection deprecation - mMinimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity(); - // noinspection deprecation - mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); - } - - /** - * This method converts dp unit to equivalent pixels, depending on device - * density. NEEDS UTILS TO BE INITIALIZED BEFORE USAGE. - * - * @param dp A value in dp (density independent pixels) unit. Which we need - * to convert into pixels - * @return A float value to represent px equivalent to dp depending on - * device density - */ - public static float convertDpToPixel(float dp) { - - if (mMetrics == null) { - - Log.e("MPChartLib-Utils", - "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before" + - " calling Utils.convertDpToPixel(...). Otherwise conversion does not " + - "take place."); - return dp; - } - - return dp * mMetrics.density; - } - - /** - * This method converts device specific pixels to density independent - * pixels. NEEDS UTILS TO BE INITIALIZED BEFORE USAGE. - * - * @param px A value in px (pixels) unit. Which we need to convert into db - * @return A float value to represent dp equivalent to px value - */ - public static float convertPixelsToDp(float px) { - - if (mMetrics == null) { - - Log.e("MPChartLib-Utils", - "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before" + - " calling Utils.convertPixelsToDp(...). Otherwise conversion does not" + - " take place."); - return px; - } - - return px / mMetrics.density; - } - - /** - * calculates the approximate width of a text, depending on a demo text - * avoid repeated calls (e.g. inside drawing methods) - * - * @param paint - * @param demoText - * @return - */ - public static int calcTextWidth(Paint paint, String demoText) { - return (int) paint.measureText(demoText); - } - - private static final Rect mCalcTextHeightRect = new Rect(); - /** - * calculates the approximate height of a text, depending on a demo text - * avoid repeated calls (e.g. inside drawing methods) - * - * @param paint - * @param demoText - * @return - */ - public static int calcTextHeight(Paint paint, String demoText) { - - Rect r = mCalcTextHeightRect; - r.set(0,0,0,0); - paint.getTextBounds(demoText, 0, demoText.length(), r); - return r.height(); - } - - private static final Paint.FontMetrics mFontMetrics = new Paint.FontMetrics(); - - public static float getLineHeight(Paint paint) { - return getLineHeight(paint, mFontMetrics); - } - - public static float getLineHeight(Paint paint, Paint.FontMetrics fontMetrics){ - paint.getFontMetrics(fontMetrics); - return fontMetrics.descent - fontMetrics.ascent; - } - - public static float getLineSpacing(Paint paint) { - return getLineSpacing(paint, mFontMetrics); - } - - public static float getLineSpacing(Paint paint, Paint.FontMetrics fontMetrics){ - paint.getFontMetrics(fontMetrics); - return fontMetrics.ascent - fontMetrics.top + fontMetrics.bottom; - } - - /** - * Returns a recyclable FSize instance. - * calculates the approximate size of a text, depending on a demo text - * avoid repeated calls (e.g. inside drawing methods) - * - * @param paint - * @param demoText - * @return A Recyclable FSize instance - */ - public static FSize calcTextSize(Paint paint, String demoText) { - - FSize result = FSize.getInstance(0,0); - calcTextSize(paint, demoText, result); - return result; - } - - private static final Rect mCalcTextSizeRect = new Rect(); - /** - * calculates the approximate size of a text, depending on a demo text - * avoid repeated calls (e.g. inside drawing methods) - * - * @param paint - * @param demoText - * @param outputFSize An output variable, modified by the function. - */ - public static void calcTextSize(Paint paint, String demoText, FSize outputFSize) { - - Rect r = mCalcTextSizeRect; - r.set(0,0,0,0); - paint.getTextBounds(demoText, 0, demoText.length(), r); - outputFSize.width = r.width(); - outputFSize.height = r.height(); - - } - - - /** - * Math.pow(...) is very expensive, so avoid calling it and create it - * yourself. - */ - private static final int[] POW_10 = { - 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 - }; - - private static final IValueFormatter mDefaultValueFormatter = generateDefaultValueFormatter(); - - private static IValueFormatter generateDefaultValueFormatter() { - return new DefaultValueFormatter(1); - } - - /// - returns: The default value formatter used for all chart components that needs a default - public static IValueFormatter getDefaultValueFormatter() - { - return mDefaultValueFormatter; - } - - /** - * Formats the given number to the given number of decimals, and returns the - * number as a string, maximum 35 characters. If thousands are separated, the separating - * character is a dot ("."). - * - * @param number - * @param digitCount - * @param separateThousands set this to true to separate thousands values - * @return - */ - public static String formatNumber(float number, int digitCount, boolean separateThousands) { - return formatNumber(number, digitCount, separateThousands, '.'); - } - - /** - * Formats the given number to the given number of decimals, and returns the - * number as a string, maximum 35 characters. - * - * @param number - * @param digitCount - * @param separateThousands set this to true to separate thousands values - * @param separateChar a caracter to be paced between the "thousands" - * @return - */ - public static String formatNumber(float number, int digitCount, boolean separateThousands, - char separateChar) { - - char[] out = new char[35]; - - boolean neg = false; - if (number == 0) { - return "0"; - } - - boolean zero = number < 1 && number > -1; - - if (number < 0) { - neg = true; - number = -number; - } - - if (digitCount > POW_10.length) { - digitCount = POW_10.length - 1; - } - - number *= POW_10[digitCount]; - long lval = Math.round(number); - int ind = out.length - 1; - int charCount = 0; - boolean decimalPointAdded = false; - - while (lval != 0 || charCount < (digitCount + 1)) { - int digit = (int) (lval % 10); - lval = lval / 10; - out[ind--] = (char) (digit + '0'); - charCount++; - - // add decimal point - if (charCount == digitCount) { - out[ind--] = ','; - charCount++; - decimalPointAdded = true; - - // add thousand separators - } else if (separateThousands && lval != 0 && charCount > digitCount) { - - if (decimalPointAdded) { - - if ((charCount - digitCount) % 4 == 0) { - out[ind--] = separateChar; - charCount++; - } - - } else { - - if ((charCount - digitCount) % 4 == 3) { - out[ind--] = separateChar; - charCount++; - } - } - } - } - - // if number around zero (between 1 and -1) - if (zero) { - out[ind--] = '0'; - charCount += 1; - } - - // if the number is negative - if (neg) { - out[ind--] = '-'; - charCount += 1; - } - - int start = out.length - charCount; - - // use this instead of "new String(...)" because of issue < Android 4.0 - return String.valueOf(out, start, out.length - start); - } - - /** - * rounds the given number to the next significant number - * - * @param number - * @return - */ - public static float roundToNextSignificant(double number) { - if (Double.isInfinite(number) || - Double.isNaN(number) || - number == 0.0) - return 0; - - final float d = (float) Math.ceil((float) Math.log10(number < 0 ? -number : number)); - final int pw = 1 - (int) d; - final float magnitude = (float) Math.pow(10, pw); - final long shifted = Math.round(number * magnitude); - return shifted / magnitude; - } - - /** - * Returns the appropriate number of decimals to be used for the provided - * number. - * - * @param number - * @return - */ - public static int getDecimals(float number) { - - float i = roundToNextSignificant(number); - - if (Float.isInfinite(i)) - return 0; - - return (int) Math.ceil(-Math.log10(i)) + 2; - } - - /** - * Converts the provided Integer List to an int array. - * - * @param integers - * @return - */ - public static int[] convertIntegers(List integers) { - - int[] ret = new int[integers.size()]; - - copyIntegers(integers, ret); - - return ret; - } - - public static void copyIntegers(List from, int[] to){ - int count = Math.min(to.length, from.size()); - for(int i = 0 ; i < count ; i++){ - to[i] = from.get(i); - } - } - - /** - * Converts the provided String List to a String array. - * - * @param strings - * @return - */ - public static String[] convertStrings(List strings) { - - String[] ret = new String[strings.size()]; - - for (int i = 0; i < ret.length; i++) { - ret[i] = strings.get(i); - } - - return ret; - } - - /** - * Replacement for the Math.nextUp(...) method that is only available in - * HONEYCOMB and higher. Dat's some seeeeek sheeet. - * - * @param d - * @return - */ - public static double nextUp(double d) { - if (d == Double.POSITIVE_INFINITY) - return d; - else { - d += 0.0d; - return Double.longBitsToDouble(Double.doubleToRawLongBits(d) + - ((d >= 0.0d) ? +1L : -1L)); - } - } - - /** - * Returns a recyclable MPPointF instance. - * 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 - */ - public static MPPointF getPosition(MPPointF center, float dist, float angle) { - - MPPointF p = MPPointF.getInstance(0,0); - getPosition(center, dist, angle, p); - return p; - } - - public static void getPosition(MPPointF center, float dist, float angle, MPPointF outputPoint){ - outputPoint.x = (float) (center.x + dist * Math.cos(Math.toRadians(angle))); - outputPoint.y = (float) (center.y + dist * Math.sin(Math.toRadians(angle))); - } - - public static void velocityTrackerPointerUpCleanUpIfNecessary(MotionEvent ev, - VelocityTracker tracker) { - - // Check the dot product of current velocities. - // If the pointer that left was opposing another velocity vector, clear. - tracker.computeCurrentVelocity(1000, mMaximumFlingVelocity); - final int upIndex = ev.getActionIndex(); - final int id1 = ev.getPointerId(upIndex); - final float x1 = tracker.getXVelocity(id1); - final float y1 = tracker.getYVelocity(id1); - for (int i = 0, count = ev.getPointerCount(); i < count; i++) { - if (i == upIndex) - continue; - - final int id2 = ev.getPointerId(i); - final float x = x1 * tracker.getXVelocity(id2); - final float y = y1 * tracker.getYVelocity(id2); - - final float dot = x + y; - if (dot < 0) { - tracker.clear(); - break; - } - } - } - - /** - * Original method view.postInvalidateOnAnimation() only supportd in API >= - * 16, This is a replica of the code from ViewCompat. - * - * @param view - */ - @SuppressLint("NewApi") - public static void postInvalidateOnAnimation(View view) { - view.postInvalidateOnAnimation(); - } - - public static int getMinimumFlingVelocity() { - return mMinimumFlingVelocity; - } - - public static int getMaximumFlingVelocity() { - return mMaximumFlingVelocity; - } - - /** - * returns an angle between 0.f < 360.f (not less than zero, less than 360) - */ - public static float getNormalizedAngle(float angle) { - while (angle < 0.f) - angle += 360.f; - - return angle % 360.f; - } - - private static final Rect mDrawableBoundsCache = new Rect(); - - public static void drawImage(Canvas canvas, - Drawable drawable, - int x, int y, - int width, int height) { - - MPPointF drawOffset = MPPointF.getInstance(); - drawOffset.x = x - (width / 2); - drawOffset.y = y - (height / 2); - - drawable.copyBounds(mDrawableBoundsCache); - drawable.setBounds( - mDrawableBoundsCache.left, - mDrawableBoundsCache.top, - mDrawableBoundsCache.left + width, - mDrawableBoundsCache.top + width); - - int saveId = canvas.save(); - // translate to the correct position and draw - canvas.translate(drawOffset.x, drawOffset.y); - drawable.draw(canvas); - canvas.restoreToCount(saveId); - } - - private static final Rect mDrawTextRectBuffer = new Rect(); - private static final Paint.FontMetrics mFontMetricsBuffer = new Paint.FontMetrics(); - - public static void drawXAxisValue(Canvas c, String text, float x, float y, - Paint paint, - MPPointF anchor, float angleDegrees) { - - float drawOffsetX = 0.f; - float drawOffsetY = 0.f; - - final float lineHeight = paint.getFontMetrics(mFontMetricsBuffer); - paint.getTextBounds(text, 0, text.length(), mDrawTextRectBuffer); - - // Android sometimes has pre-padding - drawOffsetX -= mDrawTextRectBuffer.left; - - // Android does not snap the bounds to line boundaries, - // and draws from bottom to top. - // And we want to normalize it. - drawOffsetY -= mFontMetricsBuffer.ascent; - - // To have a consistent point of reference, we always draw left-aligned - Paint.Align originalTextAlign = paint.getTextAlign(); - paint.setTextAlign(Paint.Align.LEFT); - - if (angleDegrees != 0.f) { - - // Move the text drawing rect in a way that it always rotates around its center - drawOffsetX -= mDrawTextRectBuffer.width() * 0.5f; - drawOffsetY -= lineHeight * 0.5f; - - float translateX = x; - float translateY = y; - - // Move the "outer" rect relative to the anchor, assuming its centered - if (anchor.x != 0.5f || anchor.y != 0.5f) { - final FSize rotatedSize = getSizeOfRotatedRectangleByDegrees( - mDrawTextRectBuffer.width(), - lineHeight, - angleDegrees); - - translateX -= rotatedSize.width * (anchor.x - 0.5f); - translateY -= rotatedSize.height * (anchor.y - 0.5f); - FSize.recycleInstance(rotatedSize); - } - - c.save(); - c.translate(translateX, translateY); - c.rotate(angleDegrees); - - c.drawText(text, drawOffsetX, drawOffsetY, paint); - - c.restore(); - } else { - if (anchor.x != 0.f || anchor.y != 0.f) { - - drawOffsetX -= mDrawTextRectBuffer.width() * anchor.x; - drawOffsetY -= lineHeight * anchor.y; - } - - drawOffsetX += x; - drawOffsetY += y; - - c.drawText(text, drawOffsetX, drawOffsetY, paint); - } - - paint.setTextAlign(originalTextAlign); - } - - public static void drawMultilineText(Canvas c, StaticLayout textLayout, - float x, float y, - TextPaint paint, - MPPointF anchor, float angleDegrees) { - - float drawOffsetX = 0.f; - float drawOffsetY = 0.f; - float drawWidth; - float drawHeight; - - final float lineHeight = paint.getFontMetrics(mFontMetricsBuffer); - - drawWidth = textLayout.getWidth(); - drawHeight = textLayout.getLineCount() * lineHeight; - - // Android sometimes has pre-padding - drawOffsetX -= mDrawTextRectBuffer.left; - - // Android does not snap the bounds to line boundaries, - // and draws from bottom to top. - // And we want to normalize it. - drawOffsetY += drawHeight; - - // To have a consistent point of reference, we always draw left-aligned - Paint.Align originalTextAlign = paint.getTextAlign(); - paint.setTextAlign(Paint.Align.LEFT); - - if (angleDegrees != 0.f) { - - // Move the text drawing rect in a way that it always rotates around its center - drawOffsetX -= drawWidth * 0.5f; - drawOffsetY -= drawHeight * 0.5f; - - float translateX = x; - float translateY = y; - - // Move the "outer" rect relative to the anchor, assuming its centered - if (anchor.x != 0.5f || anchor.y != 0.5f) { - final FSize rotatedSize = getSizeOfRotatedRectangleByDegrees( - drawWidth, - drawHeight, - angleDegrees); - - translateX -= rotatedSize.width * (anchor.x - 0.5f); - translateY -= rotatedSize.height * (anchor.y - 0.5f); - FSize.recycleInstance(rotatedSize); - } - - c.save(); - c.translate(translateX, translateY); - c.rotate(angleDegrees); - - c.translate(drawOffsetX, drawOffsetY); - textLayout.draw(c); - - c.restore(); - } else { - if (anchor.x != 0.f || anchor.y != 0.f) { - - drawOffsetX -= drawWidth * anchor.x; - drawOffsetY -= drawHeight * anchor.y; - } - - drawOffsetX += x; - drawOffsetY += y; - - c.save(); - - c.translate(drawOffsetX, drawOffsetY); - textLayout.draw(c); - - c.restore(); - } - - paint.setTextAlign(originalTextAlign); - } - - public static void drawMultilineText(Canvas c, String text, - float x, float y, - TextPaint paint, - FSize constrainedToSize, - MPPointF anchor, float angleDegrees) { - - StaticLayout textLayout = new StaticLayout( - text, 0, text.length(), - paint, - (int) Math.max(Math.ceil(constrainedToSize.width), 1.f), - Layout.Alignment.ALIGN_NORMAL, 1.f, 0.f, false); - - - drawMultilineText(c, textLayout, x, y, paint, anchor, angleDegrees); - } - - /** - * Returns a recyclable FSize instance. - * Represents size of a rotated rectangle by degrees. - * - * @param rectangleSize - * @param degrees - * @return A Recyclable FSize instance - */ - public static FSize getSizeOfRotatedRectangleByDegrees(FSize rectangleSize, float degrees) { - final float radians = degrees * FDEG2RAD; - return getSizeOfRotatedRectangleByRadians(rectangleSize.width, rectangleSize.height, - radians); - } - - /** - * Returns a recyclable FSize instance. - * Represents size of a rotated rectangle by radians. - * - * @param rectangleSize - * @param radians - * @return A Recyclable FSize instance - */ - public static FSize getSizeOfRotatedRectangleByRadians(FSize rectangleSize, float radians) { - return getSizeOfRotatedRectangleByRadians(rectangleSize.width, rectangleSize.height, - radians); - } - - /** - * Returns a recyclable FSize instance. - * Represents size of a rotated rectangle by degrees. - * - * @param rectangleWidth - * @param rectangleHeight - * @param degrees - * @return A Recyclable FSize instance - */ - public static FSize getSizeOfRotatedRectangleByDegrees(float rectangleWidth, float - rectangleHeight, float degrees) { - final float radians = degrees * FDEG2RAD; - return getSizeOfRotatedRectangleByRadians(rectangleWidth, rectangleHeight, radians); - } - - /** - * Returns a recyclable FSize instance. - * Represents size of a rotated rectangle by radians. - * - * @param rectangleWidth - * @param rectangleHeight - * @param radians - * @return A Recyclable FSize instance - */ - public static FSize getSizeOfRotatedRectangleByRadians(float rectangleWidth, float - rectangleHeight, float radians) { - return FSize.getInstance( - Math.abs(rectangleWidth * (float) Math.cos(radians)) + Math.abs(rectangleHeight * - (float) Math.sin(radians)), - Math.abs(rectangleWidth * (float) Math.sin(radians)) + Math.abs(rectangleHeight * - (float) Math.cos(radians)) - ); - } - - public static int getSDKInt() { - return android.os.Build.VERSION.SDK_INT; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Utils.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Utils.kt new file mode 100644 index 0000000000..5ca117bad7 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/Utils.kt @@ -0,0 +1,748 @@ +package com.github.mikephil.charting.utils + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.Resources +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Paint.Align +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.os.Build +import android.text.Layout +import android.text.StaticLayout +import android.text.TextPaint +import android.util.DisplayMetrics +import android.util.Log +import android.view.MotionEvent +import android.view.VelocityTracker +import android.view.View +import android.view.ViewConfiguration +import androidx.core.graphics.withTranslation +import com.github.mikephil.charting.formatter.DefaultValueFormatter +import com.github.mikephil.charting.formatter.IValueFormatter +import kotlin.math.abs +import kotlin.math.ceil +import kotlin.math.cos +import kotlin.math.log10 +import kotlin.math.max +import kotlin.math.min +import kotlin.math.pow +import kotlin.math.roundToInt +import kotlin.math.sin + +/** + * Utilities class that has some helper methods. Needs to be initialized by + * calling Utils.init(...) before usage. Inside the Chart.init() method, this is + * done, if the Utils are used before that, Utils.init(...) needs to be called + * manually. + * + * @author Philipp Jahoda + */ +object Utils { + private var mMetrics: DisplayMetrics? = null + var minimumFlingVelocity: Int = 50 + private set + var maximumFlingVelocity: Int = 8000 + private set + const val DEG2RAD: Double = (Math.PI / 180.0) + const val FDEG2RAD: Float = (Math.PI.toFloat() / 180f) + + @Suppress("unused") + val DOUBLE_EPSILON: Double = java.lang.Double.longBitsToDouble(1) + + @Suppress("unused") + val FLOAT_EPSILON: Float = java.lang.Float.intBitsToFloat(1) + + /** + * initialize method, called inside the Chart.init() method. + */ + @Suppress("deprecation") + fun init(context: Context?) { + if (context == null) { + // noinspection deprecation + minimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity() + // noinspection deprecation + maximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity() + + Log.e( + "MPChartLib-Utils", + "Utils.init(...) PROVIDED CONTEXT OBJECT IS NULL" + ) + } else { + val viewConfiguration = ViewConfiguration.get(context) + minimumFlingVelocity = viewConfiguration.scaledMinimumFlingVelocity + maximumFlingVelocity = viewConfiguration.scaledMaximumFlingVelocity + + val res = context.resources + mMetrics = res.displayMetrics + } + } + + /** + * initialize method, called inside the Chart.init() method. backwards + * compatibility - to not break existing code + * + * @param res + */ + @Deprecated("") + fun init(res: Resources) { + mMetrics = res.displayMetrics + + // noinspection deprecation + minimumFlingVelocity = ViewConfiguration.getMinimumFlingVelocity() + // noinspection deprecation + maximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity() + } + + /** + * This method converts dp unit to equivalent pixels, depending on device + * density. NEEDS UTILS TO BE INITIALIZED BEFORE USAGE. + * + * @param dp A value in dp (density independent pixels) unit. Which we need + * to convert into pixels + * @return A float value to represent px equivalent to dp depending on + * device density + */ + @JvmStatic + fun convertDpToPixel(dp: Float): Float { + if (mMetrics == null) { + Log.e( + "MPChartLib-Utils", + "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before" + + " calling Utils.convertDpToPixel(...). Otherwise conversion does not " + + "take place." + ) + return dp + } + + return dp * mMetrics!!.density + } + + /** + * This method converts device specific pixels to density independent + * pixels. NEEDS UTILS TO BE INITIALIZED BEFORE USAGE. + * + * @param px A value in px (pixels) unit. Which we need to convert into db + * @return A float value to represent dp equivalent to px value + */ + fun convertPixelsToDp(px: Float): Float { + if (mMetrics == null) { + Log.e( + "MPChartLib-Utils", + "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before" + + " calling Utils.convertPixelsToDp(...). Otherwise conversion does not" + + " take place." + ) + return px + } + + return px / mMetrics!!.density + } + + /** + * calculates the approximate width of a text, depending on a demo text + * avoid repeated calls (e.g. inside drawing methods) + * + * @param paint + * @param demoText + * @return + */ + fun calcTextWidth(paint: Paint, demoText: String?): Int { + return paint.measureText(demoText).toInt() + } + + private val mCalcTextHeightRect by lazy { Rect() } + + /** + * calculates the approximate height of a text, depending on a demo text + * avoid repeated calls (e.g. inside drawing methods) + * + * @param paint + * @param demoText + * @return + */ + fun calcTextHeight(paint: Paint, demoText: String): Int { + val r = mCalcTextHeightRect + r.set(0, 0, 0, 0) + paint.getTextBounds(demoText, 0, demoText.length, r) + return r.height() + } + + private val mFontMetrics = Paint.FontMetrics() + + fun getLineHeight(paint: Paint): Float { + return getLineHeight(paint, mFontMetrics) + } + + fun getLineHeight(paint: Paint, fontMetrics: Paint.FontMetrics): Float { + paint.getFontMetrics(fontMetrics) + return fontMetrics.descent - fontMetrics.ascent + } + + fun getLineSpacing(paint: Paint): Float { + return getLineSpacing(paint, mFontMetrics) + } + + fun getLineSpacing(paint: Paint, fontMetrics: Paint.FontMetrics): Float { + paint.getFontMetrics(fontMetrics) + return fontMetrics.ascent - fontMetrics.top + fontMetrics.bottom + } + + /** + * Returns a recyclable FSize instance. + * calculates the approximate size of a text, depending on a demo text + * avoid repeated calls (e.g. inside drawing methods) + * + * @param paint + * @param demoText + * @return A Recyclable FSize instance + */ + fun calcTextSize(paint: Paint, demoText: String): FSize { + val result: FSize = FSize.Companion.getInstance(0f, 0f) + calcTextSize(paint, demoText, result) + return result + } + + private val mCalcTextSizeRect = Rect() + + /** + * calculates the approximate size of a text, depending on a demo text + * avoid repeated calls (e.g. inside drawing methods) + * + * @param paint + * @param demoText + * @param outputFSize An output variable, modified by the function. + */ + fun calcTextSize(paint: Paint, demoText: String, outputFSize: FSize) { + val r = mCalcTextSizeRect + r.set(0, 0, 0, 0) + paint.getTextBounds(demoText, 0, demoText.length, r) + outputFSize.width = r.width().toFloat() + outputFSize.height = r.height().toFloat() + } + + + /** + * Math.pow(...) is very expensive, so avoid calling it and create it + * yourself. + */ + private val POW_10 = intArrayOf( + 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 + ) + + /** - returns: The default value formatter used for all chart components that needs a default */ + val defaultValueFormatter: IValueFormatter = generateDefaultValueFormatter() + + private fun generateDefaultValueFormatter(): IValueFormatter { + return DefaultValueFormatter(1) + } + + /** + * Formats the given number to the given number of decimals, and returns the + * number as a string, maximum 35 characters. + * + * @param number + * @param digitCount + * @param separateThousands set this to true to separate thousands values + * @param separateChar a caracter to be paced between the "thousands" + * @return + */ + /** + * Formats the given number to the given number of decimals, and returns the + * number as a string, maximum 35 characters. If thousands are separated, the separating + * character is a dot ("."). + * + * @param number + * @param digitCount + * @param separateThousands set this to true to separate thousands values + * @return + */ + @JvmOverloads + fun formatNumber( + number: Float, digitCount: Int, separateThousands: Boolean, + separateChar: Char = '.' + ): String { + var number = number + var digitCount = digitCount + val out = CharArray(35) + + var neg = false + if (number == 0f) { + return "0" + } + + val zero = number < 1 && number > -1 + + if (number < 0) { + neg = true + number = -number + } + + if (digitCount > POW_10.size) { + digitCount = POW_10.size - 1 + } + + number *= POW_10[digitCount].toFloat() + var lval = number.roundToInt().toLong() + var ind = out.size - 1 + var charCount = 0 + var decimalPointAdded = false + + while (lval != 0L || charCount < (digitCount + 1)) { + val digit = (lval % 10).toInt() + lval = lval / 10 + out[ind--] = (digit + '0'.code).toChar() + charCount++ + + // add decimal point + if (charCount == digitCount) { + out[ind--] = ',' + charCount++ + decimalPointAdded = true + + // add thousand separators + } else if (separateThousands && lval != 0L && charCount > digitCount) { + if (decimalPointAdded) { + if ((charCount - digitCount) % 4 == 0) { + out[ind--] = separateChar + charCount++ + } + } else { + if ((charCount - digitCount) % 4 == 3) { + out[ind--] = separateChar + charCount++ + } + } + } + } + + // if number around zero (between 1 and -1) + if (zero) { + out[ind--] = '0' + charCount += 1 + } + + // if the number is negative + if (neg) { + out[ind--] = '-' + charCount += 1 + } + + val start = out.size - charCount + + // use this instead of "new String(...)" because of issue < Android 4.0 + return String(out, start, out.size - start) + } + + /** + * rounds the given number to the next significant number + * + * @param number + * @return + */ + fun roundToNextSignificant(number: Double): Float { + if (number.isInfinite() || number.isNaN() || number == 0.0) return 0f + + val d = ceil(log10(if (number < 0) -number else number).toFloat().toDouble()).toFloat() + val pw = 1 - d.toInt() + val magnitude = 10.0.pow(pw.toDouble()).toFloat() + val shifted = (number * magnitude).roundToInt() + return shifted / magnitude + } + + /** + * Returns the appropriate number of decimals to be used for the provided + * number. + * + * @param number + * @return + */ + fun getDecimals(number: Float): Int { + val i = roundToNextSignificant(number.toDouble()) + + if (i.isInfinite()) return 0 + + return ceil(-log10(i.toDouble())).toInt() + 2 + } + + /** + * Converts the provided Integer List to an int array. + * + * @param integers + * @return + */ + fun convertIntegers(integers: MutableList): IntArray { + val ret = IntArray(integers.size) + + copyIntegers(integers, ret) + + return ret + } + + fun copyIntegers(from: MutableList, to: IntArray) { + val count = min(to.size, from.size) + for (i in 0..): Array { + val ret = arrayOfNulls(strings.size) + + for (i in ret.indices) { + ret[i] = strings[i] + } + + return ret + } + + /** + * Replacement for the Math.nextUp(...) method that is only available in + * HONEYCOMB and higher. Dat's some seeeeek sheeet. + * + * @param d + * @return + */ + fun nextUp(d: Double): Double { + var d = d + if (d == Double.Companion.POSITIVE_INFINITY) return d + else { + d += 0.0 + return java.lang.Double.longBitsToDouble( + java.lang.Double.doubleToRawLongBits(d) + + (if (d >= 0.0) +1L else -1L) + ) + } + } + + /** + * Returns a recyclable MPPointF instance. + * 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 + */ + fun getPosition(center: MPPointF, dist: Float, angle: Float): MPPointF { + val p: MPPointF = MPPointF.Companion.getInstance(0f, 0f) + getPosition(center, dist, angle, p) + return p + } + + fun getPosition(center: MPPointF, dist: Float, angle: Float, outputPoint: MPPointF) { + outputPoint.x = (center.x + dist * cos(Math.toRadians(angle.toDouble()))).toFloat() + outputPoint.y = (center.y + dist * sin(Math.toRadians(angle.toDouble()))).toFloat() + } + + fun velocityTrackerPointerUpCleanUpIfNecessary( + ev: MotionEvent, + tracker: VelocityTracker + ) { + // Check the dot product of current velocities. + // If the pointer that left was opposing another velocity vector, clear. + + tracker.computeCurrentVelocity(1000, maximumFlingVelocity.toFloat()) + val upIndex = ev.actionIndex + val id1 = ev.getPointerId(upIndex) + val x1 = tracker.getXVelocity(id1) + val y1 = tracker.getYVelocity(id1) + var i = 0 + val count = ev.pointerCount + while (i < count) { + if (i == upIndex) { + i++ + continue + } + + val id2 = ev.getPointerId(i) + val x = x1 * tracker.getXVelocity(id2) + val y = y1 * tracker.getYVelocity(id2) + + val dot = x + y + if (dot < 0) { + tracker.clear() + break + } + i++ + } + } + + /** + * Original method view.postInvalidateOnAnimation() only supportd in API >= + * 16, This is a replica of the code from ViewCompat. + * + * @param view + */ + @SuppressLint("NewApi") + fun postInvalidateOnAnimation(view: View) { + view.postInvalidateOnAnimation() + } + + /** + * returns an angle between 0.f < 360.f (not less than zero, less than 360) + */ + fun getNormalizedAngle(angle: Float): Float { + var angle = angle + while (angle < 0f) angle += 360f + + return angle % 360f + } + + private val mDrawableBoundsCache = Rect() + + fun drawImage( + canvas: Canvas, + drawable: Drawable?, + x: Int, y: Int, + width: Int, height: Int + ) { + val drawOffset: MPPointF = MPPointF.instance + drawOffset.x = (x - (width / 2)).toFloat() + drawOffset.y = (y - (height / 2)).toFloat() + + drawable?.copyBounds(mDrawableBoundsCache) + drawable?.setBounds( + mDrawableBoundsCache.left, + mDrawableBoundsCache.top, + mDrawableBoundsCache.left + width, + mDrawableBoundsCache.top + width + ) + + canvas.withTranslation(drawOffset.x, drawOffset.y) { + // translate to the correct position and draw + drawable?.draw(canvas) + } + } + + private val mDrawTextRectBuffer = Rect() + private val mFontMetricsBuffer = Paint.FontMetrics() + + fun drawXAxisValue( + c: Canvas?, text: String, x: Float, y: Float, + paint: Paint, + anchor: MPPointF, angleDegrees: Float + ) { + var drawOffsetX = 0f + var drawOffsetY = 0f + + val lineHeight = paint.getFontMetrics(mFontMetricsBuffer) + paint.getTextBounds(text, 0, text.length, mDrawTextRectBuffer) + + // Android sometimes has pre-padding + drawOffsetX -= mDrawTextRectBuffer.left.toFloat() + + // Android does not snap the bounds to line boundaries, + // and draws from bottom to top. + // And we want to normalize it. + drawOffsetY -= mFontMetricsBuffer.ascent + + // To have a consistent point of reference, we always draw left-aligned + val originalTextAlign = paint.textAlign + paint.textAlign = Align.LEFT + + if (angleDegrees != 0f) { + // Move the text drawing rect in a way that it always rotates around its center + + drawOffsetX -= mDrawTextRectBuffer.width() * 0.5f + drawOffsetY -= lineHeight * 0.5f + + var translateX = x + var translateY = y + + // Move the "outer" rect relative to the anchor, assuming its centered + if (anchor.x != 0.5f || anchor.y != 0.5f) { + val rotatedSize = getSizeOfRotatedRectangleByDegrees( + mDrawTextRectBuffer.width().toFloat(), + lineHeight, + angleDegrees + ) + + translateX -= rotatedSize.width * (anchor.x - 0.5f) + translateY -= rotatedSize.height * (anchor.y - 0.5f) + FSize.Companion.recycleInstance(rotatedSize) + } + + c?.withTranslation(translateX, translateY) { + rotate(angleDegrees) + + drawText(text, drawOffsetX, drawOffsetY, paint) + + } + } else { + if (anchor.x != 0f || anchor.y != 0f) { + drawOffsetX -= mDrawTextRectBuffer.width() * anchor.x + drawOffsetY -= lineHeight * anchor.y + } + + drawOffsetX += x + drawOffsetY += y + + c?.drawText(text, drawOffsetX, drawOffsetY, paint) + } + + paint.textAlign = originalTextAlign + } + + fun drawMultilineText( + c: Canvas, textLayout: StaticLayout, + x: Float, y: Float, + paint: TextPaint, + anchor: MPPointF, angleDegrees: Float + ) { + var drawOffsetX = 0f + var drawOffsetY = 0f + val drawHeight: Float + + val lineHeight = paint.getFontMetrics(mFontMetricsBuffer) + + val drawWidth = textLayout.width.toFloat() + drawHeight = textLayout.lineCount * lineHeight + + // Android sometimes has pre-padding + drawOffsetX -= mDrawTextRectBuffer.left.toFloat() + + // Android does not snap the bounds to line boundaries, + // and draws from bottom to top. + // And we want to normalize it. + drawOffsetY += drawHeight + + // To have a consistent point of reference, we always draw left-aligned + val originalTextAlign = paint.textAlign + paint.textAlign = Align.LEFT + + if (angleDegrees != 0f) { + // Move the text drawing rect in a way that it always rotates around its center + + drawOffsetX -= drawWidth * 0.5f + drawOffsetY -= drawHeight * 0.5f + + var translateX = x + var translateY = y + + // Move the "outer" rect relative to the anchor, assuming its centered + if (anchor.x != 0.5f || anchor.y != 0.5f) { + val rotatedSize = getSizeOfRotatedRectangleByDegrees( + drawWidth, + drawHeight, + angleDegrees + ) + + translateX -= rotatedSize.width * (anchor.x - 0.5f) + translateY -= rotatedSize.height * (anchor.y - 0.5f) + FSize.Companion.recycleInstance(rotatedSize) + } + + c.withTranslation(translateX, translateY) { + rotate(angleDegrees) + + translate(drawOffsetX, drawOffsetY) + textLayout.draw(this) + } + } else { + if (anchor.x != 0f || anchor.y != 0f) { + drawOffsetX -= drawWidth * anchor.x + drawOffsetY -= drawHeight * anchor.y + } + + drawOffsetX += x + drawOffsetY += y + + c.withTranslation(drawOffsetX, drawOffsetY) { + textLayout.draw(this) + } + } + + paint.textAlign = originalTextAlign + } + + fun drawMultilineText( + c: Canvas, text: String, + x: Float, y: Float, + paint: TextPaint, + constrainedToSize: FSize, + anchor: MPPointF, angleDegrees: Float + ) { + val textLayout = StaticLayout( + text, 0, text.length, + paint, + max(ceil(constrainedToSize.width.toDouble()), 1.0).toInt(), + Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false + ) + + + drawMultilineText(c, textLayout, x, y, paint, anchor, angleDegrees) + } + + /** + * Returns a recyclable FSize instance. + * Represents size of a rotated rectangle by degrees. + * + * @param rectangleSize + * @param degrees + * @return A Recyclable FSize instance + */ + fun getSizeOfRotatedRectangleByDegrees(rectangleSize: FSize, degrees: Float): FSize { + val radians = degrees * FDEG2RAD + return getSizeOfRotatedRectangleByRadians( + rectangleSize.width, rectangleSize.height, + radians + ) + } + + /** + * Returns a recyclable FSize instance. + * Represents size of a rotated rectangle by radians. + * + * @param rectangleSize + * @param radians + * @return A Recyclable FSize instance + */ + fun getSizeOfRotatedRectangleByRadians(rectangleSize: FSize, radians: Float): FSize { + return getSizeOfRotatedRectangleByRadians( + rectangleSize.width, rectangleSize.height, + radians + ) + } + + /** + * Returns a recyclable FSize instance. + * Represents size of a rotated rectangle by degrees. + * + * @param rectangleWidth + * @param rectangleHeight + * @param degrees + * @return A Recyclable FSize instance + */ + fun getSizeOfRotatedRectangleByDegrees(rectangleWidth: Float, rectangleHeight: Float, degrees: Float): FSize { + val radians = degrees * FDEG2RAD + return getSizeOfRotatedRectangleByRadians(rectangleWidth, rectangleHeight, radians) + } + + /** + * Returns a recyclable FSize instance. + * Represents size of a rotated rectangle by radians. + * + * @param rectangleWidth + * @param rectangleHeight + * @param radians + * @return A Recyclable FSize instance + */ + fun getSizeOfRotatedRectangleByRadians(rectangleWidth: Float, rectangleHeight: Float, radians: Float): FSize { + return FSize.Companion.getInstance( + abs(rectangleWidth * cos(radians.toDouble()).toFloat()) + abs(rectangleHeight * sin(radians.toDouble()).toFloat()), + abs(rectangleWidth * sin(radians.toDouble()).toFloat()) + abs(rectangleHeight * cos(radians.toDouble()).toFloat()) + ) + } + + val sDKInt: Int + get() = Build.VERSION.SDK_INT +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ViewPortHandler.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ViewPortHandler.kt index 8b3a60022d..028a42a38e 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ViewPortHandler.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/utils/ViewPortHandler.kt @@ -327,7 +327,7 @@ open class ViewPortHandler { * @param view * @return save */ - fun centerViewPort(transformedPts: FloatArray, view: View) { + fun centerViewPort(transformedPts: FloatArray, view: View?) { val save = mCenterViewPortMatrixBuffer save.reset() save.set(matrixTouch) @@ -351,13 +351,13 @@ open class ViewPortHandler { * @param newMatrix * @return */ - fun refresh(newMatrix: Matrix, chart: View, invalidate: Boolean): Matrix { + fun refresh(newMatrix: Matrix, chart: View?, invalidate: Boolean): Matrix { matrixTouch.set(newMatrix) // make sure scale and translation are within their bounds limitTransAndScale(matrixTouch, contentRect) - if (invalidate) chart.invalidate() + if (invalidate) chart?.invalidate() newMatrix.set(matrixTouch) return newMatrix diff --git a/MPChartLib/src/test/java/com/github/mikephil/charting/test/ChartDataTest.kt b/MPChartLib/src/test/java/com/github/mikephil/charting/test/ChartDataTest.kt index 2658ee6a14..2686be28c2 100644 --- a/MPChartLib/src/test/java/com/github/mikephil/charting/test/ChartDataTest.kt +++ b/MPChartLib/src/test/java/com/github/mikephil/charting/test/ChartDataTest.kt @@ -39,13 +39,13 @@ class ChartDataTest { Assert.assertEquals(-2f, data.yMin, 0.01f) Assert.assertEquals(50f, data.yMax, 0.01f) - Assert.assertEquals(3, data.maxEntryCountSet.entryCount) + Assert.assertEquals(3, data.maxEntryCountSet?.entryCount) // now add and remove values data.addEntry(Entry(-10f, -10f), 0) Assert.assertEquals(set1, data.maxEntryCountSet) - Assert.assertEquals(4, data.maxEntryCountSet.entryCount) + Assert.assertEquals(4, data.maxEntryCountSet?.entryCount) Assert.assertEquals(-10f, data.getYMin(YAxis.AxisDependency.LEFT), 0.01f) Assert.assertEquals(50f, data.getYMax(YAxis.AxisDependency.LEFT), 0.01f) diff --git a/MPChartLib/src/test/java/com/github/mikephil/charting/test/DataSetTest.kt b/MPChartLib/src/test/java/com/github/mikephil/charting/test/DataSetTest.kt index b5db37fc29..b8d3645cd5 100644 --- a/MPChartLib/src/test/java/com/github/mikephil/charting/test/DataSetTest.kt +++ b/MPChartLib/src/test/java/com/github/mikephil/charting/test/DataSetTest.kt @@ -137,31 +137,31 @@ class DataSetTest { val set = ScatterDataSet(entries, "") var closest = set.getEntryForXValue(17f, Float.NaN, DataSet.Rounding.CLOSEST) - Assert.assertEquals(15f, closest.x, 0.01f) + Assert.assertEquals(15f, closest!!.x, 0.01f) Assert.assertEquals(5f, closest.y, 0.01f) closest = set.getEntryForXValue(17f, Float.NaN, DataSet.Rounding.DOWN) - Assert.assertEquals(15f, closest.x, 0.01f) + Assert.assertEquals(15f, closest!!.x, 0.01f) Assert.assertEquals(5f, closest.y, 0.01f) closest = set.getEntryForXValue(15f, Float.NaN, DataSet.Rounding.DOWN) - Assert.assertEquals(15f, closest.x, 0.01f) + Assert.assertEquals(15f, closest!!.x, 0.01f) Assert.assertEquals(5f, closest.y, 0.01f) closest = set.getEntryForXValue(14f, Float.NaN, DataSet.Rounding.DOWN) - Assert.assertEquals(10f, closest.x, 0.01f) + Assert.assertEquals(10f, closest!!.x, 0.01f) Assert.assertEquals(10f, closest.y, 0.01f) closest = set.getEntryForXValue(17f, Float.NaN, DataSet.Rounding.UP) - Assert.assertEquals(21f, closest.x, 0.01f) + Assert.assertEquals(21f, closest!!.x, 0.01f) Assert.assertEquals(5f, closest.y, 0.01f) closest = set.getEntryForXValue(21f, Float.NaN, DataSet.Rounding.UP) - Assert.assertEquals(21f, closest.x, 0.01f) + Assert.assertEquals(21f, closest!!.x, 0.01f) Assert.assertEquals(5f, closest.y, 0.01f) closest = set.getEntryForXValue(21f, Float.NaN, DataSet.Rounding.CLOSEST) - Assert.assertEquals(21f, closest.x, 0.01f) + Assert.assertEquals(21f, closest!!.x, 0.01f) Assert.assertEquals(5f, closest.y, 0.01f) } @@ -186,27 +186,27 @@ class DataSetTest { val set = ScatterDataSet(values, "") var closest = set.getEntryForXValue(0f, Float.NaN, DataSet.Rounding.CLOSEST) - Assert.assertEquals(0f, closest.x, 0.01f) + Assert.assertEquals(0f, closest!!.x, 0.01f) Assert.assertEquals(10f, closest.y, 0.01f) closest = set.getEntryForXValue(5f, Float.NaN, DataSet.Rounding.CLOSEST) - Assert.assertEquals(5f, closest.x, 0.01f) + Assert.assertEquals(5f, closest!!.x, 0.01f) Assert.assertEquals(80f, closest.y, 0.01f) closest = set.getEntryForXValue(5.4f, Float.NaN, DataSet.Rounding.CLOSEST) - Assert.assertEquals(5f, closest.x, 0.01f) + Assert.assertEquals(5f, closest!!.x, 0.01f) Assert.assertEquals(80f, closest.y, 0.01f) closest = set.getEntryForXValue(4.6f, Float.NaN, DataSet.Rounding.CLOSEST) - Assert.assertEquals(5f, closest.x, 0.01f) + Assert.assertEquals(5f, closest!!.x, 0.01f) Assert.assertEquals(80f, closest.y, 0.01f) closest = set.getEntryForXValue(7f, Float.NaN, DataSet.Rounding.CLOSEST) - Assert.assertEquals(7f, closest.x, 0.01f) + Assert.assertEquals(7f, closest!!.x, 0.01f) Assert.assertEquals(100f, closest.y, 0.01f) closest = set.getEntryForXValue(4f, Float.NaN, DataSet.Rounding.CLOSEST) - Assert.assertEquals(4f, closest.x, 0.01f) + Assert.assertEquals(4f, closest!!.x, 0.01f) Assert.assertEquals(60f, closest.y, 0.01f) var entries = set.getEntriesForXValue(4f) diff --git a/MPChartLib/src/test/java/com/github/mikephil/charting/test/ObjectPoolTest.kt b/MPChartLib/src/test/java/com/github/mikephil/charting/test/ObjectPoolTest.kt index bb390d30ef..26e8a2243b 100644 --- a/MPChartLib/src/test/java/com/github/mikephil/charting/test/ObjectPoolTest.kt +++ b/MPChartLib/src/test/java/com/github/mikephil/charting/test/ObjectPoolTest.kt @@ -6,13 +6,13 @@ import org.junit.Assert import org.junit.Test class ObjectPoolTest { - internal class TestPoolable private constructor(var foo: Int, var bar: Int) : Poolable() { - override fun instantiate(): Poolable { + internal class TestPoolable private constructor(var foo: Int, var bar: Int) : Poolable() { + override fun instantiate(): TestPoolable { return TestPoolable(0, 0) } companion object { - private val pool: ObjectPool = ObjectPool.create(4, TestPoolable(0, 0)) as ObjectPool + private val pool: ObjectPool = ObjectPool.create(4, TestPoolable(0, 0)) fun getInstance(foo: Int, bar: Int): TestPoolable { val result = pool.get() @@ -25,7 +25,7 @@ class ObjectPoolTest { pool.recycle(instance) } - fun recycleInstances(instances: List?) { + fun recycleInstances(instances: List) { pool.recycle(instances) } diff --git a/app/build.gradle b/app/build.gradle index ae8938734a..90a297924a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -16,11 +16,11 @@ android { testInstrumentationRunnerArguments useTestStorageService: "true" } compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 + sourceCompatibility JavaVersion.VERSION_21 + targetCompatibility JavaVersion.VERSION_21 } kotlinOptions { - jvmTarget = "17" + jvmTarget = "21" } buildFeatures { viewBinding true diff --git a/app/src/main/java/info/appdev/chartexample/AnotherBarActivity.java b/app/src/main/java/info/appdev/chartexample/AnotherBarActivity.java deleted file mode 100644 index 16c9568904..0000000000 --- a/app/src/main/java/info/appdev/chartexample/AnotherBarActivity.java +++ /dev/null @@ -1,216 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -public class AnotherBarActivity extends DemoBase implements OnSeekBarChangeListener { - - private static final int DEFAULT_VALUE = 10; - private BarChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_barchart); - - setTitle("AnotherBarActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setOnSeekBarChangeListener(this); - - seekBarY = findViewById(R.id.seekBarY); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - - chart.getDescription().setEnabled(false); - - // if more than 60 entries are displayed in the chart, no values will be - // drawn - chart.setMaxVisibleValueCount(60); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setDrawBarShadow(false); - chart.setDrawGridBackground(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTTOM); - xAxis.setDrawGridLines(false); - - chart.getAxisLeft().setDrawGridLines(false); - - // setting data - seekBarX.setProgress(DEFAULT_VALUE); - seekBarY.setProgress(100); - - // add a nice and smooth animation - chart.animateY(1500); - - chart.getLegend().setEnabled(false); - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100); - - for (int i = 0; i < seekBarX.getProgress(); i++) { - float multi = (seekBarY.getProgress() + 1); - float val = (float) (sampleValues[i].floatValue() * multi) + multi / 3; - values.add(new BarEntry(i, val)); - } - - BarDataSet set1; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set1 = (BarDataSet) chart.getData().getDataSetByIndex(0); - set1.setEntries(values); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - } else { - set1 = new BarDataSet(values, "Data Set"); - set1.setColors(ColorTemplate.VORDIPLOM_COLORS); - set1.setDrawValues(false); - - ArrayList dataSets = new ArrayList<>(); - dataSets.add(set1); - - BarData data = new BarData(dataSets); - chart.setData(data); - chart.setFitBars(true); - } - - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bar, menu); - menu.removeItem(R.id.actionToggleIcons); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/AnotherBarActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - /* - case R.id.actionToggleIcons: { break; } - */ - case R.id.actionToggleHighlight: { - - if(chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) - chart.setPinchZoom(false); - else - chart.setPinchZoom(true); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleBarBorders: { - for (IBarDataSet set : chart.getData().getDataSets()) - ((BarDataSet)set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); - - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - - chart.animateXY(2000, 2000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "AnotherBarActivity"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/AnotherBarActivity.kt b/app/src/main/java/info/appdev/chartexample/AnotherBarActivity.kt new file mode 100644 index 0000000000..fe553c0c8b --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/AnotherBarActivity.kt @@ -0,0 +1,202 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class AnotherBarActivity : DemoBase(), OnSeekBarChangeListener { + private var chart: BarChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_barchart) + + setTitle("AnotherBarActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarX?.setOnSeekBarChangeListener(this) + + seekBarY = findViewById(R.id.seekBarY) + seekBarY?.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + + chart?.description?.isEnabled = false + + // if more than 60 entries are displayed in the chart, no values will be + // drawn + chart?.setMaxVisibleValueCount(60) + + // scaling can now only be done on x- and y-axis separately + chart?.setPinchZoom(false) + + chart?.isDrawBarShadowEnabled = true + chart?.drawGridBackground = false + + val xAxis = chart?.xAxis + xAxis?.position = XAxisPosition.BOTTOM + xAxis?.setDrawGridLines(false) + + chart?.axisLeft?.setDrawGridLines(false) + + // setting data + seekBarX?.progress = DEFAULT_VALUE + seekBarY?.progress = 100 + + // add a nice and smooth animation + chart?.animateY(1500) + + chart?.legend?.isEnabled = false + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX?.text = seekBarX?.progress.toString() + tvY?.text = seekBarY?.progress.toString() + + val values = ArrayList() + val sampleValues = getValues(100) + + for (i in 0..<(seekBarX?.progress ?: 0)) { + val multi = ((seekBarY?.progress ?: 0) + 1).toFloat() + val `val` = (sampleValues[i].toFloat() * multi) + multi / 3 + values.add(BarEntry(i.toFloat(), `val`)) + } + + val set1: BarDataSet? + + if (chart?.data != null && + chart?.data?.let { it.dataSetCount > 0 } == true + ) { + set1 = chart?.data?.getDataSetByIndex(0) as BarDataSet + set1.entries = values + chart?.data?.notifyDataChanged() + chart?.notifyDataSetChanged() + } else { + set1 = BarDataSet(values, "Data Set") + set1.setColors(*ColorTemplate.VORDIPLOM_COLORS) + set1.isDrawValuesEnabled = false + + val dataSets = ArrayList() + dataSets.add(set1) + + val data = BarData(dataSets) + chart?.setData(data) + chart?.setFitBars(true) + } + + chart?.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.bar, menu) + menu.removeItem(R.id.actionToggleIcons) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/AnotherBarActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + chart?.data?.dataSets?.let { + for (set in it) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart?.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart?.data != null) { + chart?.data?.isHighlightEnabled = chart?.data?.isHighlightEnabled != true + chart?.invalidate() + } + } + + R.id.actionTogglePinch -> { + if (chart?.isPinchZoomEnabled == true) chart?.setPinchZoom(false) + else chart?.setPinchZoom(true) + + chart?.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart?.isAutoScaleMinMaxEnabled = chart?.isAutoScaleMinMaxEnabled != true + chart?.notifyDataSetChanged() + } + + R.id.actionToggleBarBorders -> { + chart?.data?.dataSets?.let { + for (set in it) (set as BarDataSet).setBarBorderWidth(if (set.barBorderWidth == 1f) 0f else 1f) + } + + chart?.invalidate() + } + + R.id.animateX -> { + chart?.animateX(2000) + } + + R.id.animateY -> { + chart?.animateY(2000) + } + + R.id.animateXY -> { + chart?.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "AnotherBarActivity") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + + companion object { + private const val DEFAULT_VALUE = 10 + } +} diff --git a/app/src/main/java/info/appdev/chartexample/BarChartActivity.java b/app/src/main/java/info/appdev/chartexample/BarChartActivity.java deleted file mode 100644 index 74c97f78df..0000000000 --- a/app/src/main/java/info/appdev/chartexample/BarChartActivity.java +++ /dev/null @@ -1,335 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.Legend.LegendForm; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.components.YAxis.YAxisLabelPosition; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.Fill; -import com.github.mikephil.charting.utils.MPPointF; - -import info.appdev.chartexample.custom.DayAxisValueFormatter; -import info.appdev.chartexample.custom.MyAxisValueFormatter; -import info.appdev.chartexample.custom.XYMarkerView; -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -public class BarChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private BarChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_barchart); - - setTitle("BarChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarY = findViewById(R.id.seekBarY); - - seekBarY.setOnSeekBarChangeListener(this); - seekBarX.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - chart.setRoundedBarRadius(50f); - - chart.setDrawBarShadow(false); - chart.setDrawValueAboveBar(true); - - chart.getDescription().setEnabled(false); - - // if more than 60 entries are displayed in the chart, no values will be - // drawn - chart.setMaxVisibleValueCount(60); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setDrawGridBackground(false); - // chart.setDrawYLabels(false); - - IAxisValueFormatter xAxisFormatter = new DayAxisValueFormatter(chart); - - XAxis xAxis = chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTTOM); - xAxis.setTypeface(tfLight); - xAxis.setDrawGridLines(false); - xAxis.setGranularity(1f); // only intervals of 1 day - xAxis.setLabelCount(7); - xAxis.setValueFormatter(xAxisFormatter); - - IAxisValueFormatter custom = new MyAxisValueFormatter(); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tfLight); - leftAxis.setLabelCount(8, false); - leftAxis.setValueFormatter(custom); - leftAxis.setPosition(YAxisLabelPosition.OUTSIDE_CHART); - leftAxis.setSpaceTop(15f); - leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setDrawGridLines(false); - rightAxis.setTypeface(tfLight); - rightAxis.setLabelCount(8, false); - rightAxis.setValueFormatter(custom); - rightAxis.setSpaceTop(15f); - rightAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - l.setForm(LegendForm.SQUARE); - l.setFormSize(9f); - l.setTextSize(11f); - l.setXEntrySpace(4f); - - XYMarkerView mv = new XYMarkerView(this, xAxisFormatter); - mv.setChartView(chart); // For bounds control - chart.setMarker(mv); // Set the marker to the chart - - // setting data - seekBarY.setProgress(50); - seekBarX.setProgress(12); - - // chart.setDrawLegend(false); - } - - private void setData(int count, float range) { - - float start = 1f; - - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100); - - for (int i = (int) start; i < start + count; i++) { - float val = (float) (sampleValues[i].floatValue() * (range + 1)); - - if (val * 100 < 25) { - values.add(new BarEntry(i, val, getResources().getDrawable(R.drawable.star))); - } else { - values.add(new BarEntry(i, val)); - } - } - - BarDataSet set1; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set1 = (BarDataSet) chart.getData().getDataSetByIndex(0); - set1.setEntries(values); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - - } else { - set1 = new BarDataSet(values, "The year 2017"); - - set1.setDrawIcons(false); - - int startColor1 = ContextCompat.getColor(this, android.R.color.holo_orange_light); - int startColor2 = ContextCompat.getColor(this, android.R.color.holo_blue_light); - int startColor3 = ContextCompat.getColor(this, android.R.color.holo_orange_light); - int startColor4 = ContextCompat.getColor(this, android.R.color.holo_green_light); - int startColor5 = ContextCompat.getColor(this, android.R.color.holo_red_light); - int endColor1 = ContextCompat.getColor(this, android.R.color.holo_blue_dark); - int endColor2 = ContextCompat.getColor(this, android.R.color.holo_purple); - int endColor3 = ContextCompat.getColor(this, android.R.color.holo_green_dark); - int endColor4 = ContextCompat.getColor(this, android.R.color.holo_red_dark); - int endColor5 = ContextCompat.getColor(this, android.R.color.holo_orange_dark); - - List gradientFills = new ArrayList<>(); - gradientFills.add(new Fill(startColor1, endColor1)); - gradientFills.add(new Fill(startColor2, endColor2)); - gradientFills.add(new Fill(startColor3, endColor3)); - gradientFills.add(new Fill(startColor4, endColor4)); - gradientFills.add(new Fill(startColor5, endColor5)); - - set1.setFills(gradientFills); - - ArrayList dataSets = new ArrayList<>(); - dataSets.add(set1); - - BarData data = new BarData(dataSets); - data.setValueTextSize(10f); - data.setValueTypeface(tfLight); - data.setBarWidth(0.9f); - - chart.setData(data); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bar, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BarChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawIcons(!set.isDrawIconsEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if (chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) - chart.setPinchZoom(false); - else - chart.setPinchZoom(true); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleBarBorders: { - for (IBarDataSet set : chart.getData().getDataSets()) - ((BarDataSet) set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); - - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - case R.id.actionRotateXAxisLabelsBy45Deg: { - chart.getXAxis().setLabelRotationAngle(45); - chart.notifyDataSetChanged(); - chart.invalidate(); - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - setData(seekBarX.getProgress(), seekBarY.getProgress()); - chart.invalidate(); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "BarChartActivity"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} - - private final RectF onValueSelectedRectF = new RectF(); - - @Override - public void onValueSelected(Entry e, Highlight h) { - - if (e == null) - return; - - RectF bounds = onValueSelectedRectF; - chart.getBarBounds((BarEntry) e, bounds); - MPPointF position = chart.getPosition(e, AxisDependency.LEFT); - - Log.i("bounds", bounds.toString()); - Log.i("position", position.toString()); - - Log.i("x-index", - "low: " + chart.getLowestVisibleX() + ", high: " - + chart.getHighestVisibleX()); - - MPPointF.recycleInstance(position); - } - - @Override - public void onNothingSelected() { } -} diff --git a/app/src/main/java/info/appdev/chartexample/BarChartActivity.kt b/app/src/main/java/info/appdev/chartexample/BarChartActivity.kt new file mode 100644 index 0000000000..ca02ab84a1 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/BarChartActivity.kt @@ -0,0 +1,321 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.RectF +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.Legend.LegendForm +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.components.YAxis.YAxisLabelPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.Fill +import com.github.mikephil.charting.utils.MPPointF.Companion.recycleInstance +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.custom.DayAxisValueFormatter +import info.appdev.chartexample.custom.MyAxisValueFormatter +import info.appdev.chartexample.custom.XYMarkerView +import info.appdev.chartexample.notimportant.DemoBase + +class BarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: BarChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_barchart) + + setTitle("BarChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarY = findViewById(R.id.seekBarY) + + seekBarY?.setOnSeekBarChangeListener(this) + seekBarX?.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart?.setOnChartValueSelectedListener(this) + chart?.setRoundedBarRadius(50f) + + chart?.isDrawBarShadowEnabled = false + chart?.isDrawValueAboveBarEnabled = true + + chart?.description?.isEnabled = false + + // if more than 60 entries are displayed in the chart, no values will be + // drawn + chart?.setMaxVisibleValueCount(60) + + // scaling can now only be done on x- and y-axis separately + chart?.setPinchZoom(false) + + chart?.drawGridBackground = false + + // chart.setDrawYLabels(false); + val xAxisFormatter: IAxisValueFormatter = DayAxisValueFormatter(chart!!) + + val xAxis = chart?.xAxis + xAxis?.position = XAxisPosition.BOTTOM + xAxis?.typeface = tfLight + xAxis?.setDrawGridLines(false) + xAxis?.granularity = 1f // only intervals of 1 day + xAxis?.labelCount = 7 + xAxis?.valueFormatter = xAxisFormatter + + val custom: IAxisValueFormatter = MyAxisValueFormatter() + + val leftAxis = chart?.axisLeft + leftAxis?.typeface = tfLight + leftAxis?.setLabelCount(8, false) + leftAxis?.valueFormatter = custom + leftAxis?.setPosition(YAxisLabelPosition.OUTSIDE_CHART) + leftAxis?.spaceTop = 15f + leftAxis?.axisMinimum = 0f // this replaces setStartAtZero(true) + + val rightAxis = chart?.axisRight + rightAxis?.setDrawGridLines(false) + rightAxis?.typeface = tfLight + rightAxis?.setLabelCount(8, false) + rightAxis?.valueFormatter = custom + rightAxis?.spaceTop = 15f + rightAxis?.axisMinimum = 0f // this replaces setStartAtZero(true) + + val l = chart?.legend + l?.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + l?.horizontalAlignment = Legend.LegendHorizontalAlignment.LEFT + l?.orientation = Legend.LegendOrientation.HORIZONTAL + l?.setDrawInside(false) + l?.form = LegendForm.SQUARE + l?.formSize = 9f + l?.textSize = 11f + l?.xEntrySpace = 4f + + val mv = XYMarkerView(this, xAxisFormatter) + mv.chartView = chart // For bounds control + chart?.setMarker(mv) // Set the marker to the chart + + // setting data + seekBarY?.progress = 50 + seekBarX?.progress = 12 + + // chart.setDrawLegend(false); + } + + private fun setData(count: Int, range: Float) { + val start = 1f + + val values = ArrayList() + val sampleValues = getValues(100) + + var i = start.toInt() + while (i < start + count) { + val `val` = (sampleValues[i].toFloat() * (range + 1)) + + if (`val` * 100 < 25) { + values.add(BarEntry(i.toFloat(), `val`, ResourcesCompat.getDrawable(resources, R.drawable.star, theme))) + } else { + values.add(BarEntry(i.toFloat(), `val`)) + } + i++ + } + + val set1: BarDataSet? + + if (chart?.data != null && + chart?.data?.let { it.dataSetCount > 0 } == true + ) { + set1 = chart?.data?.getDataSetByIndex(0) as BarDataSet + set1.entries = values + chart?.data?.notifyDataChanged() + chart?.notifyDataSetChanged() + } else { + set1 = BarDataSet(values, "The year 2017") + + set1.isDrawIconsEnabled = false + + val startColor1 = ContextCompat.getColor(this, android.R.color.holo_orange_light) + val startColor2 = ContextCompat.getColor(this, android.R.color.holo_blue_light) + val startColor3 = ContextCompat.getColor(this, android.R.color.holo_orange_light) + val startColor4 = ContextCompat.getColor(this, android.R.color.holo_green_light) + val startColor5 = ContextCompat.getColor(this, android.R.color.holo_red_light) + val endColor1 = ContextCompat.getColor(this, android.R.color.holo_blue_dark) + val endColor2 = ContextCompat.getColor(this, android.R.color.holo_purple) + val endColor3 = ContextCompat.getColor(this, android.R.color.holo_green_dark) + val endColor4 = ContextCompat.getColor(this, android.R.color.holo_red_dark) + val endColor5 = ContextCompat.getColor(this, android.R.color.holo_orange_dark) + + val gradientFills: MutableList = ArrayList() + gradientFills.add(Fill(startColor1, endColor1)) + gradientFills.add(Fill(startColor2, endColor2)) + gradientFills.add(Fill(startColor3, endColor3)) + gradientFills.add(Fill(startColor4, endColor4)) + gradientFills.add(Fill(startColor5, endColor5)) + + set1.setFills(gradientFills) + + val dataSets = ArrayList() + dataSets.add(set1) + + val data = BarData(dataSets) + data.setValueTextSize(10f) + data.setValueTypeface(tfLight) + data.barWidth = 0.9f + + chart?.setData(data) + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.bar, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BarChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + chart?.data?.dataSets?.let { + for (set in it) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart?.invalidate() + } + + R.id.actionToggleIcons -> { + chart?.data?.dataSets?.let { + for (set in it) set.isDrawIconsEnabled = !set.isDrawIconsEnabled + } + + chart?.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart?.data != null) { + chart?.data?.isHighlightEnabled = chart?.data?.isHighlightEnabled != true + chart?.invalidate() + } + } + + R.id.actionTogglePinch -> { + if (chart?.isPinchZoomEnabled == true) chart?.setPinchZoom(false) + else chart?.setPinchZoom(true) + + chart?.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart?.isAutoScaleMinMaxEnabled = chart?.isAutoScaleMinMaxEnabled != true + chart?.notifyDataSetChanged() + } + + R.id.actionToggleBarBorders -> { + chart?.data?.dataSets?.let { + for (set in it) (set as BarDataSet).setBarBorderWidth(if (set.barBorderWidth == 1f) 0f else 1f) + } + + chart?.invalidate() + } + + R.id.animateX -> { + chart?.animateX(2000) + } + + R.id.animateY -> { + chart?.animateY(2000) + } + + R.id.animateXY -> { + chart?.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + + R.id.actionRotateXAxisLabelsBy45Deg -> { + chart?.xAxis?.labelRotationAngle = 45f + chart?.notifyDataSetChanged() + chart?.invalidate() + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX?.text = seekBarX?.progress.toString() + tvY?.text = seekBarY?.progress.toString() + + setData(seekBarX?.progress ?: 0, seekBarY?.progress?.toFloat() ?: 0f) + chart?.invalidate() + } + + override fun saveToGallery() { + saveToGallery(chart, "BarChartActivity") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + + private val onValueSelectedRectF = RectF() + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null) return + + val bounds = onValueSelectedRectF + chart?.getBarBounds(e as BarEntry, bounds) + val position = chart?.getPosition(e, AxisDependency.LEFT) + + Log.i("bounds", bounds.toString()) + Log.i("position", position.toString()) + + Log.i( + "x-index", + ("low: " + chart?.lowestVisibleX + ", high: " + + chart?.highestVisibleX) + ) + + recycleInstance(position) + } + + override fun onNothingSelected() {} +} diff --git a/app/src/main/java/info/appdev/chartexample/BarChartActivityMultiDataset.java b/app/src/main/java/info/appdev/chartexample/BarChartActivityMultiDataset.java deleted file mode 100644 index 798f454a1a..0000000000 --- a/app/src/main/java/info/appdev/chartexample/BarChartActivityMultiDataset.java +++ /dev/null @@ -1,291 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.AxisBase; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.formatter.LargeValueFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; - -import info.appdev.chartexample.custom.MyMarkerView; -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.Locale; - -public class BarChartActivityMultiDataset extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private BarChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_barchart); - - setTitle("BarChartActivityMultiDataset"); - - tvX = findViewById(R.id.tvXMax); - tvX.setTextSize(10); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setMax(50); - seekBarX.setOnSeekBarChangeListener(this); - - seekBarY = findViewById(R.id.seekBarY); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - chart.getDescription().setEnabled(false); - -// chart.setDrawBorders(true); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setDrawBarShadow(false); - - chart.setDrawGridBackground(false); - - // create a custom MarkerView (extend MarkerView) and specify the layout - // to use for it - MyMarkerView mv = new MyMarkerView(this, R.layout.custom_marker_view); - mv.setChartView(chart); // For bounds control - chart.setMarker(mv); // Set the marker to the chart - - seekBarX.setProgress(10); - seekBarY.setProgress(100); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(true); - l.setTypeface(tfLight); - l.setYOffset(0f); - l.setXOffset(10f); - l.setYEntrySpace(0f); - l.setTextSize(8f); - - XAxis xAxis = chart.getXAxis(); - xAxis.setTypeface(tfLight); - xAxis.setGranularity(1f); - xAxis.setCenterAxisLabels(true); - xAxis.setValueFormatter(new IAxisValueFormatter() { - @Override - public String getFormattedValue(float value, AxisBase axis) { - return String.valueOf((int) value); - } - }); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tfLight); - leftAxis.setValueFormatter(new LargeValueFormatter()); - leftAxis.setDrawGridLines(false); - leftAxis.setSpaceTop(35f); - leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - chart.getAxisRight().setEnabled(false); - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - float groupSpace = 0.08f; - float barSpace = 0.03f; // x4 DataSet - float barWidth = 0.2f; // x4 DataSet - // (0.2 + 0.03) * 4 + 0.08 = 1.00 -> interval per "group" - - int groupCount = seekBarX.getProgress() + 1; - int startYear = 1980; - int endYear = startYear + groupCount; - - tvX.setText(String.format(Locale.ENGLISH, "%d-%d", startYear, endYear)); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - ArrayList values1 = new ArrayList<>(); - ArrayList values2 = new ArrayList<>(); - ArrayList values3 = new ArrayList<>(); - ArrayList values4 = new ArrayList<>(); - - float randomMultiplier = seekBarY.getProgress() * 100000f; - Double[] sampleValues = DataTools.Companion.getValues(100 + 2); - - for (int i = startYear; i < endYear; i++) { - values1.add(new BarEntry(i, (float) (sampleValues[i - startYear].floatValue() * randomMultiplier))); - values2.add(new BarEntry(i, (float) (sampleValues[i - startYear + 1].floatValue() * randomMultiplier))); - values3.add(new BarEntry(i, (float) (sampleValues[i - startYear + 2].floatValue() * randomMultiplier))); - values4.add(new BarEntry(i, (float) (sampleValues[i - startYear].floatValue() * randomMultiplier))); - } - - BarDataSet set1, set2, set3, set4; - - if (chart.getData() != null && chart.getData().getDataSetCount() > 0) { - - set1 = (BarDataSet) chart.getData().getDataSetByIndex(0); - set2 = (BarDataSet) chart.getData().getDataSetByIndex(1); - set3 = (BarDataSet) chart.getData().getDataSetByIndex(2); - set4 = (BarDataSet) chart.getData().getDataSetByIndex(3); - set1.setEntries(values1); - set2.setEntries(values2); - set3.setEntries(values3); - set4.setEntries(values4); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - - } else { - // create 4 DataSets - set1 = new BarDataSet(values1, "Company A"); - set1.setColor(Color.rgb(104, 241, 175)); - set2 = new BarDataSet(values2, "Company B"); - set2.setColor(Color.rgb(164, 228, 251)); - set3 = new BarDataSet(values3, "Company C"); - set3.setColor(Color.rgb(242, 247, 158)); - set4 = new BarDataSet(values4, "Company D"); - set4.setColor(Color.rgb(255, 102, 0)); - - BarData data = new BarData(set1, set2, set3, set4); - data.setValueFormatter(new LargeValueFormatter()); - data.setValueTypeface(tfLight); - - chart.setData(data); - } - - // specify the width each bar should have - chart.getBarData().setBarWidth(barWidth); - - // restrict the x-axis range - chart.getXAxis().setAxisMinimum(startYear); - - // barData.getGroupWith(...) is a helper that calculates the width each group needs based on the provided parameters - chart.getXAxis().setAxisMaximum(startYear + chart.getBarData().getGroupWidth(groupSpace, barSpace) * groupCount); - chart.groupBars(startYear, groupSpace, barSpace); - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bar, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BarChartActivityMultiDataset.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IBarDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) - chart.setPinchZoom(false); - else - chart.setPinchZoom(true); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleBarBorders: { - for (IBarDataSet set : chart.getData().getDataSets()) - ((BarDataSet) set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if (chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "BarChartActivityMultiDataset"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} - - @Override - public void onValueSelected(Entry e, Highlight h) { - Log.i("Activity", "Selected: " + e.toString() + ", dataSet: " + h.getDataSetIndex()); - } - - @Override - public void onNothingSelected() { - Log.i("Activity", "Nothing selected."); - } -} diff --git a/app/src/main/java/info/appdev/chartexample/BarChartActivityMultiDataset.kt b/app/src/main/java/info/appdev/chartexample/BarChartActivityMultiDataset.kt new file mode 100644 index 0000000000..ba791d0240 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/BarChartActivityMultiDataset.kt @@ -0,0 +1,274 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.AxisBase +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.formatter.LargeValueFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.custom.MyMarkerView +import info.appdev.chartexample.notimportant.DemoBase +import java.util.Locale + +class BarChartActivityMultiDataset : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: BarChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_barchart) + + setTitle("BarChartActivityMultiDataset") + + tvX = findViewById(R.id.tvXMax) + tvX?.textSize = 10f + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarX?.setMax(50) + seekBarX?.setOnSeekBarChangeListener(this) + + seekBarY = findViewById(R.id.seekBarY) + seekBarY?.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart?.setOnChartValueSelectedListener(this) + chart?.description?.isEnabled = false + + // chart.setDrawBorders(true); + + // scaling can now only be done on x- and y-axis separately + chart?.setPinchZoom(false) + + chart?.isDrawBarShadowEnabled = false + + chart?.drawGridBackground = false + + // create a custom MarkerView (extend MarkerView) and specify the layout + // to use for it + val mv = MyMarkerView(this, R.layout.custom_marker_view) + mv.chartView = chart // For bounds control + chart?.setMarker(mv) // Set the marker to the chart + + seekBarX?.progress = 10 + seekBarY?.progress = 100 + + val l = chart?.legend + l?.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l?.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l?.orientation = Legend.LegendOrientation.VERTICAL + l?.setDrawInside(true) + l?.typeface = tfLight + l?.yOffset = 0f + l?.xOffset = 10f + l?.yEntrySpace = 0f + l?.textSize = 8f + + val xAxis = chart?.xAxis + xAxis?.typeface = tfLight + xAxis?.granularity = 1f + xAxis?.setCenterAxisLabels(true) + xAxis?.valueFormatter = object : IAxisValueFormatter { + override fun getFormattedValue(value: Float, axis: AxisBase?): String { + return value.toInt().toString() + } + } + + val leftAxis = chart?.axisLeft + leftAxis?.typeface = tfLight + leftAxis?.valueFormatter = LargeValueFormatter() + leftAxis?.setDrawGridLines(false) + leftAxis?.spaceTop = 35f + leftAxis?.axisMinimum = 0f // this replaces setStartAtZero(true) + + chart?.axisRight?.isEnabled = false + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + val groupSpace = 0.08f + val barSpace = 0.03f // x4 DataSet + val barWidth = 0.2f // x4 DataSet + + // (0.2 + 0.03) * 4 + 0.08 = 1.00 -> interval per "group" + val groupCount = (seekBarX?.progress ?: 0) + 1 + val startYear = 1980 + val endYear = startYear + groupCount + + tvX?.text = String.format(Locale.ENGLISH, "%d-%d", startYear, endYear) + tvY?.text = seekBarY?.progress.toString() + + val values1 = ArrayList() + val values2 = ArrayList() + val values3 = ArrayList() + val values4 = ArrayList() + + val randomMultiplier = (seekBarY?.progress ?: 0) * 100000f + val sampleValues = getValues(100 + 2) + + for (i in startYear.. 0 } == true) { + set1 = chart?.data?.getDataSetByIndex(0) as BarDataSet + set2 = chart?.data?.getDataSetByIndex(1) as BarDataSet + set3 = chart?.data?.getDataSetByIndex(2) as BarDataSet + set4 = chart?.data?.getDataSetByIndex(3) as BarDataSet + set1.entries = values1 + set2.entries = values2 + set3.entries = values3 + set4.entries = values4 + chart?.data?.notifyDataChanged() + chart?.notifyDataSetChanged() + } else { + // create 4 DataSets + set1 = BarDataSet(values1, "Company A") + set1.setColor(Color.rgb(104, 241, 175)) + set2 = BarDataSet(values2, "Company B") + set2.setColor(Color.rgb(164, 228, 251)) + set3 = BarDataSet(values3, "Company C") + set3.setColor(Color.rgb(242, 247, 158)) + set4 = BarDataSet(values4, "Company D") + set4.setColor(Color.rgb(255, 102, 0)) + + val data = BarData(set1, set2, set3, set4) + data.setValueFormatter(LargeValueFormatter()) + data.setValueTypeface(tfLight) + + chart?.setData(data) + } + + // specify the width each bar should have + chart?.barData?.barWidth = barWidth + + // restrict the x-axis range + chart?.xAxis?.axisMinimum = startYear.toFloat() + + // barData.getGroupWith(...) is a helper that calculates the width each group needs based on the provided parameters + chart?.xAxis?.axisMaximum = startYear + ((chart?.barData?.getGroupWidth(groupSpace, barSpace) ?: 0f) * groupCount) + chart?.groupBars(startYear.toFloat(), groupSpace, barSpace) + chart?.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.bar, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BarChartActivityMultiDataset.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + chart?.data?.dataSets?.let { + for (set in it) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart?.invalidate() + } + + R.id.actionTogglePinch -> { + if (chart?.isPinchZoomEnabled == true) chart?.setPinchZoom(false) + else chart?.setPinchZoom(true) + + chart?.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart?.isAutoScaleMinMaxEnabled = chart?.isAutoScaleMinMaxEnabled != true + chart?.notifyDataSetChanged() + } + + R.id.actionToggleBarBorders -> { + chart?.data?.dataSets?.let { + for (set in it) (set as BarDataSet).setBarBorderWidth(if (set.barBorderWidth == 1f) 0f else 1f) + } + + chart?.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart?.data != null) { + chart?.data?.isHighlightEnabled = chart?.data?.isHighlightEnabled != true + chart?.invalidate() + } + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + + R.id.animateX -> { + chart?.animateX(2000) + } + + R.id.animateY -> { + chart?.animateY(2000) + } + + R.id.animateXY -> { + chart?.animateXY(2000, 2000) + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "BarChartActivityMultiDataset") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + + override fun onValueSelected(e: Entry?, h: Highlight?) { + Log.i("Activity", "Selected: " + e.toString() + ", dataSet: " + h?.dataSetIndex) + } + + override fun onNothingSelected() { + Log.i("Activity", "Nothing selected.") + } +} diff --git a/app/src/main/java/info/appdev/chartexample/BarChartActivitySinus.java b/app/src/main/java/info/appdev/chartexample/BarChartActivitySinus.java deleted file mode 100644 index 7b8ce01c2a..0000000000 --- a/app/src/main/java/info/appdev/chartexample/BarChartActivitySinus.java +++ /dev/null @@ -1,240 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.Legend.LegendForm; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.utils.FileUtils; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -public class BarChartActivitySinus extends DemoBase implements OnSeekBarChangeListener { - - private BarChart chart; - private SeekBar seekBarX; - private TextView tvX; - - private List data; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_barchart_sinus); - - setTitle("BarChartActivitySinus"); - - data = FileUtils.loadBarEntriesFromAssets(getAssets(), "othersine.txt"); - - tvX = findViewById(R.id.tvValueCount); - - seekBarX = findViewById(R.id.seekbarValues); - - chart = findViewById(R.id.chart1); - - chart.setDrawBarShadow(false); - chart.setDrawValueAboveBar(true); - - chart.getDescription().setEnabled(false); - - // if more than 60 entries are displayed in the chart, no values will be - // drawn - chart.setMaxVisibleValueCount(60); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - // draw shadows for each bar that show the maximum value - // chart.setDrawBarShadow(true); - - // chart.setDrawXLabels(false); - - chart.setDrawGridBackground(false); - // chart.setDrawYLabels(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setEnabled(false); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tfLight); - leftAxis.setLabelCount(6, false); - leftAxis.setAxisMinimum(-2.5f); - leftAxis.setAxisMaximum(2.5f); - leftAxis.setGranularityEnabled(true); - leftAxis.setGranularity(0.1f); - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setDrawGridLines(false); - rightAxis.setTypeface(tfLight); - rightAxis.setLabelCount(6, false); - rightAxis.setAxisMinimum(-2.5f); - rightAxis.setAxisMaximum(2.5f); - rightAxis.setGranularity(0.1f); - - seekBarX.setOnSeekBarChangeListener(this); - seekBarX.setProgress(150); // set data - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - l.setForm(LegendForm.SQUARE); - l.setFormSize(9f); - l.setTextSize(11f); - l.setXEntrySpace(4f); - - chart.animateXY(1500, 1500); - } - - private void setData(int count) { - - ArrayList entries = new ArrayList<>(); - - for (int i = 0; i < count; i++) { - entries.add(data.get(i)); - } - - BarDataSet set; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set = (BarDataSet) chart.getData().getDataSetByIndex(0); - set.setEntries(entries); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - } else { - set = new BarDataSet(entries, "Sinus Function"); - set.setColor(Color.BLUE); - } - - BarData data = new BarData(set); - data.setValueTextSize(10f); - data.setValueTypeface(tfLight); - data.setDrawValues(false); - data.setBarWidth(0.8f); - - chart.setData(data); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bar, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BarChartActivitySinus.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IBarDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if (chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) - chart.setPinchZoom(false); - else - chart.setPinchZoom(true); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleBarBorders: { - for (IBarDataSet set : chart.getData().getDataSets()) - ((BarDataSet) set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); - - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - - chart.animateXY(2000, 2000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - - setData(seekBarX.getProgress()); - chart.invalidate(); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "BarChartActivitySinus"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} - -} diff --git a/app/src/main/java/info/appdev/chartexample/BarChartActivitySinus.kt b/app/src/main/java/info/appdev/chartexample/BarChartActivitySinus.kt new file mode 100644 index 0000000000..4f7ac5ee36 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/BarChartActivitySinus.kt @@ -0,0 +1,215 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.Legend.LegendForm +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.utils.FileUtils.loadBarEntriesFromAssets +import info.appdev.chartexample.notimportant.DemoBase + +class BarChartActivitySinus : DemoBase(), OnSeekBarChangeListener { + private var chart: BarChart? = null + private var seekBarX: SeekBar? = null + private var tvX: TextView? = null + + private val data: MutableList by lazy { + loadBarEntriesFromAssets(assets, "othersine.txt") + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_barchart_sinus) + + setTitle("BarChartActivitySinus") + + tvX = findViewById(R.id.tvValueCount) + + seekBarX = findViewById(R.id.seekbarValues) + + chart = findViewById(R.id.chart1) + + chart!!.isDrawBarShadowEnabled = false + chart!!.isDrawValueAboveBarEnabled = true + + chart!!.description.isEnabled = false + + // if more than 60 entries are displayed in the chart, no values will be + // drawn + chart!!.setMaxVisibleValueCount(60) + + // scaling can now only be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + // draw shadows for each bar that show the maximum value + // chart.setDrawBarShadow(true); + + // chart.setDrawXLabels(false); + chart!!.drawGridBackground = false + + // chart.setDrawYLabels(false); + val xAxis = chart!!.xAxis + xAxis.isEnabled = false + + val leftAxis = chart!!.axisLeft + leftAxis.typeface = tfLight + leftAxis.setLabelCount(6, false) + leftAxis.axisMinimum = -2.5f + leftAxis.axisMaximum = 2.5f + leftAxis.isGranularityEnabled = true + leftAxis.granularity = 0.1f + + val rightAxis = chart!!.axisRight + rightAxis.setDrawGridLines(false) + rightAxis.typeface = tfLight + rightAxis.setLabelCount(6, false) + rightAxis.axisMinimum = -2.5f + rightAxis.axisMaximum = 2.5f + rightAxis.granularity = 0.1f + + seekBarX!!.setOnSeekBarChangeListener(this) + seekBarX!!.progress = 150 // set data + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + l.horizontalAlignment = Legend.LegendHorizontalAlignment.LEFT + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + l.form = LegendForm.SQUARE + l.formSize = 9f + l.textSize = 11f + l.xEntrySpace = 4f + + chart!!.animateXY(1500, 1500) + } + + private fun setData(count: Int) { + val entries = ArrayList() + + for (i in 0.. 0 + ) { + set = chart!!.data!!.getDataSetByIndex(0) as BarDataSet + set.entries = entries + chart!!.data!!.notifyDataChanged() + chart!!.notifyDataSetChanged() + } else { + set = BarDataSet(entries, "Sinus Function") + set.setColor(Color.BLUE) + } + + val data = BarData(set) + data.setValueTextSize(10f) + data.setValueTypeface(tfLight) + data.setDrawValues(false) + data.barWidth = 0.8f + + chart!!.setData(data) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.bar, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BarChartActivitySinus.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + for (set in chart!!.data!!.dataSets) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionTogglePinch -> { + if (chart!!.isPinchZoomEnabled) chart!!.setPinchZoom(false) + else chart!!.setPinchZoom(true) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionToggleBarBorders -> { + for (set in chart!!.data!!.dataSets) (set as BarDataSet).setBarBorderWidth(if (set.barBorderWidth == 1f) 0f else 1f) + + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + + setData(seekBarX!!.progress) + chart!!.invalidate() + } + + override fun saveToGallery() { + saveToGallery(chart, "BarChartActivitySinus") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/BarChartPositiveNegative.java b/app/src/main/java/info/appdev/chartexample/BarChartPositiveNegative.java deleted file mode 100644 index 65072ece0e..0000000000 --- a/app/src/main/java/info/appdev/chartexample/BarChartPositiveNegative.java +++ /dev/null @@ -1,201 +0,0 @@ - -package info.appdev.chartexample; - -import android.content.Intent; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.AxisBase; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.utils.ViewPortHandler; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; - -public class BarChartPositiveNegative extends DemoBase { - - private BarChart chart; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_barchart_noseekbar); - - setTitle("BarChartPositiveNegative"); - - chart = findViewById(R.id.chart1); - chart.setBackgroundColor(Color.WHITE); - chart.setExtraTopOffset(-30f); - chart.setExtraBottomOffset(10f); - chart.setExtraLeftOffset(70f); - chart.setExtraRightOffset(70f); - - chart.setDrawBarShadow(false); - chart.setDrawValueAboveBar(true); - - chart.getDescription().setEnabled(false); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setDrawGridBackground(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTTOM); - xAxis.setTypeface(tfRegular); - xAxis.setDrawGridLines(false); - xAxis.setDrawAxisLine(false); - xAxis.setTextColor(Color.LTGRAY); - xAxis.setTextSize(13f); - xAxis.setLabelCount(5); - xAxis.setCenterAxisLabels(true); - xAxis.setGranularity(1f); - - YAxis left = chart.getAxisLeft(); - left.setDrawLabels(false); - left.setSpaceTop(25f); - left.setSpaceBottom(25f); - left.setDrawAxisLine(false); - left.setDrawGridLines(false); - left.setDrawZeroLine(true); // draw a zero line - left.setZeroLineColor(Color.GRAY); - left.setZeroLineWidth(0.7f); - chart.getAxisRight().setEnabled(false); - chart.getLegend().setEnabled(false); - - // THIS IS THE ORIGINAL DATA YOU WANT TO PLOT - final List data = new ArrayList<>(); - data.add(new Data(0f, -224.1f, "12-29")); - data.add(new Data(1f, 238.5f, "12-30")); - data.add(new Data(2f, 1280.1f, "12-31")); - data.add(new Data(3f, -442.3f, "01-01")); - data.add(new Data(4f, -2280.1f, "01-02")); - - xAxis.setValueFormatter(new IAxisValueFormatter() { - @Override - public String getFormattedValue(float value, AxisBase axis) { - return data.get(Math.min(Math.max((int) value, 0), data.size()-1)).xAxisValue; - } - }); - - setData(data); - } - - private void setData(List dataList) { - - ArrayList values = new ArrayList<>(); - List colors = new ArrayList<>(); - - int green = Color.rgb(110, 190, 102); - int red = Color.rgb(211, 74, 88); - - for (int i = 0; i < dataList.size(); i++) { - - Data d = dataList.get(i); - BarEntry entry = new BarEntry(d.xValue, d.yValue); - values.add(entry); - - // specific colors - if (d.yValue >= 0) - colors.add(red); - else - colors.add(green); - } - - BarDataSet set; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set = (BarDataSet) chart.getData().getDataSetByIndex(0); - set.setEntries(values); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - } else { - set = new BarDataSet(values, "Values"); - set.setColors(colors); - set.setValueTextColors(colors); - - BarData data = new BarData(set); - data.setValueTextSize(13f); - data.setValueTypeface(tfRegular); - data.setValueFormatter(new ValueFormatter()); - data.setBarWidth(0.8f); - - chart.setData(data); - chart.invalidate(); - } - } - - /** - * Demo class representing data. - */ - private class Data { - - final String xAxisValue; - final float yValue; - final float xValue; - - Data(float xValue, float yValue, String xAxisValue) { - this.xAxisValue = xAxisValue; - this.yValue = yValue; - this.xValue = xValue; - } - } - - private class ValueFormatter implements IValueFormatter - { - - private final DecimalFormat mFormat; - - ValueFormatter() { - mFormat = new DecimalFormat("######.0"); - } - - @Override - public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { - return mFormat.format(value); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.only_github, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BarChartPositiveNegative.java")); - startActivity(i); - break; - } - } - - return true; - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } -} diff --git a/app/src/main/java/info/appdev/chartexample/BarChartPositiveNegative.kt b/app/src/main/java/info/appdev/chartexample/BarChartPositiveNegative.kt new file mode 100644 index 0000000000..f13792c93f --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/BarChartPositiveNegative.kt @@ -0,0 +1,169 @@ +package info.appdev.chartexample + +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.AxisBase +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.formatter.IValueFormatter +import com.github.mikephil.charting.utils.ViewPortHandler +import info.appdev.chartexample.notimportant.DemoBase +import java.text.DecimalFormat +import kotlin.math.max +import kotlin.math.min + +class BarChartPositiveNegative : DemoBase() { + private var chart: BarChart? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_barchart_noseekbar) + + setTitle("BarChartPositiveNegative") + + chart = findViewById(R.id.chart1) + chart!!.setBackgroundColor(Color.WHITE) + chart!!.extraTopOffset = -30f + chart!!.extraBottomOffset = 10f + chart!!.extraLeftOffset = 70f + chart!!.extraRightOffset = 70f + + chart!!.isDrawBarShadowEnabled = false + chart!!.isDrawValueAboveBarEnabled = true + + chart!!.description.isEnabled = false + + // scaling can now only be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + chart!!.drawGridBackground = false + + val xAxis = chart!!.xAxis + xAxis.position = XAxisPosition.BOTTOM + xAxis.typeface = tfRegular + xAxis.setDrawGridLines(false) + xAxis.setDrawAxisLine(false) + xAxis.textColor = Color.LTGRAY + xAxis.textSize = 13f + xAxis.labelCount = 5 + xAxis.setCenterAxisLabels(true) + xAxis.granularity = 1f + + val left = chart!!.axisLeft + left.setDrawLabels(false) + left.spaceTop = 25f + left.spaceBottom = 25f + left.setDrawAxisLine(false) + left.setDrawGridLines(false) + left.setDrawZeroLine(true) // draw a zero line + left.zeroLineColor = Color.GRAY + left.zeroLineWidth = 0.7f + chart!!.axisRight.isEnabled = false + chart!!.legend.isEnabled = false + + // THIS IS THE ORIGINAL DATA YOU WANT TO PLOT + val data: MutableList = ArrayList() + data.add(Data(0f, -224.1f, "12-29")) + data.add(Data(1f, 238.5f, "12-30")) + data.add(Data(2f, 1280.1f, "12-31")) + data.add(Data(3f, -442.3f, "01-01")) + data.add(Data(4f, -2280.1f, "01-02")) + + xAxis.valueFormatter = object : IAxisValueFormatter { + override fun getFormattedValue(value: Float, axis: AxisBase?): String { + return data[min(max(value.toInt(), 0), data.size - 1)].xAxisValue + } + } + + setData(data) + } + + private fun setData(dataList: MutableList) { + val values = ArrayList() + val colors: MutableList = ArrayList() + + val green = Color.rgb(110, 190, 102) + val red = Color.rgb(211, 74, 88) + + for (i in dataList.indices) { + val d = dataList[i] + val entry = BarEntry(d.xValue, d.yValue) + values.add(entry) + + // specific colors + if (d.yValue >= 0) colors.add(red) + else colors.add(green) + } + + val set: BarDataSet? + + if (chart!!.data != null && + chart!!.data!!.dataSetCount > 0 + ) { + set = chart!!.data!!.getDataSetByIndex(0) as BarDataSet + set.entries = values + chart!!.data!!.notifyDataChanged() + chart!!.notifyDataSetChanged() + } else { + set = BarDataSet(values, "Values") + set.setColors(colors) + set.setValueTextColors(colors) + + val data = BarData(set) + data.setValueTextSize(13f) + data.setValueTypeface(tfRegular) + data.setValueFormatter(ValueFormatter()) + data.barWidth = 0.8f + + chart!!.setData(data) + chart!!.invalidate() + } + } + + /** + * Demo class representing data. + */ + private data class Data(val xValue: Float, val yValue: Float, val xAxisValue: String) + + private class ValueFormatter : IValueFormatter { + private val mFormat = DecimalFormat("######.0") + + override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + return mFormat.format(value.toDouble()) + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.only_github, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BarChartPositiveNegative.java".toUri()) + startActivity(i) + } + } + + return true + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } +} diff --git a/app/src/main/java/info/appdev/chartexample/BubbleChartActivity.java b/app/src/main/java/info/appdev/chartexample/BubbleChartActivity.java deleted file mode 100644 index c2e34e38ba..0000000000 --- a/app/src/main/java/info/appdev/chartexample/BubbleChartActivity.java +++ /dev/null @@ -1,250 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.BubbleChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BubbleData; -import com.github.mikephil.charting.data.BubbleDataSet; -import com.github.mikephil.charting.data.BubbleEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.MPPointF; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -public class BubbleChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private BubbleChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_bubblechart); - - setTitle("BubbleChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setOnSeekBarChangeListener(this); - - seekBarY = findViewById(R.id.seekBarY); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.getDescription().setEnabled(false); - - chart.setOnChartValueSelectedListener(this); - - chart.setDrawGridBackground(false); - - chart.setTouchEnabled(true); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - - chart.setMaxVisibleValueCount(200); - chart.setPinchZoom(true); - - seekBarX.setProgress(10); - seekBarY.setProgress(50); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(false); - l.setTypeface(tfLight); - - YAxis yl = chart.getAxisLeft(); - yl.setTypeface(tfLight); - yl.setSpaceTop(30f); - yl.setSpaceBottom(30f); - yl.setDrawZeroLine(false); - - chart.getAxisRight().setEnabled(false); - - XAxis xl = chart.getXAxis(); - xl.setPosition(XAxis.XAxisPosition.BOTTOM); - xl.setTypeface(tfLight); - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - int count = seekBarX.getProgress(); - int range = seekBarY.getProgress(); - - tvX.setText(String.valueOf(count)); - tvY.setText(String.valueOf(range)); - - ArrayList values1 = new ArrayList<>(); - ArrayList values2 = new ArrayList<>(); - ArrayList values3 = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100); - - for (int i = 0; i < count; i++) { - values1.add(new BubbleEntry(i, (float) (sampleValues[i+1] * range), (float) (sampleValues[i].floatValue() * range), getResources().getDrawable(R.drawable.star))); - values2.add(new BubbleEntry(i, (float) (sampleValues[i+2] * range), (float) (sampleValues[i+1].floatValue() * range), getResources().getDrawable(R.drawable.star))); - values3.add(new BubbleEntry(i, (float) (sampleValues[i] * range), (float) (sampleValues[i+2].floatValue() * range))); - } - - // create a dataset and give it a type - BubbleDataSet set1 = new BubbleDataSet(values1, "DS 1"); - set1.setDrawIcons(false); - set1.setColor(ColorTemplate.COLORFUL_COLORS[0], 130); - set1.setDrawValues(true); - - BubbleDataSet set2 = new BubbleDataSet(values2, "DS 2"); - set2.setDrawIcons(false); - set2.setIconsOffset(new MPPointF(0, 15)); - set2.setColor(ColorTemplate.COLORFUL_COLORS[1], 130); - set2.setDrawValues(true); - - BubbleDataSet set3 = new BubbleDataSet(values3, "DS 3"); - set3.setColor(ColorTemplate.COLORFUL_COLORS[2], 130); - set3.setDrawValues(true); - - ArrayList dataSets = new ArrayList<>(); - dataSets.add(set1); // add the data sets - dataSets.add(set2); - dataSets.add(set3); - - // create a data object with the data sets - BubbleData data = new BubbleData(dataSets); - data.setDrawValues(false); - data.setValueTypeface(tfLight); - data.setValueTextSize(8f); - data.setValueTextColor(Color.WHITE); - data.setHighlightCircleWidth(1.5f); - - chart.setData(data); - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bubble, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BubbleChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawIcons(!set.isDrawIconsEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if(chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) - chart.setPinchZoom(false); - else - chart.setPinchZoom(true); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "BubbleChartActivity"); - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - Log.i("VAL SELECTED", - "Value: " + e.getY() + ", xIndex: " + e.getX() - + ", DataSet index: " + h.getDataSetIndex()); - } - - @Override - public void onNothingSelected() {} - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/BubbleChartActivity.kt b/app/src/main/java/info/appdev/chartexample/BubbleChartActivity.kt new file mode 100644 index 0000000000..7469edf7dc --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/BubbleChartActivity.kt @@ -0,0 +1,245 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.BubbleChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BubbleData +import com.github.mikephil.charting.data.BubbleDataSet +import com.github.mikephil.charting.data.BubbleEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IBubbleDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.MPPointF +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class BubbleChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: BubbleChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_bubblechart) + + setTitle("BubbleChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarX!!.setOnSeekBarChangeListener(this) + + seekBarY = findViewById(R.id.seekBarY) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.description.isEnabled = false + + chart!!.setOnChartValueSelectedListener(this) + + chart!!.drawGridBackground = false + + chart!!.setTouchEnabled(true) + + // enable scaling and dragging + chart!!.isDragEnabled = true + chart!!.setScaleEnabled(true) + + chart!!.setMaxVisibleValueCount(200) + chart!!.setPinchZoom(true) + + seekBarX!!.progress = 10 + seekBarY!!.progress = 50 + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.VERTICAL + l.setDrawInside(false) + l.typeface = tfLight + + val yl = chart!!.axisLeft + yl.typeface = tfLight + yl.spaceTop = 30f + yl.spaceBottom = 30f + yl.setDrawZeroLine(false) + + chart!!.axisRight.isEnabled = false + + val xl = chart!!.xAxis + xl.position = XAxisPosition.BOTTOM + xl.typeface = tfLight + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + val count = seekBarX!!.progress + val range = seekBarY!!.progress + + tvX!!.text = count.toString() + tvY!!.text = range.toString() + + val values1 = ArrayList() + val values2 = ArrayList() + val values3 = ArrayList() + val sampleValues = getValues(100) + + for (i in 0..() + dataSets.add(set1) // add the data sets + dataSets.add(set2) + dataSets.add(set3) + + // create a data object with the data sets + val data = BubbleData(dataSets) + data.setDrawValues(false) + data.setValueTypeface(tfLight) + data.setValueTextSize(8f) + data.setValueTextColor(Color.WHITE) + data.setHighlightCircleWidth(1.5f) + + chart!!.setData(data) + chart!!.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.bubble, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/BubbleChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + for (set in chart!!.data!!.dataSets) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + + chart!!.invalidate() + } + + R.id.actionToggleIcons -> { + for (set in chart!!.data!!.dataSets) set.isDrawIconsEnabled = !set.isDrawIconsEnabled + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionTogglePinch -> { + if (chart!!.isPinchZoomEnabled) chart!!.setPinchZoom(false) + else chart!!.setPinchZoom(true) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "BubbleChartActivity") + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + Log.i( + "VAL SELECTED", + ("Value: " + e?.y + ", xIndex: " + e?.x + + ", DataSet index: " + h?.dataSetIndex) + ) + } + + override fun onNothingSelected() {} + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/CandleStickChartActivity.java b/app/src/main/java/info/appdev/chartexample/CandleStickChartActivity.java deleted file mode 100644 index b66d933f3b..0000000000 --- a/app/src/main/java/info/appdev/chartexample/CandleStickChartActivity.java +++ /dev/null @@ -1,240 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.graphics.Paint; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.CandleStickChart; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.data.CandleData; -import com.github.mikephil.charting.data.CandleDataSet; -import com.github.mikephil.charting.data.CandleEntry; -import com.github.mikephil.charting.interfaces.datasets.ICandleDataSet; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -public class CandleStickChartActivity extends DemoBase implements OnSeekBarChangeListener { - - private CandleStickChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_candlechart); - - setTitle("CandleStickChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setOnSeekBarChangeListener(this); - - seekBarY = findViewById(R.id.seekBarY); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setBackgroundColor(Color.WHITE); - - chart.getDescription().setEnabled(false); - - // if more than 60 entries are displayed in the chart, no values will be - // drawn - chart.setMaxVisibleValueCount(60); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setDrawGridBackground(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTTOM); - xAxis.setDrawGridLines(false); - - YAxis leftAxis = chart.getAxisLeft(); -// leftAxis.setEnabled(false); - leftAxis.setLabelCount(7, false); - leftAxis.setDrawGridLines(false); - leftAxis.setDrawAxisLine(false); - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setEnabled(false); -// rightAxis.setStartAtZero(false); - - // setting data - seekBarX.setProgress(40); - seekBarY.setProgress(100); - - chart.getLegend().setEnabled(false); - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - progress = (seekBarX.getProgress()); - - tvX.setText(String.valueOf(progress)); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - chart.resetTracking(); - - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100); - - for (int i = 0; i < progress; i++) { - float multi = (seekBarY.getProgress() + 1); - float val = (float) (sampleValues[i].floatValue() * 40) + multi; - - float high = (float) (sampleValues[i].floatValue() * 9) + 8f; - float low = (float) (sampleValues[i].floatValue() * 8) + 8f; - - float open = (float) (sampleValues[i].floatValue() * 6) + 1f; - float close = (float) (sampleValues[i].floatValue() * 7) + 1f; - - boolean even = i % 2 == 0; - - values.add(new CandleEntry( - i, val + high, - val - low, - even ? val + open : val - open, - even ? val - close : val + close, - getResources().getDrawable(R.drawable.star) - )); - } - - CandleDataSet set1 = new CandleDataSet(values, "Data Set"); - - set1.setDrawIcons(false); - set1.setAxisDependency(AxisDependency.LEFT); -// set1.setColor(Color.rgb(80, 80, 80)); - set1.setShadowColor(Color.DKGRAY); - set1.setShadowWidth(0.7f); - set1.setDecreasingColor(Color.BLUE); - set1.setDecreasingPaintStyle(Paint.Style.FILL); - set1.setIncreasingColor(Color.rgb(122, 242, 84)); - set1.setIncreasingPaintStyle(Paint.Style.STROKE); - set1.setNeutralColor(Color.BLUE); - //set1.setHighlightLineWidth(1f); - - CandleData data = new CandleData(set1); - - chart.setData(data); - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.candle, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/CandleStickChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawIcons(!set.isDrawIconsEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if(chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) - chart.setPinchZoom(false); - else - chart.setPinchZoom(true); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleMakeShadowSameColorAsCandle: { - for (ICandleDataSet set : chart.getData().getDataSets()) { - ((CandleDataSet) set).setShadowColorSameAsCandle(!set.getShadowColorSameAsCandle()); - } - - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "CandleStickChartActivity"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/CandleStickChartActivity.kt b/app/src/main/java/info/appdev/chartexample/CandleStickChartActivity.kt new file mode 100644 index 0000000000..c76fd4c77e --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/CandleStickChartActivity.kt @@ -0,0 +1,225 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.graphics.Paint +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.CandleStickChart +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.CandleData +import com.github.mikephil.charting.data.CandleDataSet +import com.github.mikephil.charting.data.CandleEntry +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class CandleStickChartActivity : DemoBase(), OnSeekBarChangeListener { + private var chart: CandleStickChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_candlechart) + + setTitle("CandleStickChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarX!!.setOnSeekBarChangeListener(this) + + seekBarY = findViewById(R.id.seekBarY) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setBackgroundColor(Color.WHITE) + + chart!!.description.isEnabled = false + + // if more than 60 entries are displayed in the chart, no values will be + // drawn + chart!!.setMaxVisibleValueCount(60) + + // scaling can now only be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + chart!!.drawGridBackground = false + + val xAxis = chart!!.xAxis + xAxis.position = XAxisPosition.BOTTOM + xAxis.setDrawGridLines(false) + + val leftAxis = chart!!.axisLeft + // leftAxis.setEnabled(false); + leftAxis.setLabelCount(7, false) + leftAxis.setDrawGridLines(false) + leftAxis.setDrawAxisLine(false) + + val rightAxis = chart!!.axisRight + rightAxis.isEnabled = false + + // rightAxis.setStartAtZero(false); + + // setting data + seekBarX!!.progress = 40 + seekBarY!!.progress = 100 + + chart!!.legend.isEnabled = false + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + val progress: Int = (seekBarX!!.progress) + + tvX!!.text = progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + chart!!.resetTracking() + + val values = ArrayList() + val sampleValues = getValues(100) + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/CandleStickChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + for (set in chart!!.data!!.dataSets) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + + chart!!.invalidate() + } + + R.id.actionToggleIcons -> { + for (set in chart!!.data!!.dataSets) set.isDrawIconsEnabled = !set.isDrawIconsEnabled + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionTogglePinch -> { + if (chart!!.isPinchZoomEnabled) chart!!.setPinchZoom(false) + else chart!!.setPinchZoom(true) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionToggleMakeShadowSameColorAsCandle -> { + for (set in chart!!.data!!.dataSets) { + (set as CandleDataSet).setShadowColorSameAsCandle(!set.shadowColorSameAsCandle) + } + + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "CandleStickChartActivity") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/CombinedChartActivity.java b/app/src/main/java/info/appdev/chartexample/CombinedChartActivity.java deleted file mode 100644 index bb185a7c76..0000000000 --- a/app/src/main/java/info/appdev/chartexample/CombinedChartActivity.java +++ /dev/null @@ -1,284 +0,0 @@ - -package info.appdev.chartexample; - -import android.content.Intent; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; - -import com.github.mikephil.charting.charts.CombinedChart; -import com.github.mikephil.charting.charts.CombinedChart.DrawOrder; -import com.github.mikephil.charting.components.AxisBase; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.BubbleData; -import com.github.mikephil.charting.data.BubbleDataSet; -import com.github.mikephil.charting.data.BubbleEntry; -import com.github.mikephil.charting.data.CandleData; -import com.github.mikephil.charting.data.CandleDataSet; -import com.github.mikephil.charting.data.CandleEntry; -import com.github.mikephil.charting.data.CombinedData; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.data.ScatterData; -import com.github.mikephil.charting.data.ScatterDataSet; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -public class CombinedChartActivity extends DemoBase { - - private CombinedChart chart; - private final int sampleCount = 12; - Double[] values = DataTools.Companion.getValues(sampleCount * 2); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_combined); - - setTitle("CombinedChartActivity"); - - chart = findViewById(R.id.chart1); - chart.getDescription().setEnabled(false); - chart.setBackgroundColor(Color.WHITE); - chart.setDrawGridBackground(false); - chart.setDrawBarShadow(false); - chart.setHighlightFullBarEnabled(false); - - // draw bars behind lines - chart.setDrawOrder(new DrawOrder[]{ - DrawOrder.BAR, DrawOrder.BUBBLE, DrawOrder.CANDLE, DrawOrder.LINE, DrawOrder.SCATTER - }); - - Legend l = chart.getLegend(); - l.setWordWrapEnabled(true); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setDrawGridLines(false); - rightAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setDrawGridLines(false); - leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - XAxis xAxis = chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTH_SIDED); - xAxis.setAxisMinimum(0f); - xAxis.setGranularity(1f); - xAxis.setValueFormatter(new IAxisValueFormatter() { - @Override - public String getFormattedValue(float value, AxisBase axis) { - return DemoBase.Companion.getMonths().get((int) value % DemoBase.Companion.getMonths().size()); - } - }); - - CombinedData data = new CombinedData(); - - data.setData(generateLineData()); - data.setData(generateBarData()); - data.setData(generateBubbleData()); - data.setData(generateScatterData()); - data.setData(generateCandleData()); - data.setValueTypeface(tfLight); - - xAxis.setAxisMaximum(data.getXMax() + 0.25f); - - chart.setData(data); - chart.invalidate(); - } - - private LineData generateLineData() { - - LineData d = new LineData(); - - ArrayList entries = new ArrayList<>(); - - for (int index = 0; index < sampleCount; index++) - entries.add(new Entry(index + 0.5f, values[index].floatValue() * 15 + 5)); - - LineDataSet set = new LineDataSet(entries, "Line DataSet"); - set.setColor(Color.rgb(240, 238, 70)); - set.setLineWidth(2.5f); - set.setCircleColor(Color.rgb(240, 238, 70)); - set.setCircleRadius(5f); - set.setFillColor(Color.rgb(240, 238, 70)); - set.setMode(LineDataSet.Mode.CUBIC_BEZIER); - set.setDrawValues(true); - set.setValueTextSize(10f); - set.setValueTextColor(Color.rgb(240, 238, 70)); - - set.setAxisDependency(YAxis.AxisDependency.LEFT); - d.addDataSet(set); - - return d; - } - - private BarData generateBarData() { - - ArrayList entries1 = new ArrayList<>(); - ArrayList entries2 = new ArrayList<>(); - - for (int index = 0; index < sampleCount; index++) { - entries1.add(new BarEntry(0, values[index].floatValue() * 25 + 25)); - - // stacked - entries2.add(new BarEntry(0, new float[]{values[index].floatValue() * 13 + 12, values[index].floatValue() * 13 + 12})); - } - - BarDataSet set1 = new BarDataSet(entries1, "Bar 1"); - set1.setColor(Color.rgb(60, 220, 78)); - set1.setValueTextColor(Color.rgb(60, 220, 78)); - set1.setValueTextSize(10f); - set1.setAxisDependency(YAxis.AxisDependency.LEFT); - - BarDataSet set2 = new BarDataSet(entries2, ""); - set2.setStackLabels(new String[]{"Stack 1", "Stack 2"}); - set2.setColors(Color.rgb(61, 165, 255), Color.rgb(23, 197, 255)); - set2.setValueTextColor(Color.rgb(61, 165, 255)); - set2.setValueTextSize(10f); - set2.setAxisDependency(YAxis.AxisDependency.LEFT); - - float groupSpace = 0.06f; - float barSpace = 0.02f; // x2 dataset - float barWidth = 0.45f; // x2 dataset - // (0.45 + 0.02) * 2 + 0.06 = 1.00 -> interval per "group" - - BarData d = new BarData(set1, set2); - d.setBarWidth(barWidth); - - // make this BarData object grouped - d.groupBars(0, groupSpace, barSpace); // start at x = 0 - - return d; - } - - private ScatterData generateScatterData() { - - ScatterData d = new ScatterData(); - - ArrayList entries = new ArrayList<>(); - - for (float index = 0; index < sampleCount; index += 0.5f) - entries.add(new Entry(index + 0.25f, values[Math.round(index*2)].floatValue() * 10 + 55)); - - ScatterDataSet set = new ScatterDataSet(entries, "Scatter DataSet"); - set.setColors(ColorTemplate.MATERIAL_COLORS); - set.setScatterShapeSize(7.5f); - set.setDrawValues(false); - set.setValueTextSize(10f); - d.addDataSet(set); - - return d; - } - - private CandleData generateCandleData() { - - CandleData d = new CandleData(); - - ArrayList entries = new ArrayList<>(); - - for (int index = 0; index < sampleCount; index += 2) - entries.add(new CandleEntry(index + 1f, 90, 70, 85, 75f)); - - CandleDataSet set = new CandleDataSet(entries, "Candle DataSet"); - set.setDecreasingColor(Color.rgb(142, 150, 175)); - set.setShadowColor(Color.DKGRAY); - set.setBarSpace(0.3f); - set.setValueTextSize(10f); - set.setDrawValues(false); - d.addDataSet(set); - - return d; - } - - private BubbleData generateBubbleData() { - - BubbleData bd = new BubbleData(); - - ArrayList entries = new ArrayList<>(); - - for (int index = 0; index < sampleCount; index++) { - float y = values[index].floatValue() * 10 + 105; - float size = values[index].floatValue() * 100 + 105; - entries.add(new BubbleEntry(index + 0.5f, y, size)); - } - - BubbleDataSet set = new BubbleDataSet(entries, "Bubble DataSet"); - set.setColors(ColorTemplate.VORDIPLOM_COLORS); - set.setValueTextSize(10f); - set.setValueTextColor(Color.WHITE); - set.setHighlightCircleWidth(1.5f); - set.setDrawValues(true); - bd.addDataSet(set); - - return bd; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.combined, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/CombinedChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleLineValues: { - for (IDataSet set : chart.getData().getDataSets()) { - if (set instanceof LineDataSet) - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleBarValues: { - for (IDataSet set : chart.getData().getDataSets()) { - if (set instanceof BarDataSet) - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionRemoveDataSet: { - int rnd = (int) values[sampleCount].floatValue() * chart.getData().getDataSetCount(); - chart.getData().removeDataSet(chart.getData().getDataSetByIndex(rnd)); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - chart.invalidate(); - break; - } - } - return true; - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } -} diff --git a/app/src/main/java/info/appdev/chartexample/CombinedChartActivity.kt b/app/src/main/java/info/appdev/chartexample/CombinedChartActivity.kt new file mode 100644 index 0000000000..a5f5b49a5e --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/CombinedChartActivity.kt @@ -0,0 +1,273 @@ +package info.appdev.chartexample + +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.CombinedChart +import com.github.mikephil.charting.charts.CombinedChart.DrawOrder +import com.github.mikephil.charting.components.AxisBase +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.BubbleData +import com.github.mikephil.charting.data.BubbleDataSet +import com.github.mikephil.charting.data.BubbleEntry +import com.github.mikephil.charting.data.CandleData +import com.github.mikephil.charting.data.CandleDataSet +import com.github.mikephil.charting.data.CandleEntry +import com.github.mikephil.charting.data.CombinedData +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.data.ScatterData +import com.github.mikephil.charting.data.ScatterDataSet +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase +import kotlin.math.roundToInt + +class CombinedChartActivity : DemoBase() { + private var chart: CombinedChart? = null + private val sampleCount = 12 + var values: Array = getValues(sampleCount * 2) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_combined) + + setTitle("CombinedChartActivity") + + chart = findViewById(R.id.chart1) + chart!!.description.isEnabled = false + chart!!.setBackgroundColor(Color.WHITE) + chart!!.drawGridBackground = false + chart!!.isDrawBarShadowEnabled = false + chart!!.isHighlightFullBarEnabled = false + + // draw bars behind lines + chart!!.drawOrder = arrayOf( + DrawOrder.BAR, DrawOrder.BUBBLE, DrawOrder.CANDLE, DrawOrder.LINE, DrawOrder.SCATTER + ) + + val l = chart!!.legend + l.isWordWrapEnabled = true + l.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + l.horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + + val rightAxis = chart!!.axisRight + rightAxis.setDrawGridLines(false) + rightAxis.axisMinimum = 0f // this replaces setStartAtZero(true) + + val leftAxis = chart!!.axisLeft + leftAxis.setDrawGridLines(false) + leftAxis.axisMinimum = 0f // this replaces setStartAtZero(true) + + val xAxis = chart!!.xAxis + xAxis.position = XAxisPosition.BOTH_SIDED + xAxis.axisMinimum = 0f + xAxis.granularity = 1f + xAxis.valueFormatter = object : IAxisValueFormatter { + override fun getFormattedValue(value: Float, axis: AxisBase?): String { + return months[value.toInt() % months.size] + } + } + + val data = CombinedData() + + data.setData(generateLineData()) + data.setData(generateBarData()) + data.setData(generateBubbleData()) + data.setData(generateScatterData()) + data.setData(generateCandleData()) + data.setValueTypeface(tfLight) + + xAxis.axisMaximum = data.xMax + 0.25f + + chart!!.setData(data) + chart!!.invalidate() + } + + private fun generateLineData(): LineData { + val d = LineData() + + val entries = ArrayList() + + for (index in 0..() + val entries2 = ArrayList() + + for (index in 0.. interval per "group" + val d = BarData(set1, set2) + d.barWidth = barWidth + + // make this BarData object grouped + d.groupBars(0f, groupSpace, barSpace) // start at x = 0 + + return d + } + + private fun generateScatterData(): ScatterData { + val d = ScatterData() + + val entries = ArrayList() + + var index = 0f + while (index < sampleCount) { + entries.add(Entry(index + 0.25f, values[(index * 2).roundToInt()].toFloat() * 10 + 55)) + index += 0.5f + } + + val set = ScatterDataSet(entries, "Scatter DataSet") + set.setColors(*ColorTemplate.MATERIAL_COLORS) + set.scatterShapeSize = 7.5f + set.isDrawValuesEnabled = false + set.valueTextSize = 10f + d.addDataSet(set) + + return d + } + + private fun generateCandleData(): CandleData { + val d = CandleData() + + val entries = ArrayList() + + var index = 0 + while (index < sampleCount) { + entries.add(CandleEntry(index + 1f, 90f, 70f, 85f, 75f)) + index += 2 + } + + val set = CandleDataSet(entries, "Candle DataSet") + set.setDecreasingColor(Color.rgb(142, 150, 175)) + set.setShadowColor(Color.DKGRAY) + set.setBarSpace(0.3f) + set.valueTextSize = 10f + set.isDrawValuesEnabled = false + d.addDataSet(set) + + return d + } + + private fun generateBubbleData(): BubbleData { + val bd = BubbleData() + + val entries = ArrayList() + + for (index in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/CombinedChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleLineValues -> { + for (set in chart!!.data!!.dataSets) { + if (set is LineDataSet) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleBarValues -> { + for (set in chart!!.data!!.dataSets) { + if (set is BarDataSet) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionRemoveDataSet -> { + val rnd = (values[sampleCount] * chart!!.data!!.dataSetCount).toInt() + chart!!.data!!.removeDataSet(chart!!.data!!.getDataSetByIndex(rnd)) + chart!!.data!!.notifyDataChanged() + chart!!.notifyDataSetChanged() + chart!!.invalidate() + } + } + return true + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } +} diff --git a/app/src/main/java/info/appdev/chartexample/CubicLineChartActivity.kt b/app/src/main/java/info/appdev/chartexample/CubicLineChartActivity.kt index e8b6d6236c..2b08ff7c25 100644 --- a/app/src/main/java/info/appdev/chartexample/CubicLineChartActivity.kt +++ b/app/src/main/java/info/appdev/chartexample/CubicLineChartActivity.kt @@ -5,7 +5,6 @@ import android.annotation.SuppressLint import android.content.Intent import android.content.pm.PackageManager import android.graphics.Color -import android.net.Uri import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -14,6 +13,7 @@ import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.core.net.toUri import com.github.mikephil.charting.components.YAxis import com.github.mikephil.charting.data.Entry import com.github.mikephil.charting.data.LineData @@ -50,25 +50,25 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { binding.chart1.setBackgroundColor(Color.rgb(104, 241, 175)) // no description text - binding.chart1.getDescription().isEnabled = false + binding.chart1.description.isEnabled = false // enable touch gestures binding.chart1.setTouchEnabled(true) // enable scaling and dragging - binding.chart1.setDragEnabled(true) + binding.chart1.isDragEnabled = true binding.chart1.setScaleEnabled(true) // if disabled, scaling can be done on x- and y-axis separately binding.chart1.setPinchZoom(false) - binding.chart1.setDrawGridBackground(false) + binding.chart1.drawGridBackground = false binding.chart1.setMaxHighlightDistance(300f) - val x = binding.chart1.getXAxis() + val x = binding.chart1.xAxis x.isEnabled = false - val y = binding.chart1.getAxisLeft() + val y = binding.chart1.axisLeft y.typeface = tfLight y.setLabelCount(6, false) y.textColor = Color.WHITE @@ -76,7 +76,7 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { y.setDrawGridLines(false) y.axisLineColor = Color.WHITE - binding.chart1.getAxisRight().isEnabled = false + binding.chart1.axisRight.isEnabled = false // add data binding.seekBarY.setOnSeekBarChangeListener(this) @@ -85,10 +85,10 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { // lower max, as cubic runs significantly slower than linear binding.seekBarX.setMax(700) - binding.seekBarX.setProgress(45) - binding.seekBarY.setProgress(100) + binding.seekBarX.progress = 45 + binding.seekBarY.progress = 100 - binding.chart1.getLegend().isEnabled = false + binding.chart1.legend.isEnabled = false binding.chart1.animateXY(2000, 2000) @@ -120,18 +120,18 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { set1.mode = LineDataSet.Mode.CUBIC_BEZIER set1.cubicIntensity = 0.2f - set1.setDrawFilled(true) - set1.setDrawCircles(false) + set1.isDrawFilledEnabled = true + set1.isDrawCirclesEnabled = false set1.lineWidth = 1.8f set1.circleRadius = 4f set1.setCircleColor(Color.WHITE) - set1.setHighLightColor(Color.rgb(244, 117, 117)) - set1.color = Color.WHITE + set1.highLightColor = Color.rgb(244, 117, 117) + set1.setColor(Color.WHITE) set1.fillColor = Color.WHITE set1.fillAlpha = 100 set1.setDrawHorizontalHighlightIndicator(false) set1.fillFormatter = object : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider?): Float { + override fun getFillLinePosition(dataSet: ILineDataSet, dataProvider: LineDataProvider): Float { return binding.chart1.axisLeft.axisMinimum } } @@ -143,7 +143,7 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { data.setDrawValues(false) // set data - binding.chart1.data = data + binding.chart1.setData(data) } } @@ -156,12 +156,12 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { when (item.itemId) { R.id.viewGithub -> { val i = Intent(Intent.ACTION_VIEW) - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/CubicLineChartActivity.java")) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/CubicLineChartActivity.java".toUri()) startActivity(i) } R.id.actionToggleValues -> { - for (set in binding.chart1.data!!.dataSets) set.setDrawValues(!set.isDrawValuesEnabled) + for (set in binding.chart1.data!!.dataSets) set.isDrawValuesEnabled = !set.isDrawValuesEnabled binding.chart1.invalidate() } @@ -174,63 +174,67 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { } R.id.actionToggleFilled -> { - val sets = binding.chart1.data!!.getDataSets() + val sets = binding.chart1.data!!.dataSets for (iSet in sets) { val set = iSet as LineDataSet - if (set.isDrawFilledEnabled) set.setDrawFilled(false) - else set.setDrawFilled(true) + set.isDrawFilledEnabled = !set.isDrawFilledEnabled } binding.chart1.invalidate() } R.id.actionToggleCircles -> { - val sets = binding.chart1.data!!.getDataSets() + val sets = binding.chart1.data!!.dataSets for (iSet in sets) { val set = iSet as LineDataSet - if (set.isDrawCirclesEnabled) set.setDrawCircles(false) - else set.setDrawCircles(true) + set.isDrawCirclesEnabled = !set.isDrawCirclesEnabled } binding.chart1.invalidate() } R.id.actionToggleCubic -> { - val sets = binding.chart1.data!!.getDataSets() + val sets = binding.chart1.data!!.dataSets for (iSet in sets) { val set = iSet as LineDataSet - set.mode = if (set.mode == LineDataSet.Mode.CUBIC_BEZIER) - LineDataSet.Mode.LINEAR - else - LineDataSet.Mode.CUBIC_BEZIER + set.mode = ( + if (set.mode == LineDataSet.Mode.CUBIC_BEZIER) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.CUBIC_BEZIER + ) } binding.chart1.invalidate() } R.id.actionToggleStepped -> { - val sets = binding.chart1.data!!.getDataSets() + val sets = binding.chart1.data!!.dataSets for (iSet in sets) { val set = iSet as LineDataSet - set.mode = if (set.mode == LineDataSet.Mode.STEPPED) - LineDataSet.Mode.LINEAR - else - LineDataSet.Mode.STEPPED + set.mode = ( + if (set.mode == LineDataSet.Mode.STEPPED) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.STEPPED + ) } binding.chart1.invalidate() } R.id.actionToggleHorizontalCubic -> { - val sets = binding.chart1.data!!.getDataSets() + val sets = binding.chart1.data!!.dataSets for (iSet in sets) { val set = iSet as LineDataSet - set.mode = if (set.mode == LineDataSet.Mode.HORIZONTAL_BEZIER) - LineDataSet.Mode.LINEAR - else - LineDataSet.Mode.HORIZONTAL_BEZIER + set.mode = ( + if (set.mode == LineDataSet.Mode.HORIZONTAL_BEZIER) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.HORIZONTAL_BEZIER + ) } binding.chart1.invalidate() } diff --git a/app/src/main/java/info/appdev/chartexample/DataTools.kt b/app/src/main/java/info/appdev/chartexample/DataTools.kt index fe8a0b27de..a0e60ecfad 100644 --- a/app/src/main/java/info/appdev/chartexample/DataTools.kt +++ b/app/src/main/java/info/appdev/chartexample/DataTools.kt @@ -133,7 +133,7 @@ class DataTools { 44.768654, -25.790316, 5.9754066, 99.64748, 141.99321, -17.990795, 38.272446 ) - fun getValues(size: Int) = VAL_102.copyOf(size) + fun getValues(size: Int) = VAL_102.take(size).toTypedArray() fun getMuchValues(size: Int): Array { var result = VAL_102.copyOf(VAL_102.size) @@ -176,13 +176,13 @@ class DataTools { ) { // create a dataset and give it a type val lineDataSet01 = LineDataSet(values, "DataSet 1") - lineDataSet01.setDrawIcons(false) + lineDataSet01.isDrawIconsEnabled = false // draw dashed line lineDataSet01.enableDashedLine(10f, 5f, 0f) // black lines and points - lineDataSet01.color = Color.BLACK + lineDataSet01.setColor(Color.BLACK) lineDataSet01.setCircleColor(Color.BLACK) // line thickness and point size @@ -190,11 +190,11 @@ class DataTools { lineDataSet01.circleRadius = 3f // draw points as solid circles - lineDataSet01.setDrawCircleHole(false) + lineDataSet01.isDrawCircleHoleEnabled = false // customize legend entry lineDataSet01.formLineWidth = 1f - lineDataSet01.setFormLineDashEffect(DashPathEffect(floatArrayOf(10f, 5f), 0f)) + lineDataSet01.formLineDashEffect = DashPathEffect(floatArrayOf(10f, 5f), 0f) lineDataSet01.formSize = 15f // text size of values @@ -204,15 +204,15 @@ class DataTools { lineDataSet01.enableDashedHighlightLine(10f, 5f, 0f) // set the filled area - lineDataSet01.setDrawFilled(true) + lineDataSet01.isDrawFilledEnabled = true lineDataSet01.fillFormatter = object : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider?): Float { + override fun getFillLinePosition(dataSet: ILineDataSet, dataProvider: LineDataProvider): Float { return lineChart.axisLeft.axisMinimum } } // set color of filled area - if (Utils.getSDKInt() >= 18) { + if (Utils.sDKInt >= 18) { // drawables only supported on api level 18 and above val drawable = ContextCompat.getDrawable(context, R.drawable.fade_blue) lineDataSet01.fillDrawable = drawable @@ -226,7 +226,7 @@ class DataTools { val data = LineData(dataSets) // set data - lineChart.data = data + lineChart.setData(data) } } } diff --git a/app/src/main/java/info/appdev/chartexample/DrawChartActivity.kt b/app/src/main/java/info/appdev/chartexample/DrawChartActivity.kt index 272514c2cc..b4b6884e08 100644 --- a/app/src/main/java/info/appdev/chartexample/DrawChartActivity.kt +++ b/app/src/main/java/info/appdev/chartexample/DrawChartActivity.kt @@ -46,7 +46,7 @@ class DrawChartActivity : DemoBase(), OnChartValueSelectedListener, OnDrawListen // if disabled, drawn data sets with the finger will not be automatically // finished // chart.setAutoFinish(true); - binding.chart1.setDrawGridBackground(false) + binding.chart1.drawGridBackground = false // add dummy-data to the chart initWithDummyData() @@ -76,7 +76,7 @@ class DrawChartActivity : DemoBase(), OnChartValueSelectedListener, OnDrawListen // create a data object with the data sets val data = LineData(set1) - binding.chart1.data = data + binding.chart1.setData(data) } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -91,7 +91,7 @@ class DrawChartActivity : DemoBase(), OnChartValueSelectedListener, OnDrawListen for (iSet in sets) { val set = iSet as LineDataSet - set.setDrawValues(!set.isDrawValuesEnabled) + set.isDrawValuesEnabled = !set.isDrawValuesEnabled } binding.chart1.invalidate() @@ -131,26 +131,26 @@ class DrawChartActivity : DemoBase(), OnChartValueSelectedListener, OnDrawListen saveToGallery(binding.chart1, "DrawChartActivity") } - override fun onValueSelected(e: Entry, h: Highlight) { - Log.i("VAL SELECTED", ("Value: " + e.y + ", xIndex: " + e.x + ", DataSet index: " + h.dataSetIndex)) + override fun onValueSelected(e: Entry?, h: Highlight?) { + Log.i("VAL SELECTED", ("Value: " + e?.y + ", xIndex: " + e?.x + ", DataSet index: " + h?.dataSetIndex)) } override fun onNothingSelected() = Unit /** callback for each new entry drawn with the finger */ - override fun onEntryAdded(entry: Entry) { + override fun onEntryAdded(entry: Entry?) { Log.i(Chart.LOG_TAG, entry.toString()) } /** callback when a DataSet has been drawn (when lifting the finger) */ - override fun onDrawFinished(dataSet: DataSet<*>) { - Log.i(Chart.LOG_TAG, "DataSet drawn. " + dataSet.toSimpleString()) + override fun onDrawFinished(dataSet: DataSet<*>?) { + Log.i(Chart.LOG_TAG, "DataSet drawn. " + dataSet?.toSimpleString()) // prepare the legend again binding.chart1.data?.let { binding.chart1.legendRenderer.computeLegend(it) } } - override fun onEntryMoved(entry: Entry) { + override fun onEntryMoved(entry: Entry?) { Log.i(Chart.LOG_TAG, "Point moved $entry") } } diff --git a/app/src/main/java/info/appdev/chartexample/DynamicalAddingActivity.java b/app/src/main/java/info/appdev/chartexample/DynamicalAddingActivity.java deleted file mode 100644 index f97b19f950..0000000000 --- a/app/src/main/java/info/appdev/chartexample/DynamicalAddingActivity.java +++ /dev/null @@ -1,248 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.Toast; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -public class DynamicalAddingActivity extends DemoBase implements OnChartValueSelectedListener { - - private LineChart chart; - Double[] sampleValues = DataTools.Companion.getValues(102); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_linechart_noseekbar); - - setTitle("DynamicalAddingActivity"); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - chart.setDrawGridBackground(false); - chart.getDescription().setEnabled(false); - chart.setNoDataText("No chart data available. Use the menu to add entries and data sets!"); - -// chart.getXAxis().setDrawLabels(false); -// chart.getXAxis().setDrawGridLines(false); - - chart.invalidate(); - } - - private final int[] colors = ColorTemplate.VORDIPLOM_COLORS; - - private void addEntry() { - - LineData data = chart.getData(); - - if (data == null) { - data = new LineData(); - chart.setData(data); - } - - ILineDataSet set = data.getDataSetByIndex(0); - // set.addEntry(...); // can be called as well - - if (set == null) { - set = createSet(); - data.addDataSet(set); - } - - int lastDataSetIndex = data.getDataSetCount() - 1; // add data only to the last - ILineDataSet lastSet = data.getDataSetByIndex(lastDataSetIndex); - - int cycleValue = (int) (lastSet.getEntryCount() % 100.0); - - float value = (sampleValues[cycleValue].floatValue() * 50) + 50f * (lastDataSetIndex + 1); - - data.addEntry(new Entry(lastSet.getEntryCount(), value), lastDataSetIndex); - data.notifyDataChanged(); - - // let the chart know it's data has changed - chart.notifyDataSetChanged(); - - chart.setVisibleXRangeMaximum(6); - //chart.setVisibleYRangeMaximum(15, AxisDependency.LEFT); -// -// // this automatically refreshes the chart (calls invalidate()) - chart.moveViewTo(data.getEntryCount() - 7, 50f, AxisDependency.LEFT); - - } - - private void removeLastEntry() { - - LineData data = chart.getData(); - - if (data != null) { - - ILineDataSet set = data.getDataSetByIndex(0); - - if (set != null) { - - Entry e = set.getEntryForXValue(set.getEntryCount() - 1, Float.NaN); - - data.removeEntry(e, 0); - // or remove by index - // mData.removeEntryByXValue(xIndex, dataSetIndex); - data.notifyDataChanged(); - chart.notifyDataSetChanged(); - chart.invalidate(); - } - } - } - - private void addDataSet() { - - LineData data = chart.getData(); - - if (data == null) { - chart.setData(new LineData()); - } else { - int count = (data.getDataSetCount() + 1); - int amount = data.getDataSetByIndex(0).getEntryCount(); - - ArrayList values = new ArrayList<>(); - - for (int i = 0; i < amount; i++) { - int cycleValue = (int) (i % 100.0); - - values.add(new Entry(i, (sampleValues[cycleValue].floatValue() * 50f) + 50f * count)); - } - - LineDataSet set = new LineDataSet(values, "DataSet " + count); - set.setLineWidth(2.5f); - set.setCircleRadius(4.5f); - - int color = colors[count % colors.length]; - - set.setColor(color); - set.setCircleColor(color); - set.setHighLightColor(color); - set.setValueTextSize(10f); - set.setValueTextColor(color); - - data.addDataSet(set); - data.notifyDataChanged(); - chart.notifyDataSetChanged(); - chart.invalidate(); - } - } - - private void removeDataSet() { - - LineData data = chart.getData(); - - if (data != null) { - - data.removeDataSet(data.getDataSetByIndex(data.getDataSetCount() - 1)); - - chart.notifyDataSetChanged(); - chart.invalidate(); - } - } - - private LineDataSet createSet() { - - LineDataSet set = new LineDataSet(null, "DataSet 1"); - set.setLineWidth(2.5f); - set.setCircleRadius(4.5f); - set.setColor(Color.rgb(240, 99, 99)); - set.setCircleColor(Color.rgb(240, 99, 99)); - set.setHighLightColor(Color.rgb(190, 190, 190)); - set.setAxisDependency(AxisDependency.LEFT); - set.setValueTextSize(10f); - - return set; - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - Toast.makeText(this, e.toString(), Toast.LENGTH_SHORT).show(); - } - - @Override - public void onNothingSelected() {} - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.dynamical, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/DynamicalAddingActivity.java")); - startActivity(i); - break; - } - case R.id.actionAddEntry: { - addEntry(); - Toast.makeText(this, "Entry added!", Toast.LENGTH_SHORT).show(); - break; - } - case R.id.actionRemoveEntry: { - removeLastEntry(); - Toast.makeText(this, "Entry removed!", Toast.LENGTH_SHORT).show(); - break; - } - case R.id.actionAddDataSet: { - addDataSet(); - Toast.makeText(this, "DataSet added!", Toast.LENGTH_SHORT).show(); - break; - } - case R.id.actionRemoveDataSet: { - removeDataSet(); - Toast.makeText(this, "DataSet removed!", Toast.LENGTH_SHORT).show(); - break; - } - case R.id.actionClear: { - chart.clear(); - Toast.makeText(this, "Chart cleared!", Toast.LENGTH_SHORT).show(); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "DynamicalAddingActivity"); - } -} diff --git a/app/src/main/java/info/appdev/chartexample/DynamicalAddingActivity.kt b/app/src/main/java/info/appdev/chartexample/DynamicalAddingActivity.kt new file mode 100644 index 0000000000..0942e292ef --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/DynamicalAddingActivity.kt @@ -0,0 +1,224 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class DynamicalAddingActivity : DemoBase(), OnChartValueSelectedListener { + private var chart: LineChart? = null + var sampleValues: Array = getValues(102) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_linechart_noseekbar) + + setTitle("DynamicalAddingActivity") + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + chart!!.drawGridBackground = false + chart!!.description.isEnabled = false + chart!!.setNoDataText("No chart data available. Use the menu to add entries and data sets!") + + // chart.getXAxis().setDrawLabels(false); +// chart.getXAxis().setDrawGridLines(false); + chart!!.invalidate() + } + + private val colors = ColorTemplate.VORDIPLOM_COLORS + + private fun addEntry() { + var data: LineData? = chart!!.data + + if (data == null) { + data = LineData() + chart!!.setData(data) + } + + var set: LineDataSet + + // set.addEntry(...); // can be called as well + if (data.dataSetCount == 0) { + set = createSet() + data.addDataSet(set) + } + + val lastDataSetIndex = data.dataSetCount - 1 // add data only to the last + val lastSet = data.getDataSetByIndex(lastDataSetIndex) + + val cycleValue = (lastSet.entryCount % 100.0).toInt() + + val value = (sampleValues[cycleValue].toFloat() * 50) + 50f * (lastDataSetIndex + 1) + + data.addEntry(Entry(lastSet.entryCount.toFloat(), value), lastDataSetIndex) + data.notifyDataChanged() + + // let the chart know it's data has changed + chart!!.notifyDataSetChanged() + + chart!!.setVisibleXRangeMaximum(6f) + //chart.setVisibleYRangeMaximum(15, AxisDependency.LEFT); +// +// // this automatically refreshes the chart (calls invalidate()) + chart!!.moveViewTo((data.entryCount - 7).toFloat(), 50f, AxisDependency.LEFT) + } + + private fun removeLastEntry() { + val data: LineData? = chart!!.data + + if (data != null) { + val set = data.getDataSetByIndex(0) + + val e = set.getEntryForXValue(set.entryCount - 1f, Float.Companion.NaN) + + data.removeEntry(e, 0) + // or remove by index + // mData.removeEntryByXValue(xIndex, dataSetIndex); + data.notifyDataChanged() + chart!!.notifyDataSetChanged() + chart!!.invalidate() + } + } + + private fun addDataSet() { + val data: LineData? = chart!!.data + + if (data == null) { + chart!!.setData(LineData()) + } else { + val count = (data.dataSetCount + 1) + val amount = data.getDataSetByIndex(0).entryCount + + val values = ArrayList() + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/DynamicalAddingActivity.java".toUri()) + startActivity(i) + } + + R.id.actionAddEntry -> { + addEntry() + Toast.makeText(this, "Entry added!", Toast.LENGTH_SHORT).show() + } + + R.id.actionRemoveEntry -> { + removeLastEntry() + Toast.makeText(this, "Entry removed!", Toast.LENGTH_SHORT).show() + } + + R.id.actionAddDataSet -> { + addDataSet() + Toast.makeText(this, "DataSet added!", Toast.LENGTH_SHORT).show() + } + + R.id.actionRemoveDataSet -> { + removeDataSet() + Toast.makeText(this, "DataSet removed!", Toast.LENGTH_SHORT).show() + } + + R.id.actionClear -> { + chart!!.clear() + Toast.makeText(this, "Chart cleared!", Toast.LENGTH_SHORT).show() + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "DynamicalAddingActivity") + } +} diff --git a/app/src/main/java/info/appdev/chartexample/FilledLineActivity.java b/app/src/main/java/info/appdev/chartexample/FilledLineActivity.java deleted file mode 100644 index f782c20459..0000000000 --- a/app/src/main/java/info/appdev/chartexample/FilledLineActivity.java +++ /dev/null @@ -1,190 +0,0 @@ - -package info.appdev.chartexample; - -import android.content.Intent; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.formatter.IFillFormatter; -import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -/** - * This works by inverting the background and desired "fill" color. First, we draw the fill color - * that we want between the lines as the actual background of the chart. Then, we fill the area - * above the highest line and the area under the lowest line with the desired background color. - * This method makes it look like we filled the area between the lines, but really we are filling - * the area OUTSIDE the lines! - */ -@SuppressWarnings("SameParameterValue") -public class FilledLineActivity extends DemoBase { - - private LineChart chart; - private final int fillColor = Color.argb(150, 51, 181, 229); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_linechart_noseekbar); - - setTitle("FilledLineActivity"); - - chart = findViewById(R.id.chart1); - chart.setBackgroundColor(Color.WHITE); - chart.setGridBackgroundColor(fillColor); - chart.setDrawGridBackground(true); - - chart.setDrawBorders(true); - - // no description text - chart.getDescription().setEnabled(false); - - // if disabled, scaling can be done on x- and y-axis separately - chart.setPinchZoom(false); - - Legend l = chart.getLegend(); - l.setEnabled(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setEnabled(false); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setAxisMaximum(900f); - leftAxis.setAxisMinimum(-250f); - leftAxis.setDrawAxisLine(false); - leftAxis.setDrawZeroLine(false); - leftAxis.setDrawGridLines(false); - - chart.getAxisRight().setEnabled(false); - - // add data - setData(60); - - chart.invalidate(); - } - - private void setData(float range) { - int count = 100; - ArrayList values1 = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(count + 2); - - for (int i = 0; i < count; i++) { - float val = (sampleValues[i].floatValue() * range) + 50; - values1.add(new Entry(i, val)); - } - - ArrayList values2 = new ArrayList<>(); - - for (int i = 0; i < count; i++) { - float val = (sampleValues[i+1].floatValue() * range) + 450; - values2.add(new Entry(i, val)); - } - - LineDataSet set1, set2; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set1 = (LineDataSet) chart.getData().getDataSetByIndex(0); - set2 = (LineDataSet) chart.getData().getDataSetByIndex(1); - set1.setEntries(values1); - set2.setEntries(values2); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - } else { - // create a dataset and give it a type - set1 = new LineDataSet(values1, "DataSet 1"); - - set1.setAxisDependency(YAxis.AxisDependency.LEFT); - set1.setColor(Color.rgb(255, 241, 46)); - set1.setDrawCircles(false); - set1.setLineWidth(2f); - set1.setCircleRadius(3f); - set1.setFillAlpha(255); - set1.setDrawFilled(true); - set1.setFillColor(Color.WHITE); - set1.setHighLightColor(Color.rgb(244, 117, 117)); - set1.setDrawCircleHole(false); - set1.setFillFormatter(new IFillFormatter() { - @Override - public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) { - // change the return value here to better understand the effect - // return 0; - return chart.getAxisLeft().getAxisMinimum(); - } - }); - - // create a dataset and give it a type - set2 = new LineDataSet(values2, "DataSet 2"); - set2.setAxisDependency(YAxis.AxisDependency.LEFT); - set2.setColor(Color.rgb(255, 241, 46)); - set2.setDrawCircles(false); - set2.setLineWidth(2f); - set2.setCircleRadius(3f); - set2.setFillAlpha(255); - set2.setDrawFilled(true); - set2.setFillColor(Color.WHITE); - set2.setDrawCircleHole(false); - set2.setHighLightColor(Color.rgb(244, 117, 117)); - set2.setFillFormatter(new IFillFormatter() { - @Override - public float getFillLinePosition(ILineDataSet dataSet, LineDataProvider dataProvider) { - // change the return value here to better understand the effect - // return 600; - return chart.getAxisLeft().getAxisMaximum(); - } - }); - - ArrayList dataSets = new ArrayList<>(); - dataSets.add(set1); // add the data sets - dataSets.add(set2); - - // create a data object with the data sets - LineData data = new LineData(dataSets); - data.setDrawValues(false); - - // set data - chart.setData(data); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.only_github, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/FilledLineActivity.java")); - startActivity(i); - break; - } - } - - return true; - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } -} diff --git a/app/src/main/java/info/appdev/chartexample/FilledLineActivity.kt b/app/src/main/java/info/appdev/chartexample/FilledLineActivity.kt new file mode 100644 index 0000000000..f32f1ac309 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/FilledLineActivity.kt @@ -0,0 +1,179 @@ +package info.appdev.chartexample + +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.formatter.IFillFormatter +import com.github.mikephil.charting.interfaces.dataprovider.LineDataProvider +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +/** + * This works by inverting the background and desired "fill" color. First, we draw the fill color + * that we want between the lines as the actual background of the chart. Then, we fill the area + * above the highest line and the area under the lowest line with the desired background color. + * This method makes it look like we filled the area between the lines, but really we are filling + * the area OUTSIDE the lines! + */ +class FilledLineActivity : DemoBase() { + private var chart: LineChart? = null + private val fillColor = Color.argb(150, 51, 181, 229) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_linechart_noseekbar) + + setTitle("FilledLineActivity") + + chart = findViewById(R.id.chart1) + chart!!.setBackgroundColor(Color.WHITE) + chart!!.setGridBackgroundColor(fillColor) + chart!!.drawGridBackground = true + + chart!!.isDrawBordersEnabled = true + + // no description text + chart!!.description.isEnabled = false + + // if disabled, scaling can be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + val l = chart!!.legend + l.isEnabled = false + + val xAxis = chart!!.xAxis + xAxis.isEnabled = false + + val leftAxis = chart!!.axisLeft + leftAxis.axisMaximum = 900f + leftAxis.axisMinimum = -250f + leftAxis.setDrawAxisLine(false) + leftAxis.setDrawZeroLine(false) + leftAxis.setDrawGridLines(false) + + chart!!.axisRight.isEnabled = false + + // add data + setData(60f) + + chart!!.invalidate() + } + + private fun setData(range: Float) { + val count = 100 + val values1 = ArrayList() + val sampleValues = getValues(count + 2) + + for (i in 0..() + + for (i in 0.. 0 + ) { + set1 = chart!!.data!!.getDataSetByIndex(0) as LineDataSet + set2 = chart!!.data!!.getDataSetByIndex(1) as LineDataSet + set1.entries = values1 + set2.entries = values2 + chart!!.data!!.notifyDataChanged() + chart!!.notifyDataSetChanged() + } else { + // create a dataset and give it a type + set1 = LineDataSet(values1, "DataSet 1") + + set1.axisDependency = AxisDependency.LEFT + set1.setColor(Color.rgb(255, 241, 46)) + set1.isDrawCirclesEnabled = false + set1.lineWidth = 2f + set1.circleRadius = 3f + set1.fillAlpha = 255 + set1.isDrawFilledEnabled = true + set1.fillColor = Color.WHITE + set1.highLightColor = Color.rgb(244, 117, 117) + set1.isDrawCircleHoleEnabled = false + set1.fillFormatter = object : IFillFormatter { + override fun getFillLinePosition(dataSet: ILineDataSet, dataProvider: LineDataProvider): Float { + // change the return value here to better understand the effect + // return 0; + return chart!!.axisLeft.axisMinimum + } + } + + // create a dataset and give it a type + set2 = LineDataSet(values2, "DataSet 2") + set2.axisDependency = AxisDependency.LEFT + set2.setColor(Color.rgb(255, 241, 46)) + set2.isDrawCirclesEnabled = false + set2.lineWidth = 2f + set2.circleRadius = 3f + set2.fillAlpha = 255 + set2.isDrawFilledEnabled = true + set2.fillColor = Color.WHITE + set2.isDrawCircleHoleEnabled = false + set2.highLightColor = Color.rgb(244, 117, 117) + set2.fillFormatter = object : IFillFormatter { + override fun getFillLinePosition(dataSet: ILineDataSet, dataProvider: LineDataProvider): Float { + // change the return value here to better understand the effect + // return 600; + return chart!!.axisLeft.axisMaximum + } + } + + val dataSets = ArrayList() + dataSets.add(set1) // add the data sets + dataSets.add(set2) + + // create a data object with the data sets + val data = LineData(dataSets) + data.setDrawValues(false) + + // set data + chart!!.setData(data) + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.only_github, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/FilledLineActivity.java".toUri()) + startActivity(i) + } + } + + return true + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } +} diff --git a/app/src/main/java/info/appdev/chartexample/HalfPieChartActivity.java b/app/src/main/java/info/appdev/chartexample/HalfPieChartActivity.java deleted file mode 100644 index 5c1c57b40a..0000000000 --- a/app/src/main/java/info/appdev/chartexample/HalfPieChartActivity.java +++ /dev/null @@ -1,170 +0,0 @@ - -package info.appdev.chartexample; - -import android.content.Intent; -import android.graphics.Color; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.text.style.RelativeSizeSpan; -import android.text.style.StyleSpan; -import android.util.DisplayMetrics; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.RelativeLayout; - -import com.github.mikephil.charting.animation.Easing; -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; -import com.github.mikephil.charting.formatter.PercentFormatter; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -@SuppressWarnings("SameParameterValue") -public class HalfPieChartActivity extends DemoBase { - - private PieChart chart; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_piechart_half); - - setTitle("HalfPieChartActivity"); - - chart = findViewById(R.id.chart1); - chart.setBackgroundColor(Color.WHITE); - - moveOffScreen(); - - chart.setUsePercentValues(true); - chart.getDescription().setEnabled(false); - - chart.setCenterTextTypeface(tfLight); - chart.setCenterText(generateCenterSpannableText()); - - chart.setDrawHoleEnabled(true); - chart.setHoleColor(Color.WHITE); - - chart.setTransparentCircleColor(Color.WHITE); - chart.setTransparentCircleAlpha(110); - - chart.setHoleRadius(58f); - chart.setTransparentCircleRadius(61f); - - chart.setDrawCenterText(true); - - chart.setRotationEnabled(false); - chart.setHighlightPerTapEnabled(true); - - chart.setMaxAngle(180f); // HALF CHART - chart.setRotationAngle(180f); - chart.setCenterTextOffset(0, -20); - - setData(100); - - chart.animateY(1400, Easing.EaseInOutQuad); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - l.setXEntrySpace(7f); - l.setYEntrySpace(0f); - l.setYOffset(0f); - - // entry label styling - chart.setEntryLabelColor(Color.WHITE); - chart.setEntryLabelTypeface(tfRegular); - chart.setEntryLabelTextSize(12f); - } - - private void setData(float range) { - int count = 4; - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(count); - - for (int i = 0; i < count; i++) { - values.add(new PieEntry((sampleValues[i].floatValue() * range) + range / 5, parties[i % parties.length])); - } - - PieDataSet dataSet = new PieDataSet(values, "Election Results"); - dataSet.setSliceSpace(3f); - dataSet.setSelectionShift(5f); - - dataSet.setColors(ColorTemplate.MATERIAL_COLORS); - //dataSet.setSelectionShift(0f); - - PieData data = new PieData(dataSet); - data.setValueFormatter(new PercentFormatter()); - data.setValueTextSize(11f); - data.setValueTextColor(Color.WHITE); - data.setValueTypeface(tfLight); - chart.setData(data); - - chart.invalidate(); - } - - private SpannableString generateCenterSpannableText() { - - SpannableString s = new SpannableString("MPAndroidChart\ndeveloped by Philipp Jahoda"); - s.setSpan(new RelativeSizeSpan(1.7f), 0, 14, 0); - s.setSpan(new StyleSpan(Typeface.NORMAL), 14, s.length() - 15, 0); - s.setSpan(new ForegroundColorSpan(Color.GRAY), 14, s.length() - 15, 0); - s.setSpan(new RelativeSizeSpan(.8f), 14, s.length() - 15, 0); - s.setSpan(new StyleSpan(Typeface.ITALIC), s.length() - 14, s.length(), 0); - s.setSpan(new ForegroundColorSpan(ColorTemplate.getHoloBlue()), s.length() - 14, s.length(), 0); - return s; - } - - private void moveOffScreen() { - - DisplayMetrics displayMetrics = new DisplayMetrics(); - getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); - - int height = displayMetrics.heightPixels; - - int offset = (int)(height * 0.65); /* percent to move */ - - RelativeLayout.LayoutParams rlParams = - (RelativeLayout.LayoutParams) chart.getLayoutParams(); - rlParams.setMargins(0, 0, 0, -offset); - chart.setLayoutParams(rlParams); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.only_github, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/HalfPieChartActivity.java")); - startActivity(i); - break; - } - } - - return true; - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } -} diff --git a/app/src/main/java/info/appdev/chartexample/HalfPieChartActivity.kt b/app/src/main/java/info/appdev/chartexample/HalfPieChartActivity.kt new file mode 100644 index 0000000000..38c1a55515 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/HalfPieChartActivity.kt @@ -0,0 +1,161 @@ +package info.appdev.chartexample + +import android.content.Intent +import android.graphics.Color +import android.graphics.Typeface +import android.net.Uri +import android.os.Bundle +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import android.text.style.StyleSpan +import android.util.DisplayMetrics +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.RelativeLayout +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.formatter.PercentFormatter +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.ColorTemplate.holoBlue +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase +import androidx.core.net.toUri + +class HalfPieChartActivity : DemoBase() { + private var chart: PieChart? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_piechart_half) + + setTitle("HalfPieChartActivity") + + chart = findViewById(R.id.chart1) + chart!!.setBackgroundColor(Color.WHITE) + + moveOffScreen() + + chart!!.setUsePercentValues(true) + chart!!.description.isEnabled = false + + chart!!.setCenterTextTypeface(tfLight) + chart!!.centerText = generateCenterSpannableText() + + chart!!.isDrawHoleEnabled = true + chart!!.setHoleColor(Color.WHITE) + + chart!!.setTransparentCircleColor(Color.WHITE) + chart!!.setTransparentCircleAlpha(110) + + chart!!.holeRadius = 58f + chart!!.transparentCircleRadius = 61f + + chart!!.setDrawCenterText(true) + + chart!!.isRotationEnabled = false + chart!!.isHighlightPerTapEnabled = true + + chart!!.maxAngle = 180f // HALF CHART + chart!!.rotationAngle = 180f + chart!!.setCenterTextOffset(0f, -20f) + + setData(100f) + + chart!!.animateY(1400, Easing.EaseInOutQuad) + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + l.xEntrySpace = 7f + l.yEntrySpace = 0f + l.yOffset = 0f + + // entry label styling + chart!!.setEntryLabelColor(Color.WHITE) + chart!!.setEntryLabelTypeface(tfRegular) + chart!!.setEntryLabelTextSize(12f) + } + + private fun setData(range: Float) { + val count = 4 + val values = ArrayList() + val sampleValues = getValues(count) + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/HalfPieChartActivity.java".toUri()) + startActivity(i) + } + } + + return true + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } +} diff --git a/app/src/main/java/info/appdev/chartexample/HorizontalBarChartActivity.java b/app/src/main/java/info/appdev/chartexample/HorizontalBarChartActivity.java deleted file mode 100644 index 97bdcf0af0..0000000000 --- a/app/src/main/java/info/appdev/chartexample/HorizontalBarChartActivity.java +++ /dev/null @@ -1,289 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.HorizontalBarChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.MPPointF; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -public class HorizontalBarChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private HorizontalBarChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_horizontalbarchart); - - setTitle("HorizontalBarChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarY = findViewById(R.id.seekBarY); - - seekBarY.setOnSeekBarChangeListener(this); - seekBarX.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - // chart.setHighlightEnabled(false); - - chart.setDrawBarShadow(false); - - chart.setDrawValueAboveBar(true); - - chart.getDescription().setEnabled(false); - - // if more than 60 entries are displayed in the chart, no values will be - // drawn - chart.setMaxVisibleValueCount(60); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - // draw shadows for each bar that show the maximum value - // chart.setDrawBarShadow(true); - - chart.setDrawGridBackground(false); - - XAxis xl = chart.getXAxis(); - xl.setPosition(XAxisPosition.BOTTOM); - xl.setTypeface(tfLight); - xl.setDrawAxisLine(true); - xl.setDrawGridLines(false); - xl.setGranularity(10f); - - YAxis yl = chart.getAxisLeft(); - yl.setTypeface(tfLight); - yl.setDrawAxisLine(true); - yl.setDrawGridLines(true); - yl.setAxisMinimum(0f); // this replaces setStartAtZero(true) -// yl.setInverted(true); - - YAxis yr = chart.getAxisRight(); - yr.setTypeface(tfLight); - yr.setDrawAxisLine(true); - yr.setDrawGridLines(false); - yr.setAxisMinimum(0f); // this replaces setStartAtZero(true) -// yr.setInverted(true); - - chart.setFitBars(true); - chart.animateY(2500); - - // setting data - seekBarY.setProgress(50); - seekBarX.setProgress(12); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - l.setFormSize(8f); - l.setXEntrySpace(4f); - } - - private void setData(int count, float range) { - float barWidth = 9f; - float spaceForBar = 10f; - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100); - - for (int i = 0; i < count; i++) { - float val = sampleValues[i].floatValue() * range; - values.add(new BarEntry(i * spaceForBar, val, - getResources().getDrawable(R.drawable.star))); - } - - BarDataSet set1; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set1 = (BarDataSet) chart.getData().getDataSetByIndex(0); - set1.setEntries(values); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - } else { - set1 = new BarDataSet(values, "DataSet 1"); - - set1.setDrawIcons(false); - - ArrayList dataSets = new ArrayList<>(); - dataSets.add(set1); - - BarData data = new BarData(dataSets); - data.setValueTextSize(10f); - data.setValueTypeface(tfLight); - data.setBarWidth(barWidth); - chart.setData(data); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bar, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/HorizontalBarChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData() - .getDataSets(); - - for (IBarDataSet iSet : sets) { - iSet.setDrawValues(!iSet.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - List sets = chart.getData() - .getDataSets(); - - for (IBarDataSet iSet : sets) { - iSet.setDrawIcons(!iSet.isDrawIconsEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if(chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - chart.setPinchZoom(!chart.isPinchZoomEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleBarBorders: { - for (IBarDataSet set : chart.getData().getDataSets()) - ((BarDataSet)set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); - - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - setData(seekBarX.getProgress(), seekBarY.getProgress()); - chart.setFitBars(true); - chart.invalidate(); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "HorizontalBarChartActivity"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} - - private final RectF mOnValueSelectedRectF = new RectF(); - - @Override - public void onValueSelected(Entry e, Highlight h) { - - if (e == null) - return; - - RectF bounds = mOnValueSelectedRectF; - chart.getBarBounds((BarEntry) e, bounds); - - MPPointF position = chart.getPosition(e, chart.getData().getDataSetByIndex(h.getDataSetIndex()) - .getAxisDependency()); - - Log.i("bounds", bounds.toString()); - Log.i("position", position.toString()); - - MPPointF.recycleInstance(position); - } - - @Override - public void onNothingSelected() {} -} diff --git a/app/src/main/java/info/appdev/chartexample/HorizontalBarChartActivity.kt b/app/src/main/java/info/appdev/chartexample/HorizontalBarChartActivity.kt new file mode 100644 index 0000000000..53c6395572 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/HorizontalBarChartActivity.kt @@ -0,0 +1,273 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.RectF +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.HorizontalBarChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.MPPointF.Companion.recycleInstance +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: HorizontalBarChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_horizontalbarchart) + + setTitle("HorizontalBarChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarY = findViewById(R.id.seekBarY) + + seekBarY!!.setOnSeekBarChangeListener(this) + seekBarX!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + + // chart.setHighlightEnabled(false); + chart!!.isDrawBarShadowEnabled = false + + chart!!.isDrawValueAboveBarEnabled = true + + chart!!.description.isEnabled = false + + // if more than 60 entries are displayed in the chart, no values will be + // drawn + chart!!.setMaxVisibleValueCount(60) + + // scaling can now only be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + // draw shadows for each bar that show the maximum value + // chart.setDrawBarShadow(true); + chart!!.drawGridBackground = false + + val xl = chart!!.xAxis + xl.position = XAxisPosition.BOTTOM + xl.typeface = tfLight + xl.setDrawAxisLine(true) + xl.setDrawGridLines(false) + xl.granularity = 10f + + val yl = chart!!.axisLeft + yl.typeface = tfLight + yl.setDrawAxisLine(true) + yl.setDrawGridLines(true) + yl.axisMinimum = 0f // this replaces setStartAtZero(true) + + // yl.setInverted(true); + val yr = chart!!.axisRight + yr.typeface = tfLight + yr.setDrawAxisLine(true) + yr.setDrawGridLines(false) + yr.axisMinimum = 0f // this replaces setStartAtZero(true) + + // yr.setInverted(true); + chart!!.setFitBars(true) + chart!!.animateY(2500) + + // setting data + seekBarY!!.progress = 50 + seekBarX!!.progress = 12 + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + l.horizontalAlignment = Legend.LegendHorizontalAlignment.LEFT + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + l.formSize = 8f + l.xEntrySpace = 4f + } + + private fun setData(count: Int, range: Float) { + val barWidth = 9f + val spaceForBar = 10f + val values = ArrayList() + val sampleValues = getValues(100) + + for (i in 0.. 0 + ) { + set1 = chart!!.data!!.getDataSetByIndex(0) as BarDataSet + set1.entries = values + chart!!.data!!.notifyDataChanged() + chart!!.notifyDataSetChanged() + } else { + set1 = BarDataSet(values, "DataSet 1") + + set1.isDrawIconsEnabled = false + + val dataSets = ArrayList() + dataSets.add(set1) + + val data = BarData(dataSets) + data.setValueTextSize(10f) + data.setValueTypeface(tfLight) + data.barWidth = barWidth + chart!!.setData(data) + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.bar, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/HorizontalBarChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets = chart!!.data!! + .dataSets + + for (iSet in sets) { + iSet.isDrawValuesEnabled = !iSet.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleIcons -> { + val sets = chart!!.data!! + .dataSets + + for (iSet in sets) { + iSet.isDrawIconsEnabled = !iSet.isDrawIconsEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionTogglePinch -> { + chart!!.setPinchZoom(!chart!!.isPinchZoomEnabled) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionToggleBarBorders -> { + for (set in chart!!.data!!.dataSets) (set as BarDataSet).setBarBorderWidth(if (set.barBorderWidth == 1f) 0f else 1f) + + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + setData(seekBarX!!.progress, seekBarY!!.progress.toFloat()) + chart!!.setFitBars(true) + chart!!.invalidate() + } + + override fun saveToGallery() { + saveToGallery(chart, "HorizontalBarChartActivity") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + + private val mOnValueSelectedRectF = RectF() + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null || h == null) return + + val bounds = mOnValueSelectedRectF + chart!!.getBarBounds(e as BarEntry, bounds) + + val position = chart!!.getPosition(e, chart!!.data!!.getDataSetByIndex(h.dataSetIndex).axisDependency) + + Log.i("bounds", bounds.toString()) + Log.i("position", position.toString()) + + recycleInstance(position) + } + + override fun onNothingSelected() {} +} diff --git a/app/src/main/java/info/appdev/chartexample/HorizontalBarNegativeChartActivity.java b/app/src/main/java/info/appdev/chartexample/HorizontalBarNegativeChartActivity.java deleted file mode 100644 index d64f758a3f..0000000000 --- a/app/src/main/java/info/appdev/chartexample/HorizontalBarNegativeChartActivity.java +++ /dev/null @@ -1,289 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.RectF; -import android.net.Uri; -import android.os.Bundle; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import androidx.core.content.ContextCompat; - -import com.github.mikephil.charting.charts.HorizontalBarChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.MPPointF; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -public class HorizontalBarNegativeChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private HorizontalBarChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_horizontalbarchart); - - setTitle("HorizontalBarChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarY = findViewById(R.id.seekBarY); - - seekBarY.setOnSeekBarChangeListener(this); - seekBarX.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - // chart.setHighlightEnabled(false); - - chart.setDrawBarShadow(false); - - chart.setDrawValueAboveBar(true); - - chart.getDescription().setEnabled(false); - - // if more than 60 entries are displayed in the chart, no values will be - // drawn - chart.setMaxVisibleValueCount(60); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - // draw shadows for each bar that show the maximum value - // chart.setDrawBarShadow(true); - - chart.setDrawGridBackground(false); - - XAxis xl = chart.getXAxis(); - xl.setPosition(XAxisPosition.BOTTOM); - xl.setTypeface(tfLight); - xl.setDrawAxisLine(true); - xl.setDrawGridLines(false); - xl.setGranularity(10f); - - YAxis yl = chart.getAxisLeft(); - yl.setTypeface(tfLight); - yl.setDrawAxisLine(true); - yl.setDrawGridLines(true); -// yl.setInverted(true); - - YAxis yr = chart.getAxisRight(); - yr.setTypeface(tfLight); - yr.setDrawAxisLine(true); - yr.setDrawGridLines(false); -// yr.setInverted(true); - - chart.setFitBars(true); - chart.animateY(2500); - - // setting data - seekBarY.setProgress(50); - seekBarX.setProgress(12); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - l.setFormSize(8f); - l.setXEntrySpace(4f); - } - - private void setData(int count, float range) { - - float barWidth = 9f; - float spaceForBar = 10f; - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(count + 2); - - for (int i = 0; i < count; i++) { - float val = sampleValues[i].floatValue() * range - range / 2; - values.add(new BarEntry(i * spaceForBar, val, - getResources().getDrawable(R.drawable.star))); - } - - BarDataSet set1; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set1 = (BarDataSet) chart.getData().getDataSetByIndex(0); - set1.setEntries(values); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - } else { - set1 = new BarDataSet(values, "DataSet 1"); - - set1.setDrawIcons(false); - - ArrayList dataSets = new ArrayList<>(); - dataSets.add(set1); - - BarData data = new BarData(dataSets); - data.setValueTextSize(10f); - data.setValueTypeface(tfLight); - data.setBarWidth(barWidth); - chart.setData(data); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bar, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/HorizontalBarChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData() - .getDataSets(); - - for (IBarDataSet iSet : sets) { - iSet.setDrawValues(!iSet.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - List sets = chart.getData() - .getDataSets(); - - for (IBarDataSet iSet : sets) { - iSet.setDrawIcons(!iSet.isDrawIconsEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if(chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - chart.setPinchZoom(!chart.isPinchZoomEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleBarBorders: { - for (IBarDataSet set : chart.getData().getDataSets()) - ((BarDataSet)set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); - - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - setData(seekBarX.getProgress(), seekBarY.getProgress()); - chart.setFitBars(true); - chart.invalidate(); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "HorizontalBarChartActivity"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} - - private final RectF mOnValueSelectedRectF = new RectF(); - - @Override - public void onValueSelected(Entry e, Highlight h) { - - if (e == null) - return; - - RectF bounds = mOnValueSelectedRectF; - chart.getBarBounds((BarEntry) e, bounds); - - MPPointF position = chart.getPosition(e, chart.getData().getDataSetByIndex(h.getDataSetIndex()) - .getAxisDependency()); - - Log.i("bounds", bounds.toString()); - Log.i("position", position.toString()); - - MPPointF.recycleInstance(position); - } - - @Override - public void onNothingSelected() {} -} diff --git a/app/src/main/java/info/appdev/chartexample/HorizontalBarNegativeChartActivity.kt b/app/src/main/java/info/appdev/chartexample/HorizontalBarNegativeChartActivity.kt new file mode 100644 index 0000000000..a9112050b3 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/HorizontalBarNegativeChartActivity.kt @@ -0,0 +1,271 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.RectF +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.HorizontalBarChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.MPPointF.Companion.recycleInstance +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class HorizontalBarNegativeChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: HorizontalBarChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_horizontalbarchart) + + setTitle("HorizontalBarChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarY = findViewById(R.id.seekBarY) + + seekBarY!!.setOnSeekBarChangeListener(this) + seekBarX!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + + // chart.setHighlightEnabled(false); + chart!!.isDrawBarShadowEnabled = false + + chart!!.isDrawValueAboveBarEnabled = true + + chart!!.description.isEnabled = false + + // if more than 60 entries are displayed in the chart, no values will be + // drawn + chart!!.setMaxVisibleValueCount(60) + + // scaling can now only be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + // draw shadows for each bar that show the maximum value + // chart.setDrawBarShadow(true); + chart!!.drawGridBackground = false + + val xl = chart!!.xAxis + xl.position = XAxisPosition.BOTTOM + xl.typeface = tfLight + xl.setDrawAxisLine(true) + xl.setDrawGridLines(false) + xl.granularity = 10f + + val yl = chart!!.axisLeft + yl.typeface = tfLight + yl.setDrawAxisLine(true) + yl.setDrawGridLines(true) + + // yl.setInverted(true); + val yr = chart!!.axisRight + yr.typeface = tfLight + yr.setDrawAxisLine(true) + yr.setDrawGridLines(false) + + // yr.setInverted(true); + chart!!.setFitBars(true) + chart!!.animateY(2500) + + // setting data + seekBarY!!.progress = 50 + seekBarX!!.progress = 12 + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + l.horizontalAlignment = Legend.LegendHorizontalAlignment.LEFT + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + l.formSize = 8f + l.xEntrySpace = 4f + } + + private fun setData(count: Int, range: Float) { + val barWidth = 9f + val spaceForBar = 10f + val values = ArrayList() + val sampleValues = getValues(count + 2) + + for (i in 0.. 0 + ) { + set1 = chart!!.data!!.getDataSetByIndex(0) as BarDataSet + set1.entries = values + chart!!.data!!.notifyDataChanged() + chart!!.notifyDataSetChanged() + } else { + set1 = BarDataSet(values, "DataSet 1") + + set1.isDrawIconsEnabled = false + + val dataSets = ArrayList() + dataSets.add(set1) + + val data = BarData(dataSets) + data.setValueTextSize(10f) + data.setValueTypeface(tfLight) + data.barWidth = barWidth + chart!!.setData(data) + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.bar, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/HorizontalBarChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets = chart!!.data!! + .dataSets + + for (iSet in sets) { + iSet.isDrawValuesEnabled = !iSet.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleIcons -> { + val sets = chart!!.data!! + .dataSets + + for (iSet in sets) { + iSet.isDrawIconsEnabled = !iSet.isDrawIconsEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionTogglePinch -> { + chart!!.setPinchZoom(!chart!!.isPinchZoomEnabled) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionToggleBarBorders -> { + for (set in chart!!.data!!.dataSets) (set as BarDataSet).setBarBorderWidth(if (set.barBorderWidth == 1f) 0f else 1f) + + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + setData(seekBarX!!.progress, seekBarY!!.progress.toFloat()) + chart!!.setFitBars(true) + chart!!.invalidate() + } + + override fun saveToGallery() { + saveToGallery(chart, "HorizontalBarChartActivity") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + + private val mOnValueSelectedRectF = RectF() + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null || h == null) return + + val bounds = mOnValueSelectedRectF + chart!!.getBarBounds(e as BarEntry, bounds) + + val position = chart!!.getPosition(e, chart!!.data!!.getDataSetByIndex(h.dataSetIndex).axisDependency) + + Log.i("bounds", bounds.toString()) + Log.i("position", position.toString()) + + recycleInstance(position) + } + + override fun onNothingSelected() {} +} diff --git a/app/src/main/java/info/appdev/chartexample/InvertedLineChartActivity.java b/app/src/main/java/info/appdev/chartexample/InvertedLineChartActivity.java deleted file mode 100644 index 1494083f35..0000000000 --- a/app/src/main/java/info/appdev/chartexample/InvertedLineChartActivity.java +++ /dev/null @@ -1,276 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.Legend.LegendForm; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.EntryXComparator; - -import info.appdev.chartexample.custom.MyMarkerView; -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -public class InvertedLineChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private LineChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_linechart); - - setTitle("InvertedLineChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarY = findViewById(R.id.seekBarY); - - seekBarY.setOnSeekBarChangeListener(this); - seekBarX.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - chart.setDrawGridBackground(false); - - // no description text - chart.getDescription().setEnabled(false); - - // enable touch gestures - chart.setTouchEnabled(true); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - - // if disabled, scaling can be done on x- and y-axis separately - chart.setPinchZoom(true); - - // set an alternative background color - // chart.setBackgroundColor(Color.GRAY); - - // create a custom MarkerView (extend MarkerView) and specify the layout - // to use for it - MyMarkerView mv = new MyMarkerView(this, R.layout.custom_marker_view); - mv.setChartView(chart); // For bounds control - chart.setMarker(mv); // Set the marker to the chart - - XAxis xl = chart.getXAxis(); - xl.setAvoidFirstLastClipping(true); - xl.setAxisMinimum(0f); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setInverted(true); - leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setEnabled(false); - - // add data - seekBarX.setProgress(25); - seekBarY.setProgress(50); - - // // restrain the maximum scale-out factor - // chart.setScaleMinima(3f, 3f); - // - // // center the view to a specific position inside the chart - // chart.centerViewPort(10, 50); - - // get the legend (only possible after setting data) - Legend l = chart.getLegend(); - - // modify the legend ... - l.setForm(LegendForm.LINE); - - // don't forget to refresh the drawing - chart.invalidate(); - } - - private void setData(int count, float range) { - - ArrayList entries = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(count + 2); - - for (int i = 0; i < count; i++) { - float xVal = sampleValues[i].floatValue() * range; - float yVal = sampleValues[i + 1].floatValue() * range; - entries.add(new Entry(xVal, yVal)); - } - - // sort by x-value - Collections.sort(entries, new EntryXComparator()); - - // create a dataset and give it a type - LineDataSet set1 = new LineDataSet(entries, "DataSet 1"); - - set1.setLineWidth(1.5f); - set1.setCircleRadius(4f); - - // create a data object with the data sets - LineData data = new LineData(set1); - - // set data - chart.setData(data); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.line, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/InvertedLineChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if(chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionToggleFilled: { - - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setDrawFilled(!set.isDrawFilledEnabled()); - } - chart.invalidate(); - break; - } - case R.id.actionToggleCircles: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setDrawCircles(!set.isDrawCirclesEnabled()); - } - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - - chart.animateXY(2000, 2000); - break; - } - case R.id.actionTogglePinch: { - chart.setPinchZoom(!chart.isPinchZoomEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - setData(seekBarX.getProgress(), seekBarY.getProgress()); - - // redraw - chart.invalidate(); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "InvertedLineChartActivity"); - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - Log.i("VAL SELECTED", - "Value: " + e.getY() + ", xIndex: " + e.getX() - + ", DataSet index: " + h.getDataSetIndex()); - } - - @Override - public void onNothingSelected() {} - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/InvertedLineChartActivity.kt b/app/src/main/java/info/appdev/chartexample/InvertedLineChartActivity.kt new file mode 100644 index 0000000000..8dbe246fa0 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/InvertedLineChartActivity.kt @@ -0,0 +1,254 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.Legend.LegendForm +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.EntryXComparator +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.custom.MyMarkerView +import info.appdev.chartexample.notimportant.DemoBase +import java.util.Collections + +class InvertedLineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: LineChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_linechart) + + setTitle("InvertedLineChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarY = findViewById(R.id.seekBarY) + + seekBarY!!.setOnSeekBarChangeListener(this) + seekBarX!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + chart!!.drawGridBackground = false + + // no description text + chart!!.description.isEnabled = false + + // enable touch gestures + chart!!.setTouchEnabled(true) + + // enable scaling and dragging + chart!!.isDragEnabled = true + chart!!.setScaleEnabled(true) + + // if disabled, scaling can be done on x- and y-axis separately + chart!!.setPinchZoom(true) + + // set an alternative background color + // chart.setBackgroundColor(Color.GRAY); + + // create a custom MarkerView (extend MarkerView) and specify the layout + // to use for it + val mv = MyMarkerView(this, R.layout.custom_marker_view) + mv.chartView = chart // For bounds control + chart!!.setMarker(mv) // Set the marker to the chart + + val xl = chart!!.xAxis + xl.setAvoidFirstLastClipping(true) + xl.axisMinimum = 0f + + val leftAxis = chart!!.axisLeft + leftAxis.isInverted = true + leftAxis.axisMinimum = 0f // this replaces setStartAtZero(true) + + val rightAxis = chart!!.axisRight + rightAxis.isEnabled = false + + // add data + seekBarX!!.progress = 25 + seekBarY!!.progress = 50 + + // // restrain the maximum scale-out factor + // chart.setScaleMinima(3f, 3f); + // + // // center the view to a specific position inside the chart + // chart.centerViewPort(10, 50); + + // get the legend (only possible after setting data) + val l = chart!!.legend + + // modify the legend ... + l.form = LegendForm.LINE + + // don't forget to refresh the drawing + chart!!.invalidate() + } + + private fun setData(count: Int, range: Float) { + val entries = ArrayList() + val sampleValues = getValues(count + 2) + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/InvertedLineChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionToggleFilled -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawFilledEnabled = !set.isDrawFilledEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleCircles -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawCirclesEnabled = !set.isDrawCirclesEnabled + } + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + + R.id.actionTogglePinch -> { + chart!!.setPinchZoom(!chart!!.isPinchZoomEnabled) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + setData(seekBarX!!.progress, seekBarY!!.progress.toFloat()) + + // redraw + chart!!.invalidate() + } + + override fun saveToGallery() { + saveToGallery(chart, "InvertedLineChartActivity") + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + Log.i( + "VAL SELECTED", + ("Value: " + e?.y + ", xIndex: " + e?.x + + ", DataSet index: " + h?.dataSetIndex) + ) + } + + override fun onNothingSelected() {} + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/LineChartActivity.kt b/app/src/main/java/info/appdev/chartexample/LineChartActivity.kt index dc49833f77..de1c835ffd 100644 --- a/app/src/main/java/info/appdev/chartexample/LineChartActivity.kt +++ b/app/src/main/java/info/appdev/chartexample/LineChartActivity.kt @@ -4,7 +4,6 @@ import android.Manifest import android.content.Intent import android.content.pm.PackageManager import android.graphics.Color -import android.net.Uri import android.os.Bundle import android.util.Log import android.view.Menu @@ -13,6 +12,7 @@ import android.view.WindowManager import android.widget.SeekBar import android.widget.SeekBar.OnSeekBarChangeListener import androidx.core.content.ContextCompat +import androidx.core.net.toUri import com.github.mikephil.charting.animation.Easing import com.github.mikephil.charting.components.Legend.LegendForm import com.github.mikephil.charting.components.LimitLine @@ -55,7 +55,7 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec // set listeners binding.chart1.setOnChartValueSelectedListener(this) - binding.chart1.setDrawGridBackground(false) + binding.chart1.drawGridBackground = false // create marker to display box when values are selected val mv = MyMarkerView(this, R.layout.custom_marker_view) @@ -159,14 +159,14 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec R.id.viewGithub -> { val i = Intent(Intent.ACTION_VIEW) i.data = - Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/LineChartActivity1.java") + "https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/LineChartActivity1.java".toUri() startActivity(i) } R.id.actionToggleValues -> { binding.chart1.data?.dataSets?.forEach { val set = it as LineDataSet - set.setDrawValues(!set.isDrawValuesEnabled) + set.isDrawValuesEnabled = (!set.isDrawValuesEnabled) } binding.chart1.invalidate() } @@ -174,7 +174,7 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec R.id.actionToggleIcons -> { binding.chart1.data?.dataSets?.forEach { val set = it as LineDataSet - set.setDrawIcons(!set.isDrawIconsEnabled) + set.isDrawIconsEnabled = (!set.isDrawIconsEnabled) binding.chart1.invalidate() } } @@ -189,7 +189,7 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec R.id.actionToggleFilled -> { binding.chart1.data?.dataSets?.forEach { val set = it as LineDataSet - set.setDrawFilled(!set.isDrawFilledEnabled) + set.isDrawFilledEnabled = !set.isDrawFilledEnabled binding.chart1.invalidate() } } @@ -197,7 +197,7 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec R.id.actionToggleCircles -> { binding.chart1.data?.dataSets?.forEach { val set = it as LineDataSet - set.setDrawCircles(!set.isDrawCirclesEnabled) + set.isDrawCirclesEnabled = !set.isDrawCirclesEnabled } binding.chart1.invalidate() } @@ -265,7 +265,7 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec override fun onStartTrackingTouch(seekBar: SeekBar) {} override fun onStopTrackingTouch(seekBar: SeekBar) {} - override fun onValueSelected(e: Entry, h: Highlight) { + override fun onValueSelected(e: Entry?, h: Highlight?) { Log.i("Entry selected", e.toString()) Log.i("LOW HIGH", "low: " + binding.chart1.lowestVisibleX + ", high: " + binding.chart1.highestVisibleX) Log.i( diff --git a/app/src/main/java/info/appdev/chartexample/LineChartActivityColored.java b/app/src/main/java/info/appdev/chartexample/LineChartActivityColored.java deleted file mode 100644 index c8b1980e92..0000000000 --- a/app/src/main/java/info/appdev/chartexample/LineChartActivityColored.java +++ /dev/null @@ -1,157 +0,0 @@ - -package info.appdev.chartexample; - -import android.content.Intent; -import android.graphics.Color; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -@SuppressWarnings("SameParameterValue") -public class LineChartActivityColored extends DemoBase { - - private final LineChart[] charts = new LineChart[4]; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_colored_lines); - - setTitle("LineChartActivityColored"); - - charts[0] = findViewById(R.id.chart1); - charts[1] = findViewById(R.id.chart2); - charts[2] = findViewById(R.id.chart3); - charts[3] = findViewById(R.id.chart4); - - Typeface mTf = Typeface.createFromAsset(getAssets(), "OpenSans-Bold.ttf"); - - for (int i = 0; i < charts.length; i++) { - - LineData data = getData(100); - data.setValueTypeface(mTf); - - // add some transparency to the color with "& 0x90FFFFFF" - setupChart(charts[i], data, colors[i % colors.length]); - } - } - - private final int[] colors = new int[] { - Color.rgb(137, 230, 81), - Color.rgb(240, 240, 30), - Color.rgb(89, 199, 250), - Color.rgb(250, 104, 104) - }; - - private void setupChart(LineChart chart, LineData data, int color) { - - ((LineDataSet) data.getDataSetByIndex(0)).setCircleHoleColor(color); - - // no description text - chart.getDescription().setEnabled(false); - - // chart.setDrawHorizontalGrid(false); - // - // enable / disable grid background - chart.setDrawGridBackground(false); -// chart.getRenderer().getGridPaint().setGridColor(Color.WHITE & 0x70FFFFFF); - - // enable touch gestures - chart.setTouchEnabled(true); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - - // if disabled, scaling can be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setBackgroundColor(color); - - // set custom chart offsets (automatic offset calculation is hereby disabled) - chart.setViewPortOffsets(10, 0, 10, 0); - - // add data - chart.setData(data); - - // get the legend (only possible after setting data) - Legend l = chart.getLegend(); - l.setEnabled(false); - - chart.getAxisLeft().setEnabled(false); - chart.getAxisLeft().setSpaceTop(40); - chart.getAxisLeft().setSpaceBottom(40); - chart.getAxisRight().setEnabled(false); - - chart.getXAxis().setEnabled(false); - - // animate calls invalidate()... - chart.animateX(2500); - } - - private LineData getData(float range) { - int count = 36; - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(count); - - for (int i = 0; i < count; i++) { - float val = (sampleValues[i].floatValue() * range) + 3; - values.add(new Entry(i, val)); - } - - // create a dataset and give it a type - LineDataSet set1 = new LineDataSet(values, "DataSet 1"); - // set1.setFillAlpha(110); - // set1.setFillColor(Color.RED); - - set1.setLineWidth(1.75f); - set1.setCircleRadius(5f); - set1.setCircleHoleRadius(2.5f); - set1.setColor(Color.WHITE); - set1.setCircleColor(Color.WHITE); - set1.setHighLightColor(Color.WHITE); - set1.setDrawValues(false); - - // create a data object with the data sets - return new LineData(set1); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.only_github, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/LineChartActivityColored.java")); - startActivity(i); - break; - } - } - - return true; - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } -} diff --git a/app/src/main/java/info/appdev/chartexample/LineChartActivityColored.kt b/app/src/main/java/info/appdev/chartexample/LineChartActivityColored.kt new file mode 100644 index 0000000000..cf8d2997be --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/LineChartActivityColored.kt @@ -0,0 +1,146 @@ +package info.appdev.chartexample + +import android.content.Intent +import android.graphics.Color +import android.graphics.Typeface +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class LineChartActivityColored : DemoBase() { + private val charts = arrayOfNulls(4) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_colored_lines) + + setTitle("LineChartActivityColored") + + charts[0] = findViewById(R.id.chart1) + charts[1] = findViewById(R.id.chart2) + charts[2] = findViewById(R.id.chart3) + charts[3] = findViewById(R.id.chart4) + + val mTf = Typeface.createFromAsset(assets, "OpenSans-Bold.ttf") + + for (i in charts.indices) { + val data = getData(100f) + data.setValueTypeface(mTf) + + // add some transparency to the color with "& 0x90FFFFFF" + setupChart(charts[i]!!, data, colors[i % colors.size]) + } + } + + private val colors = intArrayOf( + Color.rgb(137, 230, 81), + Color.rgb(240, 240, 30), + Color.rgb(89, 199, 250), + Color.rgb(250, 104, 104) + ) + + private fun setupChart(chart: LineChart, data: LineData, color: Int) { + (data.getDataSetByIndex(0) as LineDataSet).circleHoleColor = color + + // no description text + chart.description.isEnabled = false + + // chart.setDrawHorizontalGrid(false); + // + // enable / disable grid background + chart.drawGridBackground = false + + // chart.getRenderer().getGridPaint().setGridColor(Color.WHITE & 0x70FFFFFF); + + // enable touch gestures + chart.setTouchEnabled(true) + + // enable scaling and dragging + chart.isDragEnabled = true + chart.setScaleEnabled(true) + + // if disabled, scaling can be done on x- and y-axis separately + chart.setPinchZoom(false) + + chart.setBackgroundColor(color) + + // set custom chart offsets (automatic offset calculation is hereby disabled) + chart.setViewPortOffsets(10f, 0f, 10f, 0f) + + // add data + chart.setData(data) + + // get the legend (only possible after setting data) + val l = chart.legend + l.isEnabled = false + + chart.axisLeft.isEnabled = false + chart.axisLeft.spaceTop = 40f + chart.axisLeft.spaceBottom = 40f + chart.axisRight.isEnabled = false + + chart.xAxis.isEnabled = false + + // animate calls invalidate()... + chart.animateX(2500) + } + + private fun getData(range: Float): LineData { + val count = 36 + val values = ArrayList() + val sampleValues = getValues(count) + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/LineChartActivityColored.java".toUri()) + startActivity(i) + } + } + + return true + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } +} diff --git a/app/src/main/java/info/appdev/chartexample/LineChartDualAxisActivity.java b/app/src/main/java/info/appdev/chartexample/LineChartDualAxisActivity.java deleted file mode 100644 index e23552c0c7..0000000000 --- a/app/src/main/java/info/appdev/chartexample/LineChartDualAxisActivity.java +++ /dev/null @@ -1,402 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.Legend.LegendForm; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -/** - * Example of a dual axis {@link LineChart} with multiple data sets. - * - * @since 1.7.4 - * @version 3.1.0 - */ -public class LineChartDualAxisActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private LineChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_linechart); - - setTitle("LineChart DualAxis"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setOnSeekBarChangeListener(this); - - seekBarY = findViewById(R.id.seekBarY); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - - // no description text - chart.getDescription().setEnabled(false); - - // enable touch gestures - chart.setTouchEnabled(true); - - chart.setDragDecelerationFrictionCoef(0.9f); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - chart.setDrawGridBackground(false); - chart.setHighlightPerDragEnabled(true); - - // if disabled, scaling can be done on x- and y-axis separately - chart.setPinchZoom(true); - - // set an alternative background color - chart.setBackgroundColor(Color.LTGRAY); - - // add data - seekBarX.setProgress(20); - seekBarY.setProgress(30); - - chart.animateX(1500); - - // get the legend (only possible after setting data) - Legend l = chart.getLegend(); - - // modify the legend ... - l.setForm(LegendForm.LINE); - l.setTypeface(tfLight); - l.setTextSize(11f); - l.setTextColor(Color.WHITE); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.LEFT); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); -// l.setYOffset(11f); - - XAxis xAxis = chart.getXAxis(); - xAxis.setTypeface(tfLight); - xAxis.setTextSize(11f); - xAxis.setTextColor(Color.WHITE); - xAxis.setDrawGridLines(false); - xAxis.setDrawAxisLine(false); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tfLight); - leftAxis.setTextColor(ColorTemplate.getHoloBlue()); - leftAxis.setAxisMaximum(200f); - leftAxis.setAxisMinimum(0f); - leftAxis.setDrawGridLines(true); - leftAxis.setGranularityEnabled(true); - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setTypeface(tfLight); - rightAxis.setTextColor(Color.MAGENTA); - rightAxis.setAxisMaximum(900); - rightAxis.setAxisMinimum(-200); - rightAxis.setDrawGridLines(false); - rightAxis.setDrawZeroLine(false); - rightAxis.setGranularityEnabled(false); - } - - private void setData(int count, float range) { - - ArrayList values1 = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(count); - - for (int i = 0; i < count; i++) { - float val = (sampleValues[i].floatValue() * (range / 2f)) + 50; - values1.add(new Entry(i, val)); - } - - ArrayList values2 = new ArrayList<>(); - - for (int i = 0; i < count; i++) { - float val = (sampleValues[i].floatValue() * range) + 450; - values2.add(new Entry(i, val)); - } - - ArrayList values3 = new ArrayList<>(); - - for (int i = 0; i < count; i++) { - float val = (sampleValues[i].floatValue() * range) + 500; - values3.add(new Entry(i, val)); - } - - LineDataSet set1, set2, set3; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set1 = (LineDataSet) chart.getData().getDataSetByIndex(0); - set2 = (LineDataSet) chart.getData().getDataSetByIndex(1); - set3 = (LineDataSet) chart.getData().getDataSetByIndex(2); - set1.setEntries(values1); - set2.setEntries(values2); - set3.setEntries(values3); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - } else { - // create a dataset and give it a type - set1 = new LineDataSet(values1, "DataSet 1"); - - set1.setAxisDependency(AxisDependency.LEFT); - set1.setColor(ColorTemplate.getHoloBlue()); - set1.setCircleColor(Color.WHITE); - set1.setLineWidth(2f); - set1.setCircleRadius(3f); - set1.setFillAlpha(65); - set1.setFillColor(ColorTemplate.getHoloBlue()); - set1.setHighLightColor(Color.rgb(244, 117, 117)); - set1.setDrawCircleHole(false); - //set1.setFillFormatter(new MyFillFormatter(0f)); - //set1.setDrawHorizontalHighlightIndicator(false); - //set1.setVisible(false); - //set1.setCircleHoleColor(Color.WHITE); - - // create a dataset and give it a type - set2 = new LineDataSet(values2, "DataSet 2"); - set2.setAxisDependency(AxisDependency.RIGHT); - set2.setColor(Color.MAGENTA); - set2.setCircleColor(Color.WHITE); - set2.setLineWidth(2f); - set2.setCircleRadius(3f); - set2.setFillAlpha(65); - set2.setFillColor(Color.BLUE); - set2.setDrawCircleHole(false); - set2.setHighLightColor(Color.rgb(244, 117, 117)); - //set2.setFillFormatter(new MyFillFormatter(900f)); - - set3 = new LineDataSet(values3, "DataSet 3"); - set3.setAxisDependency(AxisDependency.RIGHT); - set3.setColor(Color.YELLOW); - set3.setCircleColor(Color.WHITE); - set3.setLineWidth(2f); - set3.setCircleRadius(3f); - set3.setFillAlpha(65); - set3.setFillColor(ColorTemplate.colorWithAlpha(Color.YELLOW, 200)); - set3.setDrawCircleHole(false); - set3.setHighLightColor(Color.rgb(244, 117, 117)); - - // create a data object with the data sets - LineData data = new LineData(set1, set2, set3); - data.setValueTextColor(Color.WHITE); - data.setValueTextSize(9f); - - // set data - chart.setData(data); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.line, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/LineChartActivity2.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if (chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionToggleFilled: { - - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setDrawFilled(!set.isDrawFilledEnabled()); - } - chart.invalidate(); - break; - } - case R.id.actionToggleCircles: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setDrawCircles(!set.isDrawCirclesEnabled()); - } - chart.invalidate(); - break; - } - case R.id.actionToggleCubic: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setMode(set.getMode() == LineDataSet.Mode.CUBIC_BEZIER - ? LineDataSet.Mode.LINEAR - : LineDataSet.Mode.CUBIC_BEZIER); - } - chart.invalidate(); - break; - } - case R.id.actionToggleStepped: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setMode(set.getMode() == LineDataSet.Mode.STEPPED - ? LineDataSet.Mode.LINEAR - : LineDataSet.Mode.STEPPED); - } - chart.invalidate(); - break; - } - case R.id.actionToggleHorizontalCubic: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setMode(set.getMode() == LineDataSet.Mode.HORIZONTAL_BEZIER - ? LineDataSet.Mode.LINEAR - : LineDataSet.Mode.HORIZONTAL_BEZIER); - } - chart.invalidate(); - break; - } - case R.id.actionTogglePinch: { - chart.setPinchZoom(!chart.isPinchZoomEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - setData(seekBarX.getProgress(), seekBarY.getProgress()); - - // redraw - chart.invalidate(); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "LineChartActivity2"); - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - Log.i("Entry selected", e.toString()); - - chart.centerViewToAnimated(e.getX(), e.getY(), chart.getData().getDataSetByIndex(h.getDataSetIndex()) - .getAxisDependency(), 500); - //chart.zoomAndCenterAnimated(2.5f, 2.5f, e.getX(), e.getY(), chart.getData().getDataSetByIndex(dataSetIndex) - // .getAxisDependency(), 1000); - //chart.zoomAndCenterAnimated(1.8f, 1.8f, e.getX(), e.getY(), chart.getData().getDataSetByIndex(dataSetIndex) - // .getAxisDependency(), 1000); - } - - @Override - public void onNothingSelected() { - Log.i("Nothing selected", "Nothing selected."); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/LineChartDualAxisActivity.kt b/app/src/main/java/info/appdev/chartexample/LineChartDualAxisActivity.kt new file mode 100644 index 0000000000..216c78aab4 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/LineChartDualAxisActivity.kt @@ -0,0 +1,392 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.Legend.LegendForm +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate.colorWithAlpha +import com.github.mikephil.charting.utils.ColorTemplate.holoBlue +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +/** + * Example of a dual axis [LineChart] with multiple data sets. + * + * @since 1.7.4 + * @version 3.1.0 + */ +class LineChartDualAxisActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: LineChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_linechart) + + setTitle("LineChart DualAxis") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarX!!.setOnSeekBarChangeListener(this) + + seekBarY = findViewById(R.id.seekBarY) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + + // no description text + chart!!.description.isEnabled = false + + // enable touch gestures + chart!!.setTouchEnabled(true) + + chart!!.dragDecelerationFrictionCoef = 0.9f + + // enable scaling and dragging + chart!!.isDragEnabled = true + chart!!.setScaleEnabled(true) + chart!!.drawGridBackground = false + chart!!.isHighlightPerDragEnabled = true + + // if disabled, scaling can be done on x- and y-axis separately + chart!!.setPinchZoom(true) + + // set an alternative background color + chart!!.setBackgroundColor(Color.LTGRAY) + + // add data + seekBarX!!.progress = 20 + seekBarY!!.progress = 30 + + chart!!.animateX(1500) + + // get the legend (only possible after setting data) + val l = chart!!.legend + + // modify the legend ... + l.form = LegendForm.LINE + l.typeface = tfLight + l.textSize = 11f + l.textColor = Color.WHITE + l.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + l.horizontalAlignment = Legend.LegendHorizontalAlignment.LEFT + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + + // l.setYOffset(11f); + val xAxis = chart!!.xAxis + xAxis.typeface = tfLight + xAxis.textSize = 11f + xAxis.textColor = Color.WHITE + xAxis.setDrawGridLines(false) + xAxis.setDrawAxisLine(false) + + val leftAxis = chart!!.axisLeft + leftAxis.typeface = tfLight + leftAxis.textColor = holoBlue + leftAxis.axisMaximum = 200f + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(true) + leftAxis.isGranularityEnabled = true + + val rightAxis = chart!!.axisRight + rightAxis.typeface = tfLight + rightAxis.textColor = Color.MAGENTA + rightAxis.axisMaximum = 900f + rightAxis.axisMinimum = -200f + rightAxis.setDrawGridLines(false) + rightAxis.setDrawZeroLine(false) + rightAxis.isGranularityEnabled = false + } + + private fun setData(count: Int, range: Float) { + val values1 = ArrayList() + val sampleValues = getValues(count) + + for (i in 0..() + + for (i in 0..() + + for (i in 0.. 0 + ) { + set1 = chart!!.data!!.getDataSetByIndex(0) as LineDataSet + set2 = chart!!.data!!.getDataSetByIndex(1) as LineDataSet + set3 = chart!!.data!!.getDataSetByIndex(2) as LineDataSet + set1.entries = values1 + set2.entries = values2 + set3.entries = values3 + chart!!.data!!.notifyDataChanged() + chart!!.notifyDataSetChanged() + } else { + // create a dataset and give it a type + set1 = LineDataSet(values1, "DataSet 1") + + set1.axisDependency = AxisDependency.LEFT + set1.setColor(holoBlue) + set1.setCircleColor(Color.WHITE) + set1.lineWidth = 2f + set1.circleRadius = 3f + set1.fillAlpha = 65 + set1.fillColor = holoBlue + set1.highLightColor = Color.rgb(244, 117, 117) + set1.isDrawCircleHoleEnabled = false + + //set1.setFillFormatter(new MyFillFormatter(0f)); + //set1.setDrawHorizontalHighlightIndicator(false); + //set1.setVisible(false); + //set1.setCircleHoleColor(Color.WHITE); + + // create a dataset and give it a type + set2 = LineDataSet(values2, "DataSet 2") + set2.axisDependency = AxisDependency.RIGHT + set2.setColor(Color.MAGENTA) + set2.setCircleColor(Color.WHITE) + set2.lineWidth = 2f + set2.circleRadius = 3f + set2.fillAlpha = 65 + set2.fillColor = Color.BLUE + set2.isDrawCircleHoleEnabled = false + set2.highLightColor = Color.rgb(244, 117, 117) + + //set2.setFillFormatter(new MyFillFormatter(900f)); + set3 = LineDataSet(values3, "DataSet 3") + set3.axisDependency = AxisDependency.RIGHT + set3.setColor(Color.YELLOW) + set3.setCircleColor(Color.WHITE) + set3.lineWidth = 2f + set3.circleRadius = 3f + set3.fillAlpha = 65 + set3.fillColor = colorWithAlpha(Color.YELLOW, 200) + set3.isDrawCircleHoleEnabled = false + set3.highLightColor = Color.rgb(244, 117, 117) + + // create a data object with the data sets + val data = LineData(set1, set2, set3) + data.setValueTextColor(Color.WHITE) + data.setValueTextSize(9f) + + // set data + chart!!.setData(data) + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.line, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/LineChartActivity2.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawValuesEnabled = (!set.isDrawValuesEnabled) + } + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionToggleFilled -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawFilledEnabled = !set.isDrawFilledEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleCircles -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawCirclesEnabled = !set.isDrawCirclesEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleCubic -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.mode = ( + if (set.mode == LineDataSet.Mode.CUBIC_BEZIER) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.CUBIC_BEZIER + ) + } + chart!!.invalidate() + } + + R.id.actionToggleStepped -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.mode = ( + if (set.mode == LineDataSet.Mode.STEPPED) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.STEPPED + ) + } + chart!!.invalidate() + } + + R.id.actionToggleHorizontalCubic -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.mode = ( + if (set.mode == LineDataSet.Mode.HORIZONTAL_BEZIER) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.HORIZONTAL_BEZIER + ) + } + chart!!.invalidate() + } + + R.id.actionTogglePinch -> { + chart!!.setPinchZoom(!chart!!.isPinchZoomEnabled) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + setData(seekBarX!!.progress, seekBarY!!.progress.toFloat()) + + // redraw + chart!!.invalidate() + } + + override fun saveToGallery() { + saveToGallery(chart, "LineChartActivity2") + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null || h == null) return + + Log.i("Entry selected", e.toString()) + + chart!!.centerViewToAnimated(e.x, e.y, chart!!.data!!.getDataSetByIndex(h.dataSetIndex).axisDependency, 500) + //chart.zoomAndCenterAnimated(2.5f, 2.5f, e.getX(), e.getY(), chart.getData().getDataSetByIndex(dataSetIndex) + // .getAxisDependency(), 1000); + //chart.zoomAndCenterAnimated(1.8f, 1.8f, e.getX(), e.getY(), chart.getData().getDataSetByIndex(dataSetIndex) + // .getAxisDependency(), 1000); + } + + override fun onNothingSelected() { + Log.i("Nothing selected", "Nothing selected.") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/LineChartTime.java b/app/src/main/java/info/appdev/chartexample/LineChartTime.java deleted file mode 100644 index d76e39baf5..0000000000 --- a/app/src/main/java/info/appdev/chartexample/LineChartTime.java +++ /dev/null @@ -1,323 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.AxisBase; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Locale; -import java.util.concurrent.TimeUnit; - -public class LineChartTime extends DemoBase implements OnSeekBarChangeListener { - - private LineChart chart; - private SeekBar seekBarX; - private TextView tvX; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_linechart_time); - - setTitle("LineChartTime"); - - tvX = findViewById(R.id.tvXMax); - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - - // no description text - chart.getDescription().setEnabled(false); - - // enable touch gestures - chart.setTouchEnabled(true); - - chart.setDragDecelerationFrictionCoef(0.9f); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - chart.setDrawGridBackground(false); - chart.setHighlightPerDragEnabled(true); - - // set an alternative background color - chart.setBackgroundColor(Color.WHITE); - chart.setViewPortOffsets(0f, 0f, 0f, 0f); - - // add data - seekBarX.setProgress(100); - - // get the legend (only possible after setting data) - Legend l = chart.getLegend(); - l.setEnabled(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setPosition(XAxis.XAxisPosition.TOP_INSIDE); - xAxis.setTypeface(tfLight); - xAxis.setTextSize(10f); - xAxis.setTextColor(Color.WHITE); - xAxis.setDrawAxisLine(false); - xAxis.setDrawGridLines(true); - xAxis.setTextColor(Color.rgb(255, 192, 56)); - xAxis.setCenterAxisLabels(true); - xAxis.setGranularity(1f); // one hour - xAxis.setValueFormatter(new IAxisValueFormatter() { - - private final SimpleDateFormat mFormat = new SimpleDateFormat("dd MMM HH:mm", Locale.ENGLISH); - - @Override - public String getFormattedValue(float value, AxisBase axis) { - - long millis = TimeUnit.HOURS.toMillis((long) value); - return mFormat.format(new Date(millis)); - } - }); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setPosition(YAxis.YAxisLabelPosition.INSIDE_CHART); - leftAxis.setTypeface(tfLight); - leftAxis.setTextColor(ColorTemplate.getHoloBlue()); - leftAxis.setDrawGridLines(true); - leftAxis.setGranularityEnabled(true); - leftAxis.setAxisMinimum(0f); - leftAxis.setAxisMaximum(170f); - leftAxis.setYOffset(-9f); - leftAxis.setTextColor(Color.rgb(255, 192, 56)); - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setEnabled(false); - } - - private void setData(int count) { - - // now in hours - long now = 0; //470044; //TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()); - - ArrayList values = new ArrayList<>(); - - // count = hours - float to = now + count; - - Double[] valuesData = DataTools.Companion.getValues(Math.round(to)); - // increment by 1 hour - for (float x = now; x < to; x++) { - float y; - if (count == 100) // initial - y = (valuesData[Math.round(x)]).floatValue() * 50 + 50; - else - y = (float) (Math.random() * 50 + 50); // manually triggered - values.add(new Entry(x, y)); // add one entry per hour - } - - // create a dataset and give it a type - LineDataSet set1 = new LineDataSet(values, "DataSet 1"); - set1.setAxisDependency(AxisDependency.LEFT); - set1.setColor(ColorTemplate.getHoloBlue()); - set1.setValueTextColor(ColorTemplate.getHoloBlue()); - set1.setLineWidth(1.5f); - set1.setDrawCircles(false); - set1.setDrawValues(false); - set1.setFillAlpha(65); - set1.setFillColor(ColorTemplate.getHoloBlue()); - set1.setHighLightColor(Color.rgb(244, 117, 117)); - set1.setDrawCircleHole(false); - - // create a data object with the data sets - LineData data = new LineData(set1); - data.setValueTextColor(Color.WHITE); - data.setValueTextSize(9f); - - // set data - chart.setData(data); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.line, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/LineChartTime.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if (chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionToggleFilled: { - - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - if (set.isDrawFilledEnabled()) - set.setDrawFilled(false); - else - set.setDrawFilled(true); - } - chart.invalidate(); - break; - } - case R.id.actionToggleCircles: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - if (set.isDrawCirclesEnabled()) - set.setDrawCircles(false); - else - set.setDrawCircles(true); - } - chart.invalidate(); - break; - } - case R.id.actionToggleCubic: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - if (set.getMode() == LineDataSet.Mode.CUBIC_BEZIER) - set.setMode(LineDataSet.Mode.LINEAR); - else - set.setMode(LineDataSet.Mode.CUBIC_BEZIER); - } - chart.invalidate(); - break; - } - case R.id.actionToggleStepped: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - if (set.getMode() == LineDataSet.Mode.STEPPED) - set.setMode(LineDataSet.Mode.LINEAR); - else - set.setMode(LineDataSet.Mode.STEPPED); - } - chart.invalidate(); - break; - } - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) - chart.setPinchZoom(false); - else - chart.setPinchZoom(true); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - - setData(seekBarX.getProgress()); - - // redraw - chart.invalidate(); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "LineChartTime"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/LineChartTime.kt b/app/src/main/java/info/appdev/chartexample/LineChartTime.kt new file mode 100644 index 0000000000..08d38a0742 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/LineChartTime.kt @@ -0,0 +1,288 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.AxisBase +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.components.YAxis.YAxisLabelPosition +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.utils.ColorTemplate.holoBlue +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit +import kotlin.math.roundToInt + +class LineChartTime : DemoBase(), OnSeekBarChangeListener { + private var chart: LineChart? = null + private var seekBarX: SeekBar? = null + private var tvX: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_linechart_time) + + setTitle("LineChartTime") + + tvX = findViewById(R.id.tvXMax) + seekBarX = findViewById(R.id.seekBarX) + seekBarX!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + + // no description text + chart!!.description.isEnabled = false + + // enable touch gestures + chart!!.setTouchEnabled(true) + + chart!!.dragDecelerationFrictionCoef = 0.9f + + // enable scaling and dragging + chart!!.isDragEnabled = true + chart!!.setScaleEnabled(true) + chart!!.drawGridBackground = false + chart!!.isHighlightPerDragEnabled = true + + // set an alternative background color + chart!!.setBackgroundColor(Color.WHITE) + chart!!.setViewPortOffsets(0f, 0f, 0f, 0f) + + // add data + seekBarX!!.progress = 100 + + // get the legend (only possible after setting data) + val l = chart!!.legend + l.isEnabled = false + + val xAxis = chart!!.xAxis + xAxis.position = XAxisPosition.TOP_INSIDE + xAxis.typeface = tfLight + xAxis.textSize = 10f + xAxis.textColor = Color.WHITE + xAxis.setDrawAxisLine(false) + xAxis.setDrawGridLines(true) + xAxis.textColor = Color.rgb(255, 192, 56) + xAxis.setCenterAxisLabels(true) + xAxis.granularity = 1f // one hour + xAxis.valueFormatter = object : IAxisValueFormatter { + private val mFormat = SimpleDateFormat("dd MMM HH:mm", Locale.ENGLISH) + + override fun getFormattedValue(value: Float, axis: AxisBase?): String { + val millis = TimeUnit.HOURS.toMillis(value.toLong()) + return mFormat.format(Date(millis)) + } + } + + val leftAxis = chart!!.axisLeft + leftAxis.setPosition(YAxisLabelPosition.INSIDE_CHART) + leftAxis.typeface = tfLight + leftAxis.textColor = holoBlue + leftAxis.setDrawGridLines(true) + leftAxis.isGranularityEnabled = true + leftAxis.axisMinimum = 0f + leftAxis.axisMaximum = 170f + leftAxis.yOffset = -9f + leftAxis.textColor = Color.rgb(255, 192, 56) + + val rightAxis = chart!!.axisRight + rightAxis.isEnabled = false + } + + private fun setData(count: Int) { + // now in hours + + val now: Long = 0 //470044; //TimeUnit.MILLISECONDS.toHours(System.currentTimeMillis()); + + val values = ArrayList() + + // count = hours + val to = (now + count).toFloat() + + val valuesData = getValues(to.roundToInt()) + // increment by 1 hour + var x = now.toFloat() + while (x < to) { + val y = if (count == 100) // initial + (valuesData[x.roundToInt()]).toFloat() * 50 + 50 + else (Math.random() * 50 + 50).toFloat() // manually triggered + + values.add(Entry(x, y)) // add one entry per hour + x++ + } + + // create a dataset and give it a type + val set1 = LineDataSet(values, "DataSet 1") + set1.axisDependency = AxisDependency.LEFT + set1.setColor(holoBlue) + set1.valueTextColor = holoBlue + set1.lineWidth = 1.5f + set1.isDrawCirclesEnabled = false + set1.isDrawValuesEnabled = false + set1.fillAlpha = 65 + set1.fillColor = holoBlue + set1.highLightColor = Color.rgb(244, 117, 117) + set1.isDrawCircleHoleEnabled = false + + // create a data object with the data sets + val data = LineData(set1) + data.setValueTextColor(Color.WHITE) + data.setValueTextSize(9f) + + // set data + chart!!.setData(data) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.line, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/LineChartTime.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionToggleFilled -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawFilledEnabled = !set.isDrawFilledEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleCircles -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawCirclesEnabled = !set.isDrawCirclesEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleCubic -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + if (set.mode == LineDataSet.Mode.CUBIC_BEZIER) set.mode = LineDataSet.Mode.LINEAR + else set.mode = LineDataSet.Mode.CUBIC_BEZIER + } + chart!!.invalidate() + } + + R.id.actionToggleStepped -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + if (set.mode == LineDataSet.Mode.STEPPED) set.mode = LineDataSet.Mode.LINEAR + else set.mode = LineDataSet.Mode.STEPPED + } + chart!!.invalidate() + } + + R.id.actionTogglePinch -> { + if (chart!!.isPinchZoomEnabled) chart!!.setPinchZoom(false) + else chart!!.setPinchZoom(true) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + + setData(seekBarX!!.progress) + + // redraw + chart!!.invalidate() + } + + override fun saveToGallery() { + saveToGallery(chart, "LineChartTime") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/ListViewBarChartActivity.java b/app/src/main/java/info/appdev/chartexample/ListViewBarChartActivity.java deleted file mode 100644 index 32da5e1957..0000000000 --- a/app/src/main/java/info/appdev/chartexample/ListViewBarChartActivity.java +++ /dev/null @@ -1,183 +0,0 @@ - -package info.appdev.chartexample; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.annotation.NonNull; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.ArrayAdapter; -import android.widget.ListView; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -/** - * Demonstrates the use of charts inside a ListView. IMPORTANT: provide a - * specific height attribute for the chart inside your ListView item - * - * @author Philipp Jahoda - */ -public class ListViewBarChartActivity extends DemoBase { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_listview_chart); - - setTitle("ListViewBarChartActivity"); - - ListView lv = findViewById(R.id.listViewMain); - - ArrayList list = new ArrayList<>(); - - // 20 items - for (int i = 0; i < 20; i++) { - list.add(generateData(i + 1)); - } - - ChartDataAdapter cda = new ChartDataAdapter(getApplicationContext(), list); - lv.setAdapter(cda); - } - - private class ChartDataAdapter extends ArrayAdapter { - - ChartDataAdapter(Context context, List objects) { - super(context, 0, objects); - } - - @SuppressLint("InflateParams") - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - - BarData data = getItem(position); - - ViewHolder holder; - - if (convertView == null) { - - holder = new ViewHolder(); - - convertView = LayoutInflater.from(getContext()).inflate( - R.layout.list_item_barchart, null); - holder.chart = convertView.findViewById(R.id.chart); - - convertView.setTag(holder); - - } else { - holder = (ViewHolder) convertView.getTag(); - } - - // apply styling - if (data != null) { - data.setValueTypeface(tfLight); - data.setValueTextColor(Color.BLACK); - } - holder.chart.getDescription().setEnabled(false); - holder.chart.setDrawGridBackground(false); - - XAxis xAxis = holder.chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTTOM); - xAxis.setTypeface(tfLight); - xAxis.setDrawGridLines(false); - - YAxis leftAxis = holder.chart.getAxisLeft(); - leftAxis.setTypeface(tfLight); - leftAxis.setLabelCount(5, false); - leftAxis.setSpaceTop(15f); - - YAxis rightAxis = holder.chart.getAxisRight(); - rightAxis.setTypeface(tfLight); - rightAxis.setLabelCount(5, false); - rightAxis.setSpaceTop(15f); - - // set data - holder.chart.setData(data); - holder.chart.setFitBars(true); - - // do not forget to refresh the chart -// holder.chart.invalidate(); - holder.chart.animateY(700); - - return convertView; - } - - private class ViewHolder { - - BarChart chart; - } - } - - /** - * generates a random ChartData object with just one DataSet - * - * @return Bar data - */ - private BarData generateData(int cnt) { - int count = 12; - ArrayList entries = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(count); - - for (int i = 0; i < count; i++) { - entries.add(new BarEntry(i, (sampleValues[i].floatValue() * 70) + 30)); - } - - BarDataSet d = new BarDataSet(entries, "New DataSet " + cnt); - d.setColors(ColorTemplate.VORDIPLOM_COLORS); - d.setBarShadowColor(Color.rgb(203, 203, 203)); - - ArrayList sets = new ArrayList<>(); - sets.add(d); - - BarData cd = new BarData(sets); - cd.setBarWidth(0.9f); - return cd; - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.only_github, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/ListViewBarChartActivity.java")); - startActivity(i); - break; - } - } - - return true; - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } -} diff --git a/app/src/main/java/info/appdev/chartexample/ListViewBarChartActivity.kt b/app/src/main/java/info/appdev/chartexample/ListViewBarChartActivity.kt new file mode 100644 index 0000000000..962946c856 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/ListViewBarChartActivity.kt @@ -0,0 +1,162 @@ +package info.appdev.chartexample + +import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.ArrayAdapter +import android.widget.ListView +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +/** + * Demonstrates the use of charts inside a ListView. IMPORTANT: provide a + * specific height attribute for the chart inside your ListView item + * + * @author Philipp Jahoda + */ +class ListViewBarChartActivity : DemoBase() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_listview_chart) + + setTitle("ListViewBarChartActivity") + + val lv = findViewById(R.id.listViewMain) + + val list = ArrayList() + + // 20 items + for (i in 0..19) { + list.add(generateData(i + 1)) + } + + val cda = ChartDataAdapter(applicationContext, list) + lv!!.setAdapter(cda) + } + + private inner class ChartDataAdapter(context: Context, objects: MutableList) : ArrayAdapter(context, 0, objects) { + @SuppressLint("InflateParams") + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + var convertView = convertView + val data = getItem(position) + + val holder: ViewHolder + + if (convertView == null) { + holder = ViewHolder() + + convertView = LayoutInflater.from(context).inflate( + R.layout.list_item_barchart, null + ) + holder.chart = convertView.findViewById(R.id.chart) + + convertView.tag = holder + } else { + holder = convertView.tag as ViewHolder + } + + // apply styling + if (data != null) { + data.setValueTypeface(tfLight) + data.setValueTextColor(Color.BLACK) + } + holder.chart!!.description.isEnabled = false + holder.chart!!.drawGridBackground = false + + val xAxis = holder.chart!!.xAxis + xAxis.position = XAxisPosition.BOTTOM + xAxis.typeface = tfLight + xAxis.setDrawGridLines(false) + + val leftAxis = holder.chart!!.axisLeft + leftAxis.typeface = tfLight + leftAxis.setLabelCount(5, false) + leftAxis.spaceTop = 15f + + val rightAxis = holder.chart!!.axisRight + rightAxis.typeface = tfLight + rightAxis.setLabelCount(5, false) + rightAxis.spaceTop = 15f + + // set data + holder.chart!!.setData(data) + holder.chart!!.setFitBars(true) + + // do not forget to refresh the chart +// holder.chart.invalidate(); + holder.chart!!.animateY(700) + + return convertView + } + + private inner class ViewHolder { + var chart: BarChart? = null + } + } + + /** + * generates a random ChartData object with just one DataSet + * + * @return Bar data + */ + private fun generateData(cnt: Int): BarData { + val count = 12 + val entries = ArrayList() + val sampleValues = getValues(count) + + for (i in 0..() + sets.add(d) + + val cd = BarData(sets) + cd.barWidth = 0.9f + return cd + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.only_github, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/ListViewBarChartActivity.java".toUri()) + startActivity(i) + } + } + + return true + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } +} diff --git a/app/src/main/java/info/appdev/chartexample/ListViewMultiChartActivity.java b/app/src/main/java/info/appdev/chartexample/ListViewMultiChartActivity.java deleted file mode 100644 index 3f086359a3..0000000000 --- a/app/src/main/java/info/appdev/chartexample/ListViewMultiChartActivity.java +++ /dev/null @@ -1,214 +0,0 @@ - -package info.appdev.chartexample; - -import android.content.Context; -import android.content.Intent; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.annotation.NonNull; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.ArrayAdapter; -import android.widget.ListView; - -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.listviewitems.BarChartItem; -import info.appdev.chartexample.listviewitems.ChartItem; -import info.appdev.chartexample.listviewitems.LineChartItem; -import info.appdev.chartexample.listviewitems.PieChartItem; -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -/** - * Demonstrates the use of charts inside a ListView. IMPORTANT: provide a - * specific height attribute for the chart inside your ListView item - * - * @author Philipp Jahoda - */ -public class ListViewMultiChartActivity extends DemoBase { - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_listview_chart); - - setTitle("ListViewMultiChartActivity"); - - ListView lv = findViewById(R.id.listViewMain); - - ArrayList list = new ArrayList<>(); - - // 30 items - for (int i = 0; i < 30; i++) { - - if(i % 3 == 0) { - list.add(new LineChartItem(generateDataLine(i + 1), getApplicationContext())); - } else if(i % 3 == 1) { - list.add(new BarChartItem(generateDataBar(i + 1), getApplicationContext())); - } else if(i % 3 == 2) { - list.add(new PieChartItem(generateDataPie(), getApplicationContext())); - } - } - - ChartDataAdapter cda = new ChartDataAdapter(getApplicationContext(), list); - lv.setAdapter(cda); - } - - /** adapter that supports 3 different item types */ - private class ChartDataAdapter extends ArrayAdapter { - - ChartDataAdapter(Context context, List objects) { - super(context, 0, objects); - } - - @NonNull - @Override - public View getView(int position, View convertView, @NonNull ViewGroup parent) { - //noinspection ConstantConditions - return getItem(position).getView(position, convertView, getContext()); - } - - @Override - public int getItemViewType(int position) { - // return the views type - ChartItem ci = getItem(position); - return ci != null ? ci.getItemType() : 0; - } - - @Override - public int getViewTypeCount() { - return 3; // we have 3 different item-types - } - } - - /** - * generates a random ChartData object with just one DataSet - * - * @return Line data - */ - private LineData generateDataLine(int cnt) { - - ArrayList values1 = new ArrayList<>(); - int count = 12; - Double[] sampleValues = DataTools.Companion.getValues(count); - - for (int i = 0; i < count; i++) { - values1.add(new Entry(i, (int) (sampleValues[i].floatValue() * 65) + 40)); - } - - LineDataSet d1 = new LineDataSet(values1, "New DataSet " + cnt + ", (1)"); - d1.setLineWidth(2.5f); - d1.setCircleRadius(4.5f); - d1.setHighLightColor(Color.rgb(244, 117, 117)); - d1.setDrawValues(false); - - ArrayList values2 = new ArrayList<>(); - - for (int i = 0; i < count; i++) { - values2.add(new Entry(i, values1.get(i).getY() - 30)); - } - - LineDataSet d2 = new LineDataSet(values2, "New DataSet " + cnt + ", (2)"); - d2.setLineWidth(2.5f); - d2.setCircleRadius(4.5f); - d2.setHighLightColor(Color.rgb(244, 117, 117)); - d2.setColor(ColorTemplate.VORDIPLOM_COLORS[0]); - d2.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[0]); - d2.setDrawValues(false); - - ArrayList sets = new ArrayList<>(); - sets.add(d1); - sets.add(d2); - - return new LineData(sets); - } - - /** - * generates a random ChartData object with just one DataSet - * - * @return Bar data - */ - private BarData generateDataBar(int cnt) { - int count = 12; - ArrayList entries = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(count); - - for (int i = 0; i < count; i++) { - entries.add(new BarEntry(i, (int) (sampleValues[i].floatValue() * 70) + 30)); - } - - BarDataSet d = new BarDataSet(entries, "New DataSet " + cnt); - d.setColors(ColorTemplate.VORDIPLOM_COLORS); - d.setHighLightAlpha(255); - - BarData cd = new BarData(d); - cd.setBarWidth(0.9f); - return cd; - } - - /** - * generates a random ChartData object with just one DataSet - * - * @return Pie data - */ - private PieData generateDataPie() { - int cnt = 4; - ArrayList entries = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(cnt); - - for (int i = 0; i < cnt; i++) { - entries.add(new PieEntry((sampleValues[i].floatValue() * 70) + 30, "Quarter " + (i+1))); - } - - PieDataSet d = new PieDataSet(entries, ""); - - // space between slices - d.setSliceSpace(2f); - d.setColors(ColorTemplate.VORDIPLOM_COLORS); - - return new PieData(d); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.only_github, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/ListViewMultiChartActivity.java")); - startActivity(i); - break; - } - } - - return true; - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } -} diff --git a/app/src/main/java/info/appdev/chartexample/ListViewMultiChartActivity.kt b/app/src/main/java/info/appdev/chartexample/ListViewMultiChartActivity.kt new file mode 100644 index 0000000000..15064d3301 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/ListViewMultiChartActivity.kt @@ -0,0 +1,192 @@ +package info.appdev.chartexample + +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.ArrayAdapter +import android.widget.ListView +import androidx.core.net.toUri +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.listviewitems.BarChartItem +import info.appdev.chartexample.listviewitems.ChartItem +import info.appdev.chartexample.listviewitems.LineChartItem +import info.appdev.chartexample.listviewitems.PieChartItem +import info.appdev.chartexample.notimportant.DemoBase + +/** + * Demonstrates the use of charts inside a ListView. IMPORTANT: provide a + * specific height attribute for the chart inside your ListView item + * + * @author Philipp Jahoda + */ +class ListViewMultiChartActivity : DemoBase() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_listview_chart) + + setTitle("ListViewMultiChartActivity") + + val lv = findViewById(R.id.listViewMain) + + val list = ArrayList() + + // 30 items + for (i in 0..29) { + if (i % 3 == 0) { + list.add(LineChartItem(generateDataLine(i + 1), applicationContext)) + } else if (i % 3 == 1) { + list.add(BarChartItem(generateDataBar(i + 1), applicationContext)) + } else { + list.add(PieChartItem(generateDataPie(), applicationContext)) + } + } + + val cda = ChartDataAdapter(applicationContext, list) + lv!!.setAdapter(cda) + } + + /** adapter that supports 3 different item types */ + private inner class ChartDataAdapter(context: Context, objects: MutableList) : ArrayAdapter(context, 0, objects) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + return getItem(position)!!.getView(position, convertView, context) + } + + override fun getItemViewType(position: Int): Int { + // return the views type + val ci = getItem(position) + return ci?.itemType ?: 0 + } + + override fun getViewTypeCount(): Int { + return 3 // we have 3 different item-types + } + } + + /** + * generates a random ChartData object with just one DataSet + * + * @return Line data + */ + private fun generateDataLine(cnt: Int): LineData { + val values1 = ArrayList() + val count = 12 + val sampleValues = getValues(count) + + for (i in 0..() + + for (i in 0..() + sets.add(d1) + sets.add(d2) + + return LineData(sets) + } + + /** + * generates a random ChartData object with just one DataSet + * + * @return Bar data + */ + private fun generateDataBar(cnt: Int): BarData { + val count = 12 + val entries = ArrayList() + val sampleValues = getValues(count) + + for (i in 0..() + val sampleValues = getValues(cnt) + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/ListViewMultiChartActivity.java".toUri()) + startActivity(i) + } + } + + return true + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } +} diff --git a/app/src/main/java/info/appdev/chartexample/MultiLineChartActivity.java b/app/src/main/java/info/appdev/chartexample/MultiLineChartActivity.java deleted file mode 100644 index 83c8e8989c..0000000000 --- a/app/src/main/java/info/appdev/chartexample/MultiLineChartActivity.java +++ /dev/null @@ -1,356 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.MotionEvent; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.listener.ChartTouchListener; -import com.github.mikephil.charting.listener.OnChartGestureListener; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -public class MultiLineChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartGestureListener, OnChartValueSelectedListener { - - private LineChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_linechart); - - setTitle("MultiLineChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setOnSeekBarChangeListener(this); - - seekBarY = findViewById(R.id.seekBarY); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - - chart.setDrawGridBackground(false); - chart.getDescription().setEnabled(false); - chart.setDrawBorders(false); - - chart.getAxisLeft().setEnabled(false); - chart.getAxisRight().setDrawAxisLine(false); - chart.getAxisRight().setDrawGridLines(false); - chart.getXAxis().setDrawAxisLine(false); - chart.getXAxis().setDrawGridLines(false); - - // enable touch gestures - chart.setTouchEnabled(true); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - - // if disabled, scaling can be done on x- and y-axis separately - chart.setPinchZoom(false); - - seekBarX.setProgress(20); - seekBarY.setProgress(100); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(false); - } - - private final int[] colors = new int[] { - ColorTemplate.VORDIPLOM_COLORS[0], - ColorTemplate.VORDIPLOM_COLORS[1], - ColorTemplate.VORDIPLOM_COLORS[2] - }; - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - chart.resetTracking(); - - progress = seekBarX.getProgress(); - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - ArrayList dataSets = new ArrayList<>(); - - for (int z = 0; z < 3; z++) { - - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100); - - for (int i = 0; i < progress; i++) { - double val = (sampleValues[i].floatValue() * seekBarY.getProgress()) + 3; - values.add(new Entry(i, (float) val)); - } - - LineDataSet d = new LineDataSet(values, "DataSet " + (z + 1)); - d.setLineWidth(2.5f); - d.setCircleRadius(4f); - - int color = colors[z % colors.length]; - d.setColor(color); - d.setCircleColor(color); - dataSets.add(d); - } - - // make the first DataSet dashed - ((LineDataSet) dataSets.get(0)).enableDashedLine(10, 10, 0); - ((LineDataSet) dataSets.get(0)).setColors(ColorTemplate.VORDIPLOM_COLORS); - ((LineDataSet) dataSets.get(0)).setCircleColors(ColorTemplate.VORDIPLOM_COLORS); - - LineData data = new LineData(dataSets); - chart.setData(data); - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.line, menu); - menu.removeItem(R.id.actionToggleIcons); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/MultiLineChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - /* - case R.id.actionToggleIcons: { break; } - */ - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) - chart.setPinchZoom(false); - else - chart.setPinchZoom(true); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleHighlight: { - if(chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionToggleFilled: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - if (set.isDrawFilledEnabled()) - set.setDrawFilled(false); - else - set.setDrawFilled(true); - } - chart.invalidate(); - break; - } - case R.id.actionToggleCircles: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - if (set.isDrawCirclesEnabled()) - set.setDrawCircles(false); - else - set.setDrawCircles(true); - } - chart.invalidate(); - break; - } - case R.id.actionToggleCubic: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setMode(set.getMode() == LineDataSet.Mode.CUBIC_BEZIER - ? LineDataSet.Mode.LINEAR - : LineDataSet.Mode.CUBIC_BEZIER); - } - chart.invalidate(); - break; - } - case R.id.actionToggleStepped: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setMode(set.getMode() == LineDataSet.Mode.STEPPED - ? LineDataSet.Mode.LINEAR - : LineDataSet.Mode.STEPPED); - } - chart.invalidate(); - break; - } - case R.id.actionToggleHorizontalCubic: { - List sets = chart.getData() - .getDataSets(); - - for (ILineDataSet iSet : sets) { - - LineDataSet set = (LineDataSet) iSet; - set.setMode(set.getMode() == LineDataSet.Mode.HORIZONTAL_BEZIER - ? LineDataSet.Mode.LINEAR - : LineDataSet.Mode.HORIZONTAL_BEZIER); - } - chart.invalidate(); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - chart.animateXY(2000, 2000); - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "MultiLineChartActivity"); - } - - @Override - public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) { - Log.i("Gesture", "START, x: " + me.getX() + ", y: " + me.getY()); - } - - @Override - public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) { - Log.i("Gesture", "END, lastGesture: " + lastPerformedGesture); - - // un-highlight values after the gesture is finished and no single-tap - if(lastPerformedGesture != ChartTouchListener.ChartGesture.SINGLE_TAP) - chart.highlightValues(null); // or highlightTouch(null) for callback to onNothingSelected(...) - } - - @Override - public void onChartLongPressed(MotionEvent me) { - Log.i("LongPress", "Chart long pressed."); - } - - @Override - public void onChartDoubleTapped(MotionEvent me) { - Log.i("DoubleTap", "Chart double-tapped."); - } - - @Override - public void onChartSingleTapped(MotionEvent me) { - Log.i("SingleTap", "Chart single-tapped."); - } - - @Override - public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { - Log.i("Fling", "Chart fling. VelocityX: " + velocityX + ", VelocityY: " + velocityY); - } - - @Override - public void onChartScale(MotionEvent me, float scaleX, float scaleY) { - Log.i("Scale / Zoom", "ScaleX: " + scaleX + ", ScaleY: " + scaleY); - } - - @Override - public void onChartTranslate(MotionEvent me, float dX, float dY) { - Log.i("Translate / Move", "dX: " + dX + ", dY: " + dY); - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - Log.i("VAL SELECTED", - "Value: " + e.getY() + ", xIndex: " + e.getX() - + ", DataSet index: " + h.getDataSetIndex()); - } - - @Override - public void onNothingSelected() {} - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/MultiLineChartActivity.kt b/app/src/main/java/info/appdev/chartexample/MultiLineChartActivity.kt new file mode 100644 index 0000000000..7e72350469 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/MultiLineChartActivity.kt @@ -0,0 +1,325 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.MotionEvent +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture +import com.github.mikephil.charting.listener.OnChartGestureListener +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class MultiLineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartGestureListener, OnChartValueSelectedListener { + private var chart: LineChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_linechart) + + setTitle("MultiLineChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarX!!.setOnSeekBarChangeListener(this) + + seekBarY = findViewById(R.id.seekBarY) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + + chart!!.drawGridBackground = false + chart!!.description.isEnabled = false + chart!!.isDrawBordersEnabled = false + + chart!!.axisLeft.isEnabled = false + chart!!.axisRight.setDrawAxisLine(false) + chart!!.axisRight.setDrawGridLines(false) + chart!!.xAxis.setDrawAxisLine(false) + chart!!.xAxis.setDrawGridLines(false) + + // enable touch gestures + chart!!.setTouchEnabled(true) + + // enable scaling and dragging + chart!!.isDragEnabled = true + chart!!.setScaleEnabled(true) + + // if disabled, scaling can be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + seekBarX!!.progress = 20 + seekBarY!!.progress = 100 + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.VERTICAL + l.setDrawInside(false) + } + + private val colors = intArrayOf( + ColorTemplate.VORDIPLOM_COLORS[0], + ColorTemplate.VORDIPLOM_COLORS[1], + ColorTemplate.VORDIPLOM_COLORS[2] + ) + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + chart!!.resetTracking() + + val progress: Int = seekBarX!!.progress + + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + val dataSets = ArrayList() + + for (z in 0..2) { + val values = ArrayList() + val sampleValues = getValues(100) + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/MultiLineChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionTogglePinch -> { + if (chart!!.isPinchZoomEnabled) chart!!.setPinchZoom(false) + else chart!!.setPinchZoom(true) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionToggleFilled -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawFilledEnabled = !set.isDrawFilledEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleCircles -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.isDrawCirclesEnabled = !set.isDrawCirclesEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleCubic -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.mode = ( + if (set.mode == LineDataSet.Mode.CUBIC_BEZIER) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.CUBIC_BEZIER + ) + } + chart!!.invalidate() + } + + R.id.actionToggleStepped -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.mode = ( + if (set.mode == LineDataSet.Mode.STEPPED) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.STEPPED + ) + } + chart!!.invalidate() + } + + R.id.actionToggleHorizontalCubic -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as LineDataSet + set.mode = ( + if (set.mode == LineDataSet.Mode.HORIZONTAL_BEZIER) + LineDataSet.Mode.LINEAR + else + LineDataSet.Mode.HORIZONTAL_BEZIER + ) + } + chart!!.invalidate() + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "MultiLineChartActivity") + } + + override fun onChartGestureStart(me: MotionEvent?, lastPerformedGesture: ChartGesture?) { + Log.i("Gesture", "START, x: " + me?.x + ", y: " + me?.y) + } + + override fun onChartGestureEnd(me: MotionEvent?, lastPerformedGesture: ChartGesture?) { + Log.i("Gesture", "END, lastGesture: $lastPerformedGesture") + + // un-highlight values after the gesture is finished and no single-tap + if (lastPerformedGesture != ChartGesture.SINGLE_TAP) chart!!.highlightValues(null) // or highlightTouch(null) for callback to onNothingSelected(...) + } + + override fun onChartLongPressed(me: MotionEvent?) { + Log.i("LongPress", "Chart long pressed.") + } + + override fun onChartDoubleTapped(me: MotionEvent?) { + Log.i("DoubleTap", "Chart double-tapped.") + } + + override fun onChartSingleTapped(me: MotionEvent?) { + Log.i("SingleTap", "Chart single-tapped.") + } + + override fun onChartFling(me1: MotionEvent?, me2: MotionEvent?, velocityX: Float, velocityY: Float) { + Log.i("Fling", "Chart fling. VelocityX: $velocityX, VelocityY: $velocityY") + } + + override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) { + Log.i("Scale / Zoom", "ScaleX: $scaleX, ScaleY: $scaleY") + } + + override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) { + Log.i("Translate / Move", "dX: $dX, dY: $dY") + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + Log.i( + "VAL SELECTED", + ("Value: " + e?.y + ", xIndex: " + e?.x + + ", DataSet index: " + h?.dataSetIndex) + ) + } + + override fun onNothingSelected() {} + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/PerformanceLineChart.java b/app/src/main/java/info/appdev/chartexample/PerformanceLineChart.java deleted file mode 100644 index 8569193f9b..0000000000 --- a/app/src/main/java/info/appdev/chartexample/PerformanceLineChart.java +++ /dev/null @@ -1,146 +0,0 @@ - -package info.appdev.chartexample; - -import android.content.Intent; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -@SuppressWarnings("SameParameterValue") -public class PerformanceLineChart extends DemoBase implements OnSeekBarChangeListener { - - private LineChart chart; - private SeekBar seekBarValues; - private TextView tvCount; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_performance_linechart); - - setTitle("PerformanceLineChart"); - - tvCount = findViewById(R.id.tvValueCount); - seekBarValues = findViewById(R.id.seekbarValues); - seekBarValues.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setDrawGridBackground(false); - - // no description text - chart.getDescription().setEnabled(false); - - // enable touch gestures - chart.setTouchEnabled(true); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - - // if disabled, scaling can be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.getAxisLeft().setDrawGridLines(false); - chart.getAxisRight().setEnabled(false); - chart.getXAxis().setDrawGridLines(true); - chart.getXAxis().setDrawAxisLine(false); - - seekBarValues.setProgress(9000); - - // don't forget to refresh the drawing - chart.invalidate(); - } - - private void setData(int count, float range) { - - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getMuchValues(count); - - for (int i = 0; i < count; i++) { - float val = (sampleValues[i].floatValue() * (range + 1)) + 3; - values.add(new Entry(i * 0.001f, val)); - } - - // create a dataset and give it a type - LineDataSet set1 = new LineDataSet(values, "DataSet 1"); - - set1.setColor(Color.BLACK); - set1.setLineWidth(0.5f); - set1.setDrawValues(false); - set1.setDrawCircles(false); - set1.setMode(LineDataSet.Mode.LINEAR); - set1.setDrawFilled(false); - - // create a data object with the data sets - LineData data = new LineData(set1); - - // set data - chart.setData(data); - - // get the legend (only possible after setting data) - Legend l = chart.getLegend(); - l.setEnabled(false); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.only_github, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/PerformanceLineChart.java")); - startActivity(i); - break; - } - } - - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - int count = seekBarValues.getProgress() + 1000; - tvCount.setText(String.valueOf(count)); - - chart.resetTracking(); - - setData(count, 500f); - - // redraw - chart.invalidate(); - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/PerformanceLineChart.kt b/app/src/main/java/info/appdev/chartexample/PerformanceLineChart.kt new file mode 100644 index 0000000000..0081b550da --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/PerformanceLineChart.kt @@ -0,0 +1,131 @@ +package info.appdev.chartexample + +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import info.appdev.chartexample.DataTools.Companion.getMuchValues +import info.appdev.chartexample.notimportant.DemoBase + +class PerformanceLineChart : DemoBase(), OnSeekBarChangeListener { + private var chart: LineChart? = null + private var seekBarValues: SeekBar? = null + private var tvCount: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_performance_linechart) + + setTitle("PerformanceLineChart") + + tvCount = findViewById(R.id.tvValueCount) + seekBarValues = findViewById(R.id.seekbarValues) + seekBarValues!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.drawGridBackground = false + + // no description text + chart!!.description.isEnabled = false + + // enable touch gestures + chart!!.setTouchEnabled(true) + + // enable scaling and dragging + chart!!.isDragEnabled = true + chart!!.setScaleEnabled(true) + + // if disabled, scaling can be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + chart!!.axisLeft.setDrawGridLines(false) + chart!!.axisRight.isEnabled = false + chart!!.xAxis.setDrawGridLines(true) + chart!!.xAxis.setDrawAxisLine(false) + + seekBarValues!!.progress = 9000 + + // don't forget to refresh the drawing + chart!!.invalidate() + } + + private fun setData(count: Int, range: Float) { + val values = ArrayList() + val sampleValues = getMuchValues(count) + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/PerformanceLineChart.java".toUri()) + startActivity(i) + } + } + + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + val count = seekBarValues!!.progress + 1000 + tvCount!!.text = count.toString() + + chart!!.resetTracking() + + setData(count, 500f) + + // redraw + chart!!.invalidate() + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/PieChartActivity.java b/app/src/main/java/info/appdev/chartexample/PieChartActivity.java deleted file mode 100644 index 05b7b83ee1..0000000000 --- a/app/src/main/java/info/appdev/chartexample/PieChartActivity.java +++ /dev/null @@ -1,322 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.text.style.RelativeSizeSpan; -import android.text.style.StyleSpan; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.animation.Easing; -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; -import com.github.mikephil.charting.formatter.PercentFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.MPPointF; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -public class PieChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private PieChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_piechart); - - setTitle("PieChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarY = findViewById(R.id.seekBarY); - - seekBarX.setOnSeekBarChangeListener(this); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setUsePercentValues(true); - chart.getDescription().setEnabled(false); - chart.setExtraOffsets(5, 10, 5, 5); - - chart.setDragDecelerationFrictionCoef(0.95f); - - chart.setCenterTextTypeface(tfLight); - chart.setCenterText(generateCenterSpannableText()); - - chart.setDrawHoleEnabled(true); - chart.setHoleColor(Color.WHITE); - - chart.setTransparentCircleColor(Color.WHITE); - chart.setTransparentCircleAlpha(110); - - chart.setHoleRadius(58f); - chart.setTransparentCircleRadius(61f); - - chart.setDrawCenterText(true); - - chart.setRotationAngle(0); - // enable rotation of the chart by touch - chart.setRotationEnabled(true); - chart.setHighlightPerTapEnabled(true); - - // chart.setUnit(" €"); - // chart.setDrawUnitsInChart(true); - - // add a selection listener - chart.setOnChartValueSelectedListener(this); - - seekBarX.setProgress(4); - seekBarY.setProgress(10); - - chart.animateY(1400, Easing.EaseInOutQuad); - // chart.spin(2000, 0, 360); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(false); - l.setXEntrySpace(7f); - l.setYEntrySpace(0f); - l.setYOffset(0f); - - // entry label styling - chart.setEntryLabelColor(Color.WHITE); - chart.setEntryLabelTypeface(tfRegular); - chart.setEntryLabelTextSize(12f); - } - - private void setData(int count, float range) { - ArrayList entries = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100); - - // NOTE: The order of the entries when being added to the entries array determines their position around the center of - // the chart. - for (int i = 0; i < count ; i++) { - entries.add(new PieEntry((sampleValues[i].floatValue() * range) + range / 5, - parties[i % parties.length], - getResources().getDrawable(R.drawable.star))); - } - - PieDataSet dataSet = new PieDataSet(entries, "Election Results"); - - dataSet.setDrawIcons(false); - - dataSet.setSliceSpace(3f); - dataSet.setIconsOffset(new MPPointF(0, 40)); - dataSet.setSelectionShift(5f); - - // add a lot of colors - - ArrayList colors = new ArrayList<>(); - - for (int c : ColorTemplate.VORDIPLOM_COLORS) - colors.add(c); - - for (int c : ColorTemplate.JOYFUL_COLORS) - colors.add(c); - - for (int c : ColorTemplate.COLORFUL_COLORS) - colors.add(c); - - for (int c : ColorTemplate.LIBERTY_COLORS) - colors.add(c); - - for (int c : ColorTemplate.PASTEL_COLORS) - colors.add(c); - - colors.add(ColorTemplate.getHoloBlue()); - - dataSet.setColors(colors); - //dataSet.setSelectionShift(0f); - - PieData data = new PieData(dataSet); - data.setValueFormatter(new PercentFormatter()); - data.setValueTextSize(11f); - data.setValueTextColor(Color.WHITE); - data.setValueTypeface(tfLight); - chart.setData(data); - - // undo all highlights - chart.highlightValues(null); - - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.pie, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawIcons(!set.isDrawIconsEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleHole: { - chart.setDrawHoleEnabled(!chart.isDrawHoleEnabled()); - chart.invalidate(); - break; - } - case R.id.actionToggleMinAngles: { - if (chart.getMinAngleForSlices() == 0f) - chart.setMinAngleForSlices(36f); - else - chart.setMinAngleForSlices(0f); - chart.notifyDataSetChanged(); - chart.invalidate(); - break; - } - case R.id.actionToggleCurvedSlices: { - boolean toSet = !chart.isDrawRoundedSlicesEnabled() || !chart.isDrawHoleEnabled(); - chart.setDrawRoundedSlices(toSet); - if (toSet && !chart.isDrawHoleEnabled()) { - chart.setDrawHoleEnabled(true); - } - if (toSet && chart.isDrawSlicesUnderHoleEnabled()) { - chart.setDrawSlicesUnderHole(false); - } - chart.invalidate(); - break; - } - case R.id.actionDrawCenter: { - chart.setDrawCenterText(!chart.isDrawCenterTextEnabled()); - chart.invalidate(); - break; - } - case R.id.actionToggleXValues: { - - chart.setDrawEntryLabels(!chart.isDrawEntryLabelsEnabled()); - chart.invalidate(); - break; - } - case R.id.actionTogglePercent: - chart.setUsePercentValues(!chart.isUsePercentValuesEnabled()); - chart.invalidate(); - break; - case R.id.animateX: { - chart.animateX(1400); - break; - } - case R.id.animateY: { - chart.animateY(1400); - break; - } - case R.id.animateXY: { - chart.animateXY(1400, 1400); - break; - } - case R.id.actionToggleSpin: { - chart.spin(1000, chart.getRotationAngle(), chart.getRotationAngle() + 360, Easing.EaseInOutCubic); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - setData(seekBarX.getProgress(), seekBarY.getProgress()); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "PieChartActivity"); - } - - private SpannableString generateCenterSpannableText() { - - SpannableString s = new SpannableString("MPAndroidChart\ndeveloped by Philipp Jahoda"); - s.setSpan(new RelativeSizeSpan(1.7f), 0, 14, 0); - s.setSpan(new StyleSpan(Typeface.NORMAL), 14, s.length() - 15, 0); - s.setSpan(new ForegroundColorSpan(Color.GRAY), 14, s.length() - 15, 0); - s.setSpan(new RelativeSizeSpan(.8f), 14, s.length() - 15, 0); - s.setSpan(new StyleSpan(Typeface.ITALIC), s.length() - 14, s.length(), 0); - s.setSpan(new ForegroundColorSpan(ColorTemplate.getHoloBlue()), s.length() - 14, s.length(), 0); - return s; - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - - if (e == null) - return; - Log.i("VAL SELECTED", - "Value: " + e.getY() + ", index: " + h.getX() - + ", DataSet index: " + h.getDataSetIndex()); - } - - @Override - public void onNothingSelected() { - Log.i("PieChart", "nothing selected"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/PieChartActivity.kt b/app/src/main/java/info/appdev/chartexample/PieChartActivity.kt new file mode 100644 index 0000000000..c519e8392d --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/PieChartActivity.kt @@ -0,0 +1,302 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.graphics.Typeface +import android.os.Bundle +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import android.text.style.StyleSpan +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.formatter.PercentFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.ColorTemplate.holoBlue +import com.github.mikephil.charting.utils.MPPointF +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class PieChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: PieChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_piechart) + + setTitle("PieChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarY = findViewById(R.id.seekBarY) + + seekBarX!!.setOnSeekBarChangeListener(this) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setUsePercentValues(true) + chart!!.description.isEnabled = false + chart!!.setExtraOffsets(5f, 10f, 5f, 5f) + + chart!!.dragDecelerationFrictionCoef = 0.95f + + chart!!.setCenterTextTypeface(tfLight) + chart!!.centerText = generateCenterSpannableText() + + chart!!.isDrawHoleEnabled = true + chart!!.setHoleColor(Color.WHITE) + + chart!!.setTransparentCircleColor(Color.WHITE) + chart!!.setTransparentCircleAlpha(110) + + chart!!.holeRadius = 58f + chart!!.transparentCircleRadius = 61f + + chart!!.setDrawCenterText(true) + + chart!!.rotationAngle = 0f + // enable rotation of the chart by touch + chart!!.isRotationEnabled = true + chart!!.isHighlightPerTapEnabled = true + + // chart.setUnit(" €"); + // chart.setDrawUnitsInChart(true); + + // add a selection listener + chart!!.setOnChartValueSelectedListener(this) + + seekBarX!!.progress = 4 + seekBarY!!.progress = 10 + + chart!!.animateY(1400, Easing.EaseInOutQuad) + + // chart.spin(2000, 0, 360); + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.VERTICAL + l.setDrawInside(false) + l.xEntrySpace = 7f + l.yEntrySpace = 0f + l.yOffset = 0f + + // entry label styling + chart!!.setEntryLabelColor(Color.WHITE) + chart!!.setEntryLabelTypeface(tfRegular) + chart!!.setEntryLabelTextSize(12f) + } + + private fun setData(count: Int, range: Float) { + val entries = ArrayList() + val sampleValues = getValues(100) + + // NOTE: The order of the entries when being added to the entries array determines their position around the center of + // the chart. + for (i in 0..() + + for (c in ColorTemplate.VORDIPLOM_COLORS) colors.add(c) + + for (c in ColorTemplate.JOYFUL_COLORS) colors.add(c) + + for (c in ColorTemplate.COLORFUL_COLORS) colors.add(c) + + for (c in ColorTemplate.LIBERTY_COLORS) colors.add(c) + + for (c in ColorTemplate.PASTEL_COLORS) colors.add(c) + + colors.add(holoBlue) + + dataSet.setColors(colors) + + //dataSet.setSelectionShift(0f); + val data = PieData(dataSet) + data.setValueFormatter(PercentFormatter()) + data.setValueTextSize(11f) + data.setValueTextColor(Color.WHITE) + data.setValueTypeface(tfLight) + chart!!.setData(data) + + // undo all highlights + chart!!.highlightValues(null) + + chart!!.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.pie, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + for (set in chart!!.data!!.dataSets) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + + chart!!.invalidate() + } + + R.id.actionToggleIcons -> { + for (set in chart!!.data!!.dataSets) set.isDrawIconsEnabled = !set.isDrawIconsEnabled + + chart!!.invalidate() + } + + R.id.actionToggleHole -> { + chart!!.isDrawHoleEnabled = !chart!!.isDrawHoleEnabled + chart!!.invalidate() + } + + R.id.actionToggleMinAngles -> { + if (chart!!.minAngleForSlices == 0f) chart!!.minAngleForSlices = 36f + else chart!!.minAngleForSlices = 0f + chart!!.notifyDataSetChanged() + chart!!.invalidate() + } + + R.id.actionToggleCurvedSlices -> { + val toSet = !chart!!.isDrawRoundedSlicesEnabled || !chart!!.isDrawHoleEnabled + chart!!.setDrawRoundedSlices(toSet) + if (toSet && !chart!!.isDrawHoleEnabled) { + chart!!.isDrawHoleEnabled = true + } + if (toSet && chart!!.isDrawSlicesUnderHoleEnabled) { + chart!!.setDrawSlicesUnderHole(false) + } + chart!!.invalidate() + } + + R.id.actionDrawCenter -> { + chart!!.setDrawCenterText(!chart!!.isDrawCenterTextEnabled) + chart!!.invalidate() + } + + R.id.actionToggleXValues -> { + chart!!.setDrawEntryLabels(!chart!!.isDrawEntryLabelsEnabled) + chart!!.invalidate() + } + + R.id.actionTogglePercent -> { + chart!!.setUsePercentValues(!chart!!.isUsePercentValuesEnabled) + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(1400) + } + + R.id.animateY -> { + chart!!.animateY(1400) + } + + R.id.animateXY -> { + chart!!.animateXY(1400, 1400) + } + + R.id.actionToggleSpin -> { + chart!!.spin(1000, chart!!.rotationAngle, chart!!.rotationAngle + 360, Easing.EaseInOutCubic) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + setData(seekBarX!!.progress, seekBarY!!.progress.toFloat()) + } + + override fun saveToGallery() { + saveToGallery(chart, "PieChartActivity") + } + + private fun generateCenterSpannableText(): SpannableString { + val s = SpannableString("MPAndroidChart\ndeveloped by Philipp Jahoda") + s.setSpan(RelativeSizeSpan(1.7f), 0, 14, 0) + s.setSpan(StyleSpan(Typeface.NORMAL), 14, s.length - 15, 0) + s.setSpan(ForegroundColorSpan(Color.GRAY), 14, s.length - 15, 0) + s.setSpan(RelativeSizeSpan(.8f), 14, s.length - 15, 0) + s.setSpan(StyleSpan(Typeface.ITALIC), s.length - 14, s.length, 0) + s.setSpan(ForegroundColorSpan(holoBlue), s.length - 14, s.length, 0) + return s + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null) return + Log.i( + "VAL SELECTED", + ("Value: " + e.y + ", index: " + h?.x + + ", DataSet index: " + h?.dataSetIndex) + ) + } + + override fun onNothingSelected() { + Log.i("PieChart", "nothing selected") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/PieChartRoundedActivity.java b/app/src/main/java/info/appdev/chartexample/PieChartRoundedActivity.java deleted file mode 100644 index 557bd9d590..0000000000 --- a/app/src/main/java/info/appdev/chartexample/PieChartRoundedActivity.java +++ /dev/null @@ -1,329 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.text.style.RelativeSizeSpan; -import android.text.style.StyleSpan; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.animation.Easing; -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; -import com.github.mikephil.charting.formatter.PercentFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.renderer.PieChartRenderer; -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.MPPointF; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -import androidx.core.content.ContextCompat; - -public class PieChartRoundedActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private PieChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_piechart); - - setTitle("PieChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarY = findViewById(R.id.seekBarY); - - seekBarX.setOnSeekBarChangeListener(this); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setUsePercentValues(true); - chart.getDescription().setEnabled(false); - chart.setExtraOffsets(5, 10, 5, 5); - - chart.setDragDecelerationFrictionCoef(0.95f); - - chart.setCenterTextTypeface(tfLight); - chart.setCenterText(generateCenterSpannableText()); - - chart.setDrawHoleEnabled(true); - chart.setHoleColor(Color.TRANSPARENT); - - chart.setTransparentCircleColor(Color.TRANSPARENT); - chart.setTransparentCircleAlpha(110); - - chart.setHoleRadius(50f); - - chart.setTransparentCircleRadius(0f); - - chart.setDrawCenterText(true); - - chart.setRotationAngle(0); - // enable rotation of the chart by touch - chart.setRotationEnabled(true); - chart.setHighlightPerTapEnabled(true); - - // chart.setUnit(" €"); - // chart.setDrawUnitsInChart(true); - - // add a selection listener - chart.setOnChartValueSelectedListener(this); - - seekBarX.setProgress(4); - seekBarY.setProgress(10); - - chart.animateY(1400, Easing.EaseInOutQuad); - // chart.spin(2000, 0, 360); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(false); - l.setXEntrySpace(7f); - l.setYEntrySpace(0f); - l.setYOffset(0f); - - // entry label styling - chart.setEntryLabelColor(Color.WHITE); - chart.setEntryLabelTypeface(tfRegular); - chart.setEntryLabelTextSize(12f); - } - - private void setData(int count, float range) { - ArrayList entries = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100); - - // NOTE: The order of the entries when being added to the entries array determines their position around the center of - // the chart. - for (int i = 0; i < count ; i++) { - entries.add(new PieEntry((sampleValues[i].floatValue() * range) + range / 5, - parties[i % parties.length], - getResources().getDrawable(R.drawable.star))); - } - - PieDataSet dataSet = new PieDataSet(entries, "Election Results"); - - dataSet.setDrawIcons(false); - - dataSet.setSliceSpace(3f); - dataSet.setIconsOffset(new MPPointF(0, 40)); - dataSet.setSelectionShift(5f); - - // add a lot of colors - - ArrayList colors = new ArrayList<>(); - - for (int c : ColorTemplate.VORDIPLOM_COLORS) - colors.add(c); - - for (int c : ColorTemplate.JOYFUL_COLORS) - colors.add(c); - - for (int c : ColorTemplate.COLORFUL_COLORS) - colors.add(c); - - for (int c : ColorTemplate.LIBERTY_COLORS) - colors.add(c); - - for (int c : ColorTemplate.PASTEL_COLORS) - colors.add(c); - - colors.add(ColorTemplate.getHoloBlue()); - - dataSet.setColors(colors); - //dataSet.setSelectionShift(0f); - - PieData data = new PieData(dataSet); - data.setValueFormatter(new PercentFormatter()); - data.setValueTextSize(11f); - data.setValueTextColor(Color.WHITE); - data.setValueTypeface(tfLight); - chart.setData(data); - - // undo all highlights - chart.highlightValues(null); - - PieChartRenderer renderer =(PieChartRenderer) chart.getRenderer(); - renderer.setRoundedCornerRadius(30f); - dataSet.setSliceSpace(renderer.getRoundedCornerRadius()/2); - - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.pie, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawIcons(!set.isDrawIconsEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleHole: { - chart.setDrawHoleEnabled(!chart.isDrawHoleEnabled()); - chart.invalidate(); - break; - } - case R.id.actionToggleMinAngles: { - if (chart.getMinAngleForSlices() == 0f) - chart.setMinAngleForSlices(36f); - else - chart.setMinAngleForSlices(0f); - chart.notifyDataSetChanged(); - chart.invalidate(); - break; - } - case R.id.actionToggleCurvedSlices: { - boolean toSet = !chart.isDrawRoundedSlicesEnabled() || !chart.isDrawHoleEnabled(); - chart.setDrawRoundedSlices(toSet); - if (toSet && !chart.isDrawHoleEnabled()) { - chart.setDrawHoleEnabled(true); - } - if (toSet && chart.isDrawSlicesUnderHoleEnabled()) { - chart.setDrawSlicesUnderHole(false); - } - chart.invalidate(); - break; - } - case R.id.actionDrawCenter: { - chart.setDrawCenterText(!chart.isDrawCenterTextEnabled()); - chart.invalidate(); - break; - } - case R.id.actionToggleXValues: { - - chart.setDrawEntryLabels(!chart.isDrawEntryLabelsEnabled()); - chart.invalidate(); - break; - } - case R.id.actionTogglePercent: - chart.setUsePercentValues(!chart.isUsePercentValuesEnabled()); - chart.invalidate(); - break; - case R.id.animateX: { - chart.animateX(1400); - break; - } - case R.id.animateY: { - chart.animateY(1400); - break; - } - case R.id.animateXY: { - chart.animateXY(1400, 1400); - break; - } - case R.id.actionToggleSpin: { - chart.spin(1000, chart.getRotationAngle(), chart.getRotationAngle() + 360, Easing.EaseInOutCubic); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - setData(seekBarX.getProgress(), seekBarY.getProgress()); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "PieChartActivity"); - } - - private SpannableString generateCenterSpannableText() { - - SpannableString s = new SpannableString("MPAndroidChart\ndeveloped by Philipp Jahoda"); - s.setSpan(new RelativeSizeSpan(1.7f), 0, 14, 0); - s.setSpan(new StyleSpan(Typeface.NORMAL), 14, s.length() - 15, 0); - s.setSpan(new ForegroundColorSpan(Color.GRAY), 14, s.length() - 15, 0); - s.setSpan(new RelativeSizeSpan(.8f), 14, s.length() - 15, 0); - s.setSpan(new StyleSpan(Typeface.ITALIC), s.length() - 14, s.length(), 0); - s.setSpan(new ForegroundColorSpan(ColorTemplate.getHoloBlue()), s.length() - 14, s.length(), 0); - return s; - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - - if (e == null) - return; - Log.i("VAL SELECTED", - "Value: " + e.getY() + ", index: " + h.getX() - + ", DataSet index: " + h.getDataSetIndex()); - } - - @Override - public void onNothingSelected() { - Log.i("PieChart", "nothing selected"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/PieChartRoundedActivity.kt b/app/src/main/java/info/appdev/chartexample/PieChartRoundedActivity.kt new file mode 100644 index 0000000000..0790a05e54 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/PieChartRoundedActivity.kt @@ -0,0 +1,308 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.graphics.Typeface +import android.os.Bundle +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import android.text.style.StyleSpan +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.formatter.PercentFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.renderer.PieChartRenderer +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.ColorTemplate.holoBlue +import com.github.mikephil.charting.utils.MPPointF +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class PieChartRoundedActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: PieChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_piechart) + + setTitle("PieChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarY = findViewById(R.id.seekBarY) + + seekBarX!!.setOnSeekBarChangeListener(this) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setUsePercentValues(true) + chart!!.description.isEnabled = false + chart!!.setExtraOffsets(5f, 10f, 5f, 5f) + + chart!!.dragDecelerationFrictionCoef = 0.95f + + chart!!.setCenterTextTypeface(tfLight) + chart!!.centerText = generateCenterSpannableText() + + chart!!.isDrawHoleEnabled = true + chart!!.setHoleColor(Color.TRANSPARENT) + + chart!!.setTransparentCircleColor(Color.TRANSPARENT) + chart!!.setTransparentCircleAlpha(110) + + chart!!.holeRadius = 50f + + chart!!.transparentCircleRadius = 0f + + chart!!.setDrawCenterText(true) + + chart!!.rotationAngle = 0f + // enable rotation of the chart by touch + chart!!.isRotationEnabled = true + chart!!.isHighlightPerTapEnabled = true + + // chart.setUnit(" €"); + // chart.setDrawUnitsInChart(true); + + // add a selection listener + chart!!.setOnChartValueSelectedListener(this) + + seekBarX!!.progress = 4 + seekBarY!!.progress = 10 + + chart!!.animateY(1400, Easing.EaseInOutQuad) + + // chart.spin(2000, 0, 360); + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.VERTICAL + l.setDrawInside(false) + l.xEntrySpace = 7f + l.yEntrySpace = 0f + l.yOffset = 0f + + // entry label styling + chart!!.setEntryLabelColor(Color.WHITE) + chart!!.setEntryLabelTypeface(tfRegular) + chart!!.setEntryLabelTextSize(12f) + } + + private fun setData(count: Int, range: Float) { + val entries = ArrayList() + val sampleValues = getValues(100) + + // NOTE: The order of the entries when being added to the entries array determines their position around the center of + // the chart. + for (i in 0..() + + for (c in ColorTemplate.VORDIPLOM_COLORS) colors.add(c) + + for (c in ColorTemplate.JOYFUL_COLORS) colors.add(c) + + for (c in ColorTemplate.COLORFUL_COLORS) colors.add(c) + + for (c in ColorTemplate.LIBERTY_COLORS) colors.add(c) + + for (c in ColorTemplate.PASTEL_COLORS) colors.add(c) + + colors.add(holoBlue) + + dataSet.setColors(colors) + + //dataSet.setSelectionShift(0f); + val data = PieData(dataSet) + data.setValueFormatter(PercentFormatter()) + data.setValueTextSize(11f) + data.setValueTextColor(Color.WHITE) + data.setValueTypeface(tfLight) + chart!!.setData(data) + + // undo all highlights + chart!!.highlightValues(null) + + val renderer = chart!!.renderer as PieChartRenderer? + renderer!!.roundedCornerRadius = 30f + dataSet.setSliceSpace(renderer.roundedCornerRadius / 2) + + chart!!.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.pie, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/PieChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + for (set in chart!!.data!!.dataSets) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + + chart!!.invalidate() + } + + R.id.actionToggleIcons -> { + for (set in chart!!.data!!.dataSets) set.isDrawIconsEnabled = !set.isDrawIconsEnabled + + chart!!.invalidate() + } + + R.id.actionToggleHole -> { + chart!!.isDrawHoleEnabled = !chart!!.isDrawHoleEnabled + chart!!.invalidate() + } + + R.id.actionToggleMinAngles -> { + if (chart!!.minAngleForSlices == 0f) chart!!.minAngleForSlices = 36f + else chart!!.minAngleForSlices = 0f + chart!!.notifyDataSetChanged() + chart!!.invalidate() + } + + R.id.actionToggleCurvedSlices -> { + val toSet = !chart!!.isDrawRoundedSlicesEnabled || !chart!!.isDrawHoleEnabled + chart!!.setDrawRoundedSlices(toSet) + if (toSet && !chart!!.isDrawHoleEnabled) { + chart!!.isDrawHoleEnabled = true + } + if (toSet && chart!!.isDrawSlicesUnderHoleEnabled) { + chart!!.setDrawSlicesUnderHole(false) + } + chart!!.invalidate() + } + + R.id.actionDrawCenter -> { + chart!!.setDrawCenterText(!chart!!.isDrawCenterTextEnabled) + chart!!.invalidate() + } + + R.id.actionToggleXValues -> { + chart!!.setDrawEntryLabels(!chart!!.isDrawEntryLabelsEnabled) + chart!!.invalidate() + } + + R.id.actionTogglePercent -> { + chart!!.setUsePercentValues(!chart!!.isUsePercentValuesEnabled) + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(1400) + } + + R.id.animateY -> { + chart!!.animateY(1400) + } + + R.id.animateXY -> { + chart!!.animateXY(1400, 1400) + } + + R.id.actionToggleSpin -> { + chart!!.spin(1000, chart!!.rotationAngle, chart!!.rotationAngle + 360, Easing.EaseInOutCubic) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + setData(seekBarX!!.progress, seekBarY!!.progress.toFloat()) + } + + override fun saveToGallery() { + saveToGallery(chart, "PieChartActivity") + } + + private fun generateCenterSpannableText(): SpannableString { + val s = SpannableString("MPAndroidChart\ndeveloped by Philipp Jahoda") + s.setSpan(RelativeSizeSpan(1.7f), 0, 14, 0) + s.setSpan(StyleSpan(Typeface.NORMAL), 14, s.length - 15, 0) + s.setSpan(ForegroundColorSpan(Color.GRAY), 14, s.length - 15, 0) + s.setSpan(RelativeSizeSpan(.8f), 14, s.length - 15, 0) + s.setSpan(StyleSpan(Typeface.ITALIC), s.length - 14, s.length, 0) + s.setSpan(ForegroundColorSpan(holoBlue), s.length - 14, s.length, 0) + return s + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null) return + Log.i( + "VAL SELECTED", + ("Value: " + e.y + ", index: " + h?.x + + ", DataSet index: " + h?.dataSetIndex) + ) + } + + override fun onNothingSelected() { + Log.i("PieChart", "nothing selected") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/PiePolylineChartActivity.java b/app/src/main/java/info/appdev/chartexample/PiePolylineChartActivity.java deleted file mode 100644 index b507ee6dc4..0000000000 --- a/app/src/main/java/info/appdev/chartexample/PiePolylineChartActivity.java +++ /dev/null @@ -1,315 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.graphics.Typeface; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.text.style.RelativeSizeSpan; -import android.text.style.StyleSpan; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.animation.Easing; -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; -import com.github.mikephil.charting.formatter.PercentFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -public class PiePolylineChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private PieChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - private Typeface tf; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_piechart); - - setTitle("PiePolylineChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarY = findViewById(R.id.seekBarY); - - seekBarX.setOnSeekBarChangeListener(this); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setUsePercentValues(true); - chart.getDescription().setEnabled(false); - chart.setExtraOffsets(5, 10, 5, 5); - - chart.setDragDecelerationFrictionCoef(0.95f); - - tf = Typeface.createFromAsset(getAssets(), "OpenSans-Regular.ttf"); - - chart.setCenterTextTypeface(Typeface.createFromAsset(getAssets(), "OpenSans-Light.ttf")); - chart.setCenterText(generateCenterSpannableText()); - - chart.setExtraOffsets(20.f, 0.f, 20.f, 0.f); - - chart.setDrawHoleEnabled(true); - chart.setHoleColor(Color.WHITE); - - chart.setTransparentCircleColor(Color.WHITE); - chart.setTransparentCircleAlpha(110); - - chart.setHoleRadius(58f); - chart.setTransparentCircleRadius(61f); - - chart.setDrawCenterText(true); - - chart.setRotationAngle(0); - // enable rotation of the chart by touch - chart.setRotationEnabled(true); - chart.setHighlightPerTapEnabled(true); - - // chart.setUnit(" €"); - // chart.setDrawUnitsInChart(true); - - // add a selection listener - chart.setOnChartValueSelectedListener(this); - - seekBarX.setProgress(4); - seekBarY.setProgress(100); - - chart.animateY(1400, Easing.EaseInOutQuad); - // chart.spin(2000, 0, 360); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(false); - l.setEnabled(false); - } - - private void setData(int count, float range) { - Double[] sampleValues = DataTools.Companion.getValues(count); - ArrayList entries = new ArrayList<>(); - - // NOTE: The order of the entries when being added to the entries array determines their position around the center of - // the chart. - for (int i = 0; i < count; i++) { - entries.add(new PieEntry((sampleValues[i].floatValue() * range) + range / 5, parties[i % parties.length])); - } - - PieDataSet dataSet = new PieDataSet(entries, "Election Results"); - dataSet.setSliceSpace(3f); - dataSet.setSelectionShift(5f); - - // add a lot of colors - - ArrayList colors = new ArrayList<>(); - - for (int c : ColorTemplate.VORDIPLOM_COLORS) - colors.add(c); - - for (int c : ColorTemplate.JOYFUL_COLORS) - colors.add(c); - - for (int c : ColorTemplate.COLORFUL_COLORS) - colors.add(c); - - for (int c : ColorTemplate.LIBERTY_COLORS) - colors.add(c); - - for (int c : ColorTemplate.PASTEL_COLORS) - colors.add(c); - - colors.add(ColorTemplate.getHoloBlue()); - - dataSet.setColors(colors); - //dataSet.setSelectionShift(0f); - - - dataSet.setValueLinePart1OffsetPercentage(80.f); - dataSet.setValueLinePart1Length(0.2f); - dataSet.setValueLinePart2Length(0.4f); - - //dataSet.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); - dataSet.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); - - PieData data = new PieData(dataSet); - data.setValueFormatter(new PercentFormatter()); - data.setValueTextSize(11f); - data.setValueTextColor(Color.BLACK); - data.setValueTypeface(tf); - chart.setData(data); - - // undo all highlights - chart.highlightValues(null); - - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.pie, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/PiePolylineChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleHole: { - chart.setDrawHoleEnabled(!chart.isDrawHoleEnabled()); - chart.invalidate(); - break; - } - case R.id.actionToggleMinAngles: { - if (chart.getMinAngleForSlices() == 0f) - chart.setMinAngleForSlices(36f); - else - chart.setMinAngleForSlices(0f); - chart.notifyDataSetChanged(); - chart.invalidate(); - break; - } - case R.id.actionToggleCurvedSlices: { - boolean toSet = !chart.isDrawRoundedSlicesEnabled() || !chart.isDrawHoleEnabled(); - chart.setDrawRoundedSlices(toSet); - if (toSet && !chart.isDrawHoleEnabled()) { - chart.setDrawHoleEnabled(true); - } - if (toSet && chart.isDrawSlicesUnderHoleEnabled()) { - chart.setDrawSlicesUnderHole(false); - } - chart.invalidate(); - break; - } - case R.id.actionDrawCenter: { - chart.setDrawCenterText(!chart.isDrawCenterTextEnabled()); - chart.invalidate(); - break; - } - case R.id.actionToggleXValues: { - - chart.setDrawEntryLabels(!chart.isDrawEntryLabelsEnabled()); - chart.invalidate(); - break; - } - case R.id.actionTogglePercent: - chart.setUsePercentValues(!chart.isUsePercentValuesEnabled()); - chart.invalidate(); - break; - case R.id.animateX: { - chart.animateX(1400); - break; - } - case R.id.animateY: { - chart.animateY(1400); - break; - } - case R.id.animateXY: { - chart.animateXY(1400, 1400); - break; - } - case R.id.actionToggleSpin: { - chart.spin(1000, chart.getRotationAngle(), chart.getRotationAngle() + 360, Easing.EaseInOutCubic); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - setData(seekBarX.getProgress(), seekBarY.getProgress()); - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "PiePolylineChartActivity"); - } - - private SpannableString generateCenterSpannableText() { - - SpannableString s = new SpannableString("MPAndroidChart\ndeveloped by Philipp Jahoda"); - s.setSpan(new RelativeSizeSpan(1.5f), 0, 14, 0); - s.setSpan(new StyleSpan(Typeface.NORMAL), 14, s.length() - 15, 0); - s.setSpan(new ForegroundColorSpan(Color.GRAY), 14, s.length() - 15, 0); - s.setSpan(new RelativeSizeSpan(.65f), 14, s.length() - 15, 0); - s.setSpan(new StyleSpan(Typeface.ITALIC), s.length() - 14, s.length(), 0); - s.setSpan(new ForegroundColorSpan(ColorTemplate.getHoloBlue()), s.length() - 14, s.length(), 0); - return s; - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - - if (e == null) - return; - Log.i("VAL SELECTED", - "Value: " + e.getY() + ", xIndex: " + e.getX() - + ", DataSet index: " + h.getDataSetIndex()); - } - - @Override - public void onNothingSelected() { - Log.i("PieChart", "nothing selected"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/PiePolylineChartActivity.kt b/app/src/main/java/info/appdev/chartexample/PiePolylineChartActivity.kt new file mode 100644 index 0000000000..90fedc0758 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/PiePolylineChartActivity.kt @@ -0,0 +1,291 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.graphics.Typeface +import android.os.Bundle +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import android.text.style.StyleSpan +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.formatter.PercentFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.ColorTemplate.holoBlue +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class PiePolylineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: PieChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + private var tf: Typeface? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_piechart) + + setTitle("PiePolylineChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarY = findViewById(R.id.seekBarY) + + seekBarX!!.setOnSeekBarChangeListener(this) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setUsePercentValues(true) + chart!!.description.isEnabled = false + chart!!.setExtraOffsets(5f, 10f, 5f, 5f) + + chart!!.dragDecelerationFrictionCoef = 0.95f + + tf = Typeface.createFromAsset(assets, "OpenSans-Regular.ttf") + + chart!!.setCenterTextTypeface(Typeface.createFromAsset(assets, "OpenSans-Light.ttf")) + chart!!.centerText = generateCenterSpannableText() + + chart!!.setExtraOffsets(20f, 0f, 20f, 0f) + + chart!!.isDrawHoleEnabled = true + chart!!.setHoleColor(Color.WHITE) + + chart!!.setTransparentCircleColor(Color.WHITE) + chart!!.setTransparentCircleAlpha(110) + + chart!!.holeRadius = 58f + chart!!.transparentCircleRadius = 61f + + chart!!.setDrawCenterText(true) + + chart!!.rotationAngle = 0f + // enable rotation of the chart by touch + chart!!.isRotationEnabled = true + chart!!.isHighlightPerTapEnabled = true + + // chart.setUnit(" €"); + // chart.setDrawUnitsInChart(true); + + // add a selection listener + chart!!.setOnChartValueSelectedListener(this) + + seekBarX!!.progress = 4 + seekBarY!!.progress = 100 + + chart!!.animateY(1400, Easing.EaseInOutQuad) + + // chart.spin(2000, 0, 360); + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.VERTICAL + l.setDrawInside(false) + l.isEnabled = false + } + + private fun setData(count: Int, range: Float) { + val sampleValues = getValues(count) + val entries = ArrayList() + + // NOTE: The order of the entries when being added to the entries array determines their position around the center of + // the chart. + for (i in 0..() + + for (c in ColorTemplate.VORDIPLOM_COLORS) colors.add(c) + + for (c in ColorTemplate.JOYFUL_COLORS) colors.add(c) + + for (c in ColorTemplate.COLORFUL_COLORS) colors.add(c) + + for (c in ColorTemplate.LIBERTY_COLORS) colors.add(c) + + for (c in ColorTemplate.PASTEL_COLORS) colors.add(c) + + colors.add(holoBlue) + + dataSet.setColors(colors) + + + //dataSet.setSelectionShift(0f); + dataSet.setValueLinePart1OffsetPercentage(80f) + dataSet.setValueLinePart1Length(0.2f) + dataSet.setValueLinePart2Length(0.4f) + + //dataSet.setXValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE); + dataSet.setYValuePosition(PieDataSet.ValuePosition.OUTSIDE_SLICE) + + val data = PieData(dataSet) + data.setValueFormatter(PercentFormatter()) + data.setValueTextSize(11f) + data.setValueTextColor(Color.BLACK) + data.setValueTypeface(tf) + chart!!.setData(data) + + // undo all highlights + chart!!.highlightValues(null) + + chart!!.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.pie, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/PiePolylineChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + for (set in chart!!.data!!.dataSets) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + + chart!!.invalidate() + } + + R.id.actionToggleHole -> { + chart!!.isDrawHoleEnabled = !chart!!.isDrawHoleEnabled + chart!!.invalidate() + } + + R.id.actionToggleMinAngles -> { + if (chart!!.minAngleForSlices == 0f) chart!!.minAngleForSlices = 36f + else chart!!.minAngleForSlices = 0f + chart!!.notifyDataSetChanged() + chart!!.invalidate() + } + + R.id.actionToggleCurvedSlices -> { + val toSet = !chart!!.isDrawRoundedSlicesEnabled || !chart!!.isDrawHoleEnabled + chart!!.setDrawRoundedSlices(toSet) + if (toSet && !chart!!.isDrawHoleEnabled) { + chart!!.isDrawHoleEnabled = true + } + if (toSet && chart!!.isDrawSlicesUnderHoleEnabled) { + chart!!.setDrawSlicesUnderHole(false) + } + chart!!.invalidate() + } + + R.id.actionDrawCenter -> { + chart!!.setDrawCenterText(!chart!!.isDrawCenterTextEnabled) + chart!!.invalidate() + } + + R.id.actionToggleXValues -> { + chart!!.setDrawEntryLabels(!chart!!.isDrawEntryLabelsEnabled) + chart!!.invalidate() + } + + R.id.actionTogglePercent -> { + chart!!.setUsePercentValues(!chart!!.isUsePercentValuesEnabled) + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(1400) + } + + R.id.animateY -> { + chart!!.animateY(1400) + } + + R.id.animateXY -> { + chart!!.animateXY(1400, 1400) + } + + R.id.actionToggleSpin -> { + chart!!.spin(1000, chart!!.rotationAngle, chart!!.rotationAngle + 360, Easing.EaseInOutCubic) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + setData(seekBarX!!.progress, seekBarY!!.progress.toFloat()) + } + + override fun saveToGallery() { + saveToGallery(chart, "PiePolylineChartActivity") + } + + private fun generateCenterSpannableText(): SpannableString { + val s = SpannableString("MPAndroidChart\ndeveloped by Philipp Jahoda") + s.setSpan(RelativeSizeSpan(1.5f), 0, 14, 0) + s.setSpan(StyleSpan(Typeface.NORMAL), 14, s.length - 15, 0) + s.setSpan(ForegroundColorSpan(Color.GRAY), 14, s.length - 15, 0) + s.setSpan(RelativeSizeSpan(.65f), 14, s.length - 15, 0) + s.setSpan(StyleSpan(Typeface.ITALIC), s.length - 14, s.length, 0) + s.setSpan(ForegroundColorSpan(holoBlue), s.length - 14, s.length, 0) + return s + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null) return + Log.i( + "VAL SELECTED", + ("Value: " + e.y + ", xIndex: " + e.x + + ", DataSet index: " + h?.dataSetIndex) + ) + } + + override fun onNothingSelected() { + Log.i("PieChart", "nothing selected") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/RadarChartActivity.java b/app/src/main/java/info/appdev/chartexample/RadarChartActivity.java deleted file mode 100644 index fa6c21c862..0000000000 --- a/app/src/main/java/info/appdev/chartexample/RadarChartActivity.java +++ /dev/null @@ -1,264 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; - -import com.github.mikephil.charting.animation.Easing; -import com.github.mikephil.charting.charts.RadarChart; -import com.github.mikephil.charting.components.AxisBase; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.MarkerView; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.RadarData; -import com.github.mikephil.charting.data.RadarDataSet; -import com.github.mikephil.charting.data.RadarEntry; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.interfaces.datasets.IDataSet; -import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet; - -import info.appdev.chartexample.custom.RadarMarkerView; -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -public class RadarChartActivity extends DemoBase { - - private RadarChart chart; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_radarchart); - - setTitle("RadarChartActivity"); - - chart = findViewById(R.id.chart1); - chart.setBackgroundColor(Color.rgb(60, 65, 82)); - - chart.getDescription().setEnabled(false); - - chart.setWebLineWidth(1f); - chart.setWebColor(Color.LTGRAY); - chart.setWebLineWidthInner(1f); - chart.setWebColorInner(Color.LTGRAY); - chart.setWebAlpha(100); - - // create a custom MarkerView (extend MarkerView) and specify the layout - // to use for it - MarkerView mv = new RadarMarkerView(this, R.layout.radar_markerview); - mv.setChartView(chart); // For bounds control - chart.setMarker(mv); // Set the marker to the chart - - setData(); - - chart.animateXY(1400, 1400, Easing.EaseInOutQuad); - - XAxis xAxis = chart.getXAxis(); - xAxis.setTypeface(tfLight); - xAxis.setTextSize(9f); - xAxis.setYOffset(0f); - xAxis.setXOffset(0f); - xAxis.setValueFormatter(new IAxisValueFormatter() { - - private final String[] mActivities = new String[]{"Burger", "Steak", "Salad", "Pasta", "Pizza"}; - - @Override - public String getFormattedValue(float value, AxisBase axis) { - return mActivities[(int) value % mActivities.length]; - } - }); - xAxis.setTextColor(Color.WHITE); - - YAxis yAxis = chart.getYAxis(); - yAxis.setTypeface(tfLight); - yAxis.setLabelCount(5, false); - yAxis.setTextSize(9f); - yAxis.setAxisMinimum(0f); - yAxis.setAxisMaximum(80f); - yAxis.setDrawLabels(false); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.CENTER); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - l.setTypeface(tfLight); - l.setXEntrySpace(7f); - l.setYEntrySpace(5f); - l.setTextColor(Color.WHITE); - } - - private void setData() { - - float mul = 80; - float min = 20; - int cnt = 5; - Double[] sampleValues = DataTools.Companion.getValues(cnt + 1); - - ArrayList entries1 = new ArrayList<>(); - ArrayList entries2 = new ArrayList<>(); - - // NOTE: The order of the entries when being added to the entries array determines their position around the center of - // the chart. - for (int i = 0; i < cnt; i++) { - float val1 = (sampleValues[i].floatValue() * mul) + min; - entries1.add(new RadarEntry(val1)); - - float val2 = (sampleValues[i + 1].floatValue() * mul) + min; - entries2.add(new RadarEntry(val2)); - } - - RadarDataSet set1 = new RadarDataSet(entries1, "Last Week"); - set1.setColor(Color.rgb(103, 110, 129)); - set1.setFillColor(Color.rgb(103, 110, 129)); - set1.setDrawFilled(true); - set1.setFillAlpha(180); - set1.setLineWidth(2f); - set1.setDrawHighlightCircleEnabled(true); - set1.setDrawHighlightIndicators(false); - - RadarDataSet set2 = new RadarDataSet(entries2, "This Week"); - set2.setColor(Color.rgb(121, 162, 175)); - set2.setFillColor(Color.rgb(121, 162, 175)); - set2.setDrawFilled(true); - set2.setFillAlpha(180); - set2.setLineWidth(2f); - set2.setDrawHighlightCircleEnabled(true); - set2.setDrawHighlightIndicators(false); - - ArrayList sets = new ArrayList<>(); - sets.add(set1); - sets.add(set2); - - RadarData data = new RadarData(sets); - data.setValueTypeface(tfLight); - data.setValueTextSize(8f); - data.setDrawValues(false); - data.setValueTextColor(Color.WHITE); - - chart.setData(data); - List colorList = new ArrayList<>(); - colorList.add(Color.rgb(222, 166, 111)); - colorList.add(Color.rgb(220, 206, 138)); - colorList.add(Color.rgb(243, 255, 192)); - colorList.add(Color.rgb(240, 255, 240)); - colorList.add(Color.rgb(250, 255, 250)); - chart.setLayerColorList(colorList); - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.radar, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/RadarChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - for (IDataSet set : chart.getData().getDataSets()) - set.setDrawValues(!set.isDrawValuesEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if (chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionToggleRotate: { - chart.setRotationEnabled(!chart.isRotationEnabled()); - chart.invalidate(); - break; - } - case R.id.actionToggleFilled: { - - ArrayList sets = (ArrayList) chart.getData() - .getDataSets(); - - for (IRadarDataSet set : sets) { - set.setDrawFilled(!set.isDrawFilledEnabled()); - } - chart.invalidate(); - break; - } - case R.id.actionToggleHighlightCircle: { - - ArrayList sets = (ArrayList) chart.getData() - .getDataSets(); - - for (IRadarDataSet set : sets) { - set.setDrawHighlightCircleEnabled(!set.isDrawHighlightCircleEnabled()); - } - chart.invalidate(); - break; - } - case R.id.actionToggleXLabels: { - chart.getXAxis().setEnabled(!chart.getXAxis().isEnabled()); - chart.notifyDataSetChanged(); - chart.invalidate(); - break; - } - case R.id.actionToggleYLabels: { - - chart.getYAxis().setEnabled(!chart.getYAxis().isEnabled()); - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(1400); - break; - } - case R.id.animateY: { - chart.animateY(1400); - break; - } - case R.id.animateXY: { - chart.animateXY(1400, 1400); - break; - } - case R.id.actionToggleSpin: { - chart.spin(2000, chart.getRotationAngle(), chart.getRotationAngle() + 360, Easing.EaseInOutCubic); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "RadarChartActivity"); - } -} diff --git a/app/src/main/java/info/appdev/chartexample/RadarChartActivity.kt b/app/src/main/java/info/appdev/chartexample/RadarChartActivity.kt new file mode 100644 index 0000000000..ae8fcb477c --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/RadarChartActivity.kt @@ -0,0 +1,244 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.animation.Easing +import com.github.mikephil.charting.charts.RadarChart +import com.github.mikephil.charting.components.AxisBase +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.RadarData +import com.github.mikephil.charting.data.RadarDataSet +import com.github.mikephil.charting.data.RadarEntry +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.interfaces.datasets.IRadarDataSet +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.custom.RadarMarkerView +import info.appdev.chartexample.notimportant.DemoBase + +class RadarChartActivity : DemoBase() { + private var chart: RadarChart? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_radarchart) + + setTitle("RadarChartActivity") + + chart = findViewById(R.id.chart1) + chart!!.setBackgroundColor(Color.rgb(60, 65, 82)) + + chart!!.description.isEnabled = false + + chart!!.webLineWidth = 1f + chart!!.webColor = Color.LTGRAY + chart!!.webLineWidthInner = 1f + chart!!.webColorInner = Color.LTGRAY + chart!!.webAlpha = 100 + + // create a custom MarkerView (extend MarkerView) and specify the layout + // to use for it + val mv: MarkerView = RadarMarkerView(this, R.layout.radar_markerview) + mv.chartView = chart // For bounds control + chart!!.setMarker(mv) // Set the marker to the chart + + setData() + + chart!!.animateXY(1400, 1400, Easing.EaseInOutQuad) + + val xAxis = chart!!.xAxis + xAxis.typeface = tfLight + xAxis.textSize = 9f + xAxis.yOffset = 0f + xAxis.xOffset = 0f + xAxis.valueFormatter = object : IAxisValueFormatter { + private val mActivities = arrayOf("Burger", "Steak", "Salad", "Pasta", "Pizza") + + override fun getFormattedValue(value: Float, axis: AxisBase?): String { + return mActivities[value.toInt() % mActivities.size] + } + } + xAxis.textColor = Color.WHITE + + val yAxis = chart!!.yAxis + yAxis.typeface = tfLight + yAxis.setLabelCount(5, false) + yAxis.textSize = 9f + yAxis.axisMinimum = 0f + yAxis.axisMaximum = 80f + yAxis.setDrawLabels(false) + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + l.typeface = tfLight + l.xEntrySpace = 7f + l.yEntrySpace = 5f + l.textColor = Color.WHITE + } + + private fun setData() { + val mul = 80f + val min = 20f + val cnt = 5 + val sampleValues = getValues(cnt + 1) + + val entries1 = ArrayList() + val entries2 = ArrayList() + + // NOTE: The order of the entries when being added to the entries array determines their position around the center of + // the chart. + for (i in 0..() + sets.add(set1) + sets.add(set2) + + val data = RadarData(sets) + data.setValueTypeface(tfLight) + data.setValueTextSize(8f) + data.setDrawValues(false) + data.setValueTextColor(Color.WHITE) + + chart!!.setData(data) + val colorList: MutableList = ArrayList() + colorList.add(Color.rgb(222, 166, 111)) + colorList.add(Color.rgb(220, 206, 138)) + colorList.add(Color.rgb(243, 255, 192)) + colorList.add(Color.rgb(240, 255, 240)) + colorList.add(Color.rgb(250, 255, 250)) + chart!!.layerColorList = colorList + chart!!.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.radar, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/RadarChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + for (set in chart!!.data!!.dataSets) set.isDrawValuesEnabled = !set.isDrawValuesEnabled + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionToggleRotate -> { + chart!!.isRotationEnabled = !chart!!.isRotationEnabled + chart!!.invalidate() + } + + R.id.actionToggleFilled -> { + val sets = chart!!.data!! + .dataSets as ArrayList + + for (set in sets) { + set.isDrawFilledEnabled = !set.isDrawFilledEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleHighlightCircle -> { + val sets = chart!!.data!! + .dataSets as ArrayList + + for (set in sets) { + set.isDrawHighlightCircleEnabled = !set.isDrawHighlightCircleEnabled + } + chart!!.invalidate() + } + + R.id.actionToggleXLabels -> { + chart!!.xAxis.isEnabled = !chart!!.xAxis.isEnabled + chart!!.notifyDataSetChanged() + chart!!.invalidate() + } + + R.id.actionToggleYLabels -> { + chart!!.yAxis.isEnabled = !chart!!.yAxis.isEnabled + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(1400) + } + + R.id.animateY -> { + chart!!.animateY(1400) + } + + R.id.animateXY -> { + chart!!.animateXY(1400, 1400) + } + + R.id.actionToggleSpin -> { + chart!!.spin(2000, chart!!.rotationAngle, chart!!.rotationAngle + 360, Easing.EaseInOutCubic) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "RadarChartActivity") + } +} diff --git a/app/src/main/java/info/appdev/chartexample/RealtimeLineChartActivity.java b/app/src/main/java/info/appdev/chartexample/RealtimeLineChartActivity.java deleted file mode 100644 index 179aa688dd..0000000000 --- a/app/src/main/java/info/appdev/chartexample/RealtimeLineChartActivity.java +++ /dev/null @@ -1,252 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.Toast; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.Legend.LegendForm; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.components.YAxis.AxisDependency; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -public class RealtimeLineChartActivity extends DemoBase implements - OnChartValueSelectedListener { - - private LineChart chart; - Double[] sampleValues = DataTools.Companion.getValues(102); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_realtime_linechart); - - setTitle("RealtimeLineChartActivity"); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - - // enable description text - chart.getDescription().setEnabled(true); - - // enable touch gestures - chart.setTouchEnabled(true); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - chart.setDrawGridBackground(false); - - // if disabled, scaling can be done on x- and y-axis separately - chart.setPinchZoom(true); - - // set an alternative background color - chart.setBackgroundColor(Color.LTGRAY); - - LineData data = new LineData(); - data.setValueTextColor(Color.WHITE); - - // add empty data - chart.setData(data); - - // get the legend (only possible after setting data) - Legend l = chart.getLegend(); - - // modify the legend ... - l.setForm(LegendForm.LINE); - l.setTypeface(tfLight); - l.setTextColor(Color.WHITE); - - XAxis xl = chart.getXAxis(); - xl.setTypeface(tfLight); - xl.setTextColor(Color.WHITE); - xl.setDrawGridLines(false); - xl.setAvoidFirstLastClipping(true); - xl.setEnabled(true); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tfLight); - leftAxis.setTextColor(Color.WHITE); - leftAxis.setAxisMaximum(100f); - leftAxis.setAxisMinimum(0f); - leftAxis.setDrawGridLines(true); - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setEnabled(false); - - } - - private void addEntry() { - - LineData data = chart.getData(); - - if (data != null) { - - ILineDataSet set = data.getDataSetByIndex(0); - // set.addEntry(...); // can be called as well - - if (set == null) { - set = createSet(); - data.addDataSet(set); - } - - int cycleValue = (int) (set.getEntryCount() % 100.0); - data.addEntry(new Entry(set.getEntryCount(), (sampleValues[cycleValue].floatValue() * 40) + 30f), 0); - data.notifyDataChanged(); - - // let the chart know it's data has changed - chart.notifyDataSetChanged(); - - // limit the number of visible entries - chart.setVisibleXRangeMaximum(120); - // chart.setVisibleYRange(30, AxisDependency.LEFT); - - // move to the latest entry - chart.moveViewToX(data.getEntryCount()); - - // this automatically refreshes the chart (calls invalidate()) - // chart.moveViewTo(data.getXValCount()-7, 55f, - // AxisDependency.LEFT); - } - } - - private LineDataSet createSet() { - - LineDataSet set = new LineDataSet(null, "Dynamic Data"); - set.setAxisDependency(AxisDependency.LEFT); - set.setColor(ColorTemplate.getHoloBlue()); - set.setCircleColor(Color.WHITE); - set.setLineWidth(2f); - set.setCircleRadius(4f); - set.setFillAlpha(65); - set.setFillColor(ColorTemplate.getHoloBlue()); - set.setHighLightColor(Color.rgb(244, 117, 117)); - set.setValueTextColor(Color.WHITE); - set.setValueTextSize(9f); - set.setDrawValues(false); - return set; - } - - private Thread thread; - - private void feedMultiple() { - - if (thread != null) - thread.interrupt(); - - final Runnable runnable = new Runnable() { - - @Override - public void run() { - addEntry(); - } - }; - - thread = new Thread(new Runnable() { - - @Override - public void run() { - for (int i = 0; i < 1000; i++) { - - // Don't generate garbage runnables inside the loop. - runOnUiThread(runnable); - - try { - Thread.sleep(25); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - }); - - thread.start(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.realtime, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/RealtimeLineChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionAdd: { - addEntry(); - break; - } - case R.id.actionClear: { - chart.clearValues(); - Toast.makeText(this, "Chart cleared!", Toast.LENGTH_SHORT).show(); - break; - } - case R.id.actionFeedMultiple: { - feedMultiple(); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "RealtimeLineChartActivity"); - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - Log.i("Entry selected", e.toString()); - } - - @Override - public void onNothingSelected() { - Log.i("Nothing selected", "Nothing selected."); - } - - @Override - protected void onPause() { - super.onPause(); - - if (thread != null) { - thread.interrupt(); - } - } -} diff --git a/app/src/main/java/info/appdev/chartexample/RealtimeLineChartActivity.kt b/app/src/main/java/info/appdev/chartexample/RealtimeLineChartActivity.kt new file mode 100644 index 0000000000..fa8e829f03 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/RealtimeLineChartActivity.kt @@ -0,0 +1,225 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.Legend.LegendForm +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate.holoBlue +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class RealtimeLineChartActivity : DemoBase(), OnChartValueSelectedListener { + private var chart: LineChart? = null + var sampleValues: Array = getValues(102) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_realtime_linechart) + + setTitle("RealtimeLineChartActivity") + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + + // enable description text + chart!!.description.isEnabled = true + + // enable touch gestures + chart!!.setTouchEnabled(true) + + // enable scaling and dragging + chart!!.isDragEnabled = true + chart!!.setScaleEnabled(true) + chart!!.drawGridBackground = false + + // if disabled, scaling can be done on x- and y-axis separately + chart!!.setPinchZoom(true) + + // set an alternative background color + chart!!.setBackgroundColor(Color.LTGRAY) + + val data = LineData() + data.setValueTextColor(Color.WHITE) + + // add empty data + chart!!.setData(data) + + // get the legend (only possible after setting data) + val l = chart!!.legend + + // modify the legend ... + l.form = LegendForm.LINE + l.typeface = tfLight + l.textColor = Color.WHITE + + val xl = chart!!.xAxis + xl.typeface = tfLight + xl.textColor = Color.WHITE + xl.setDrawGridLines(false) + xl.setAvoidFirstLastClipping(true) + xl.isEnabled = true + + val leftAxis = chart!!.axisLeft + leftAxis.typeface = tfLight + leftAxis.textColor = Color.WHITE + leftAxis.axisMaximum = 100f + leftAxis.axisMinimum = 0f + leftAxis.setDrawGridLines(true) + + val rightAxis = chart!!.axisRight + rightAxis.isEnabled = false + } + + private fun addEntry() { + val data: LineData? = chart!!.data + + if (data != null) { + var set: ILineDataSet + + // set.addEntry(...); // can be called as well + if (data.dataSetCount == 0) { + set = createSet() + data.addDataSet(set) + } else { + set = data.getDataSetByIndex(0) + } + + val cycleValue = (set.entryCount % 100.0) + data.addEntry(Entry(set.entryCount.toFloat(), (sampleValues[cycleValue.toInt()].toFloat() * 40) + 30f), 0) + data.notifyDataChanged() + + // let the chart know it's data has changed + chart!!.notifyDataSetChanged() + + // limit the number of visible entries + chart!!.setVisibleXRangeMaximum(120f) + + // chart.setVisibleYRange(30, AxisDependency.LEFT); + + // move to the latest entry + chart!!.moveViewToX(data.entryCount.toFloat()) + + // this automatically refreshes the chart (calls invalidate()) + // chart.moveViewTo(data.getXValCount()-7, 55f, + // AxisDependency.LEFT); + } + } + + private fun createSet(): LineDataSet { + val set = LineDataSet(mutableListOf(), "Dynamic Data") + set.axisDependency = AxisDependency.LEFT + set.setColor(holoBlue) + set.setCircleColor(Color.WHITE) + set.lineWidth = 2f + set.circleRadius = 4f + set.fillAlpha = 65 + set.fillColor = holoBlue + set.highLightColor = Color.rgb(244, 117, 117) + set.valueTextColor = Color.WHITE + set.valueTextSize = 9f + set.isDrawValuesEnabled = false + return set + } + + private var thread: Thread? = null + + private fun feedMultiple() { + if (thread != null) thread!!.interrupt() + + val runnable = Runnable { addEntry() } + + thread = Thread { + for (i in 0..999) { + // Don't generate garbage runnables inside the loop. + + runOnUiThread(runnable) + + try { + Thread.sleep(25) + } catch (e: InterruptedException) { + e.printStackTrace() + } + } + } + + thread!!.start() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.realtime, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/RealtimeLineChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionAdd -> { + addEntry() + } + + R.id.actionClear -> { + chart!!.clearValues() + Toast.makeText(this, "Chart cleared!", Toast.LENGTH_SHORT).show() + } + + R.id.actionFeedMultiple -> { + feedMultiple() + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "RealtimeLineChartActivity") + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + Log.i("Entry selected", e.toString()) + } + + override fun onNothingSelected() { + Log.i("Nothing selected", "Nothing selected.") + } + + override fun onPause() { + super.onPause() + + if (thread != null) { + thread!!.interrupt() + } + } +} diff --git a/app/src/main/java/info/appdev/chartexample/ScatterChartActivity.java b/app/src/main/java/info/appdev/chartexample/ScatterChartActivity.java deleted file mode 100644 index 0d12764537..0000000000 --- a/app/src/main/java/info/appdev/chartexample/ScatterChartActivity.java +++ /dev/null @@ -1,246 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.ScatterChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.ScatterData; -import com.github.mikephil.charting.data.ScatterDataSet; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.custom.CustomScatterShapeRenderer; -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -public class ScatterChartActivity extends DemoBase implements OnSeekBarChangeListener, - OnChartValueSelectedListener { - - private ScatterChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_scatterchart); - - setTitle("ScatterChartActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setOnSeekBarChangeListener(this); - - seekBarY = findViewById(R.id.seekBarY); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.getDescription().setEnabled(false); - chart.setOnChartValueSelectedListener(this); - - chart.setDrawGridBackground(false); - chart.setTouchEnabled(true); - chart.setMaxHighlightDistance(50f); - - // enable scaling and dragging - chart.setDragEnabled(true); - chart.setScaleEnabled(true); - - chart.setMaxVisibleValueCount(200); - chart.setPinchZoom(true); - - seekBarX.setProgress(45); - seekBarY.setProgress(100); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(false); - l.setTypeface(tfLight); - l.setXOffset(5f); - - YAxis yl = chart.getAxisLeft(); - yl.setTypeface(tfLight); - yl.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - chart.getAxisRight().setEnabled(false); - - XAxis xl = chart.getXAxis(); - xl.setTypeface(tfLight); - xl.setDrawGridLines(false); - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - ArrayList values1 = new ArrayList<>(); - ArrayList values2 = new ArrayList<>(); - ArrayList values3 = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100+2); - - for (int i = 0; i < seekBarX.getProgress(); i++) { - float val = (sampleValues[i].floatValue() * seekBarY.getProgress()) + 3; - values1.add(new Entry(i, val)); - } - - for (int i = 0; i < seekBarX.getProgress(); i++) { - float val = (sampleValues[i + 1].floatValue() * seekBarY.getProgress()) + 3; - values2.add(new Entry(i+0.33f, val)); - } - - for (int i = 0; i < seekBarX.getProgress(); i++) { - float val = (sampleValues[i + 2].floatValue() * seekBarY.getProgress()) + 3; - values3.add(new Entry(i+0.66f, val)); - } - - // create a dataset and give it a type - ScatterDataSet set1 = new ScatterDataSet(values1, "DS 1"); - set1.setScatterShape(ScatterChart.ScatterShape.SQUARE); - set1.setColor(ColorTemplate.COLORFUL_COLORS[0]); - ScatterDataSet set2 = new ScatterDataSet(values2, "DS 2"); - set2.setScatterShape(ScatterChart.ScatterShape.CIRCLE); - set2.setScatterShapeHoleColor(ColorTemplate.COLORFUL_COLORS[3]); - set2.setScatterShapeHoleRadius(3f); - set2.setColor(ColorTemplate.COLORFUL_COLORS[1]); - ScatterDataSet set3 = new ScatterDataSet(values3, "DS 3"); - set3.setShapeRenderer(new CustomScatterShapeRenderer()); - set3.setColor(ColorTemplate.COLORFUL_COLORS[2]); - - set1.setScatterShapeSize(8f); - set2.setScatterShapeSize(8f); - set3.setScatterShapeSize(8f); - - ArrayList dataSets = new ArrayList<>(); - dataSets.add(set1); // add the data sets - dataSets.add(set2); - dataSets.add(set3); - - // create a data object with the data sets - ScatterData data = new ScatterData(dataSets); - data.setValueTypeface(tfLight); - - chart.setData(data); - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.scatter, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/ScatterChartActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData() - .getDataSets(); - - for (IScatterDataSet iSet : sets) { - - ScatterDataSet set = (ScatterDataSet) iSet; - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if(chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - chart.setPinchZoom(!chart.isPinchZoomEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.animateX: { - chart.animateX(3000); - break; - } - case R.id.animateY: { - chart.animateY(3000); - break; - } - case R.id.animateXY: { - - chart.animateXY(3000, 3000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "ScatterChartActivity"); - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - Log.i("VAL SELECTED", - "Value: " + e.getY() + ", xIndex: " + e.getX() - + ", DataSet index: " + h.getDataSetIndex()); - } - - @Override - public void onNothingSelected() {} - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} -} diff --git a/app/src/main/java/info/appdev/chartexample/ScatterChartActivity.kt b/app/src/main/java/info/appdev/chartexample/ScatterChartActivity.kt new file mode 100644 index 0000000000..a687640958 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/ScatterChartActivity.kt @@ -0,0 +1,230 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.ScatterChart +import com.github.mikephil.charting.charts.ScatterChart.ScatterShape +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.ScatterData +import com.github.mikephil.charting.data.ScatterDataSet +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.custom.CustomScatterShapeRenderer +import info.appdev.chartexample.notimportant.DemoBase + +class ScatterChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: ScatterChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_scatterchart) + + setTitle("ScatterChartActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarX!!.setOnSeekBarChangeListener(this) + + seekBarY = findViewById(R.id.seekBarY) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.description.isEnabled = false + chart!!.setOnChartValueSelectedListener(this) + + chart!!.drawGridBackground = false + chart!!.setTouchEnabled(true) + chart!!.setMaxHighlightDistance(50f) + + // enable scaling and dragging + chart!!.isDragEnabled = true + chart!!.setScaleEnabled(true) + + chart!!.setMaxVisibleValueCount(200) + chart!!.setPinchZoom(true) + + seekBarX!!.progress = 45 + seekBarY!!.progress = 100 + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.VERTICAL + l.setDrawInside(false) + l.typeface = tfLight + l.xOffset = 5f + + val yl = chart!!.axisLeft + yl.typeface = tfLight + yl.axisMinimum = 0f // this replaces setStartAtZero(true) + + chart!!.axisRight.isEnabled = false + + val xl = chart!!.xAxis + xl.typeface = tfLight + xl.setDrawGridLines(false) + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + val values1 = ArrayList() + val values2 = ArrayList() + val values3 = ArrayList() + val sampleValues = getValues(100 + 2) + + for (i in 0..() + dataSets.add(set1) // add the data sets + dataSets.add(set2) + dataSets.add(set3) + + // create a data object with the data sets + val data = ScatterData(dataSets) + data.setValueTypeface(tfLight) + + chart!!.setData(data) + chart!!.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.scatter, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/ScatterChartActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as ScatterDataSet + set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionTogglePinch -> { + chart!!.setPinchZoom(!chart!!.isPinchZoomEnabled) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.animateX -> { + chart!!.animateX(3000) + } + + R.id.animateY -> { + chart!!.animateY(3000) + } + + R.id.animateXY -> { + chart!!.animateXY(3000, 3000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "ScatterChartActivity") + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + Log.i( + "VAL SELECTED", + ("Value: " + e?.y + ", xIndex: " + e?.x + + ", DataSet index: " + h?.dataSetIndex) + ) + } + + override fun onNothingSelected() {} + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} +} diff --git a/app/src/main/java/info/appdev/chartexample/ScrollViewActivity.java b/app/src/main/java/info/appdev/chartexample/ScrollViewActivity.java deleted file mode 100644 index 02263dbe51..0000000000 --- a/app/src/main/java/info/appdev/chartexample/ScrollViewActivity.java +++ /dev/null @@ -1,102 +0,0 @@ - -package info.appdev.chartexample; - -import android.content.Intent; -import android.net.Uri; -import android.os.Bundle; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; - -@SuppressWarnings("SameParameterValue") -public class ScrollViewActivity extends DemoBase { - - private BarChart chart; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_scrollview); - - setTitle("ScrollViewActivity"); - - chart = findViewById(R.id.chart1); - - chart.getDescription().setEnabled(false); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setDrawBarShadow(false); - chart.setDrawGridBackground(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTTOM); - xAxis.setDrawGridLines(false); - - chart.getAxisLeft().setDrawGridLines(false); - - chart.getLegend().setEnabled(false); - - setData(10); - chart.setFitBars(true); - } - - private void setData(int count) { - Double[] sampleValues = DataTools.Companion.getValues(count); - ArrayList values = new ArrayList<>(); - - for (int i = 0; i < count; i++) { - float val = (sampleValues[i].floatValue() * count) + 15; - values.add(new BarEntry(i, (int) val)); - } - - BarDataSet set = new BarDataSet(values, "Data Set"); - set.setColors(ColorTemplate.VORDIPLOM_COLORS); - set.setDrawValues(false); - - BarData data = new BarData(set); - - chart.setData(data); - chart.invalidate(); - chart.animateY(800); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.only_github, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/ScrollViewActivity.java")); - startActivity(i); - break; - } - } - - return true; - } - - @Override - public void saveToGallery() { /* Intentionally left empty */ } -} diff --git a/app/src/main/java/info/appdev/chartexample/ScrollViewActivity.kt b/app/src/main/java/info/appdev/chartexample/ScrollViewActivity.kt new file mode 100644 index 0000000000..18fb809e6d --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/ScrollViewActivity.kt @@ -0,0 +1,92 @@ +package info.appdev.chartexample + +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.notimportant.DemoBase + +class ScrollViewActivity : DemoBase() { + private var chart: BarChart? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_scrollview) + + setTitle("ScrollViewActivity") + + chart = findViewById(R.id.chart1) + + chart!!.description.isEnabled = false + + // scaling can now only be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + chart!!.isDrawBarShadowEnabled = false + chart!!.drawGridBackground = false + + val xAxis = chart!!.xAxis + xAxis.position = XAxisPosition.BOTTOM + xAxis.setDrawGridLines(false) + + chart!!.axisLeft.setDrawGridLines(false) + + chart!!.legend.isEnabled = false + + setData(10) + chart!!.setFitBars(true) + } + + private fun setData(count: Int) { + val sampleValues = getValues(count) + val values = ArrayList() + + for (i in 0.. { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/ScrollViewActivity.java".toUri()) + startActivity(i) + } + } + + return true + } + + public override fun saveToGallery() { /* Intentionally left empty */ + } +} diff --git a/app/src/main/java/info/appdev/chartexample/SpecificPositionsLineChartActivity.kt b/app/src/main/java/info/appdev/chartexample/SpecificPositionsLineChartActivity.kt index 6548e8ca2a..055f0812f3 100644 --- a/app/src/main/java/info/appdev/chartexample/SpecificPositionsLineChartActivity.kt +++ b/app/src/main/java/info/appdev/chartexample/SpecificPositionsLineChartActivity.kt @@ -57,7 +57,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, mChart = findViewById(R.id.chart1) as LineChart mChart!!.onChartGestureListener = this mChart!!.setOnChartValueSelectedListener(this) - mChart!!.setDrawGridBackground(false) + mChart!!.drawGridBackground = false // no description text mChart!!.description.isEnabled = false @@ -142,7 +142,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, R.id.actionToggleValues -> { mChart!!.data?.dataSets?.forEach { val set = it as LineDataSet - set.setDrawValues(!set.isDrawValuesEnabled) + set.isDrawValuesEnabled = !set.isDrawValuesEnabled } mChart!!.invalidate() } @@ -157,7 +157,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, R.id.actionToggleFilled -> { mChart!!.data?.dataSets?.forEach { val set = it as LineDataSet - set.setDrawFilled(!set.isDrawFilledEnabled) + set.isDrawFilledEnabled = !set.isDrawFilledEnabled } mChart!!.invalidate() } @@ -165,7 +165,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, R.id.actionToggleCircles -> { mChart!!.data?.dataSets?.forEach { val set = it as LineDataSet - set.setDrawCircles(!set.isDrawCirclesEnabled) + set.isDrawCirclesEnabled = !set.isDrawCirclesEnabled } mChart!!.invalidate() } @@ -173,7 +173,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, R.id.actionToggleCubic -> { mChart!!.data?.dataSets?.forEach { val set = it as LineDataSet - set.mode = if (set.mode == LineDataSet.Mode.CUBIC_BEZIER) LineDataSet.Mode.LINEAR else LineDataSet.Mode.CUBIC_BEZIER + set.mode = (if (set.mode == LineDataSet.Mode.CUBIC_BEZIER) LineDataSet.Mode.LINEAR else LineDataSet.Mode.CUBIC_BEZIER) } mChart!!.invalidate() } @@ -181,7 +181,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, R.id.actionToggleStepped -> { mChart!!.data?.dataSets?.forEach { val set = it as LineDataSet - set.mode = if (set.mode == LineDataSet.Mode.STEPPED) LineDataSet.Mode.LINEAR else LineDataSet.Mode.STEPPED + set.mode = (if (set.mode == LineDataSet.Mode.STEPPED) LineDataSet.Mode.LINEAR else LineDataSet.Mode.STEPPED) } mChart!!.invalidate() } @@ -189,7 +189,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, R.id.actionToggleHorizontalCubic -> { mChart!!.data?.dataSets?.forEach { val set = it as LineDataSet - set.mode = if (set.mode == LineDataSet.Mode.HORIZONTAL_BEZIER) LineDataSet.Mode.LINEAR else LineDataSet.Mode.HORIZONTAL_BEZIER + set.mode = (if (set.mode == LineDataSet.Mode.HORIZONTAL_BEZIER) LineDataSet.Mode.LINEAR else LineDataSet.Mode.HORIZONTAL_BEZIER) } mChart!!.invalidate() } @@ -242,7 +242,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, val values = ArrayList() val sampleValues = getValues(100) for (i in 0 until count) { - val `val` = (sampleValues[i]!!.toFloat() * range) + 3 + val `val` = (sampleValues[i].toFloat() * range) + 3 values.add(Entry(i.toFloat(), `val`)) } mChart!!.data?.let { @@ -265,17 +265,17 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, // set the line to be drawn like this "- - - - - -" set11.enableDashedLine(10f, 5f, 0f) set11.enableDashedHighlightLine(10f, 5f, 0f) - set11.color = Color.BLACK + set11.setColor(Color.BLACK) set11.setCircleColor(Color.BLACK) set11.lineWidth = 1f set11.circleRadius = 3f - set11.setDrawCircleHole(false) + set11.isDrawCircleHoleEnabled = false set11.valueTextSize = 9f - set11.setDrawFilled(true) + set11.isDrawFilledEnabled = true set11.formLineWidth = 1f - set11.setFormLineDashEffect(DashPathEffect(floatArrayOf(10f, 5f), 0f)) + set11.formLineDashEffect = DashPathEffect(floatArrayOf(10f, 5f), 0f) set11.formSize = 15f - if (Utils.getSDKInt() >= 18) { + if (Utils.sDKInt >= 18) { // fill drawable only supported on api level 18 and above val drawable = ContextCompat.getDrawable(this, R.drawable.fade_blue) set11.fillDrawable = drawable @@ -289,14 +289,14 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, val data = LineData(dataSets) // set data - mChart!!.data = data + mChart!!.setData(data) } - override fun onChartGestureStart(me: MotionEvent, lastPerformedGesture: ChartGesture) { - Log.i("Gesture", "START, x: " + me.x + ", y: " + me.y) + override fun onChartGestureStart(me: MotionEvent?, lastPerformedGesture: ChartGesture?) { + Log.i("Gesture", "START, x: " + me?.x + ", y: " + me?.y) } - override fun onChartGestureEnd(me: MotionEvent, lastPerformedGesture: ChartGesture) { + override fun onChartGestureEnd(me: MotionEvent?, lastPerformedGesture: ChartGesture?) { Log.i("Gesture", "END, lastGesture: $lastPerformedGesture") // un-highlight values after the gesture is finished and no single-tap @@ -305,31 +305,31 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, } } - override fun onChartLongPressed(me: MotionEvent) { + override fun onChartLongPressed(me: MotionEvent?) { Log.i("LongPress", "Chart longpressed.") } - override fun onChartDoubleTapped(me: MotionEvent) { + override fun onChartDoubleTapped(me: MotionEvent?) { Log.i("DoubleTap", "Chart double-tapped.") } - override fun onChartSingleTapped(me: MotionEvent) { + override fun onChartSingleTapped(me: MotionEvent?) { Log.i("SingleTap", "Chart single-tapped.") } - override fun onChartFling(me1: MotionEvent, me2: MotionEvent, velocityX: Float, velocityY: Float) { + override fun onChartFling(me1: MotionEvent?, me2: MotionEvent?, velocityX: Float, velocityY: Float) { Log.i("Fling", "Chart flinged. VeloX: $velocityX, VeloY: $velocityY") } - override fun onChartScale(me: MotionEvent, scaleX: Float, scaleY: Float) { + override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) { Log.i("Scale / Zoom", "ScaleX: $scaleX, ScaleY: $scaleY") } - override fun onChartTranslate(me: MotionEvent, dX: Float, dY: Float) { + override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) { Log.i("Translate / Move", "dX: $dX, dY: $dY") } - override fun onValueSelected(e: Entry, h: Highlight) { + override fun onValueSelected(e: Entry?, h: Highlight?) { Log.i("Entry selected", e.toString()) Log.i("LOWHIGH", "low: " + mChart!!.lowestVisibleX + ", high: " + mChart!!.highestVisibleX) Log.i("MIN MAX", "xmin: " + mChart!!.xChartMin + ", xmax: " + mChart!!.xChartMax + ", ymin: " + mChart!!.yChartMin + ", ymax: " + mChart!!.yChartMax) diff --git a/app/src/main/java/info/appdev/chartexample/StackedBarActivity.java b/app/src/main/java/info/appdev/chartexample/StackedBarActivity.java deleted file mode 100644 index b9fec35d61..0000000000 --- a/app/src/main/java/info/appdev/chartexample/StackedBarActivity.java +++ /dev/null @@ -1,282 +0,0 @@ -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; -import androidx.core.content.ContextCompat; -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; -import android.widget.SeekBar; -import android.widget.SeekBar.OnSeekBarChangeListener; -import android.widget.TextView; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.custom.MyAxisValueFormatter; -import info.appdev.chartexample.custom.MyValueFormatter; -import info.appdev.chartexample.notimportant.DemoBase; - -import java.util.ArrayList; -import java.util.List; - -public class StackedBarActivity extends DemoBase implements OnSeekBarChangeListener, OnChartValueSelectedListener { - - private BarChart chart; - private SeekBar seekBarX, seekBarY; - private TextView tvX, tvY; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_barchart); - - setTitle("StackedBarActivity"); - - tvX = findViewById(R.id.tvXMax); - tvY = findViewById(R.id.tvYMax); - - seekBarX = findViewById(R.id.seekBarX); - seekBarX.setOnSeekBarChangeListener(this); - - seekBarY = findViewById(R.id.seekBarY); - seekBarY.setOnSeekBarChangeListener(this); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - - chart.getDescription().setEnabled(false); - - // if more than 60 entries are displayed in the chart, no values will be - // drawn - chart.setMaxVisibleValueCount(40); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setDrawGridBackground(false); - chart.setDrawBarShadow(false); - - chart.setDrawValueAboveBar(false); - chart.setHighlightFullBarEnabled(false); - - // change the position of the y-labels - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setValueFormatter(new MyAxisValueFormatter()); - leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - chart.getAxisRight().setEnabled(false); - - XAxis xLabels = chart.getXAxis(); - xLabels.setPosition(XAxisPosition.TOP); - - // chart.setDrawXLabels(false); - // chart.setDrawYLabels(false); - - // setting data - seekBarX.setProgress(12); - seekBarY.setProgress(100); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - l.setFormSize(8f); - l.setFormToTextSpace(4f); - l.setXEntrySpace(6f); - - // chart.setDrawLegend(false); - } - - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - - tvX.setText(String.valueOf(seekBarX.getProgress())); - tvY.setText(String.valueOf(seekBarY.getProgress())); - - ArrayList values = new ArrayList<>(); - Double[] sampleValues = DataTools.Companion.getValues(100 + 2); - - for (int i = 0; i < seekBarX.getProgress(); i++) { - float mul = (seekBarY.getProgress() + 1); - float val1 = (sampleValues[i].floatValue() * mul) + mul / 3; - float val2 = (sampleValues[i + 1].floatValue() * mul) + mul / 3; - float val3 = (sampleValues[i + 2].floatValue() * mul) + mul / 3; - values.add(new BarEntry( - i, - new float[]{val1, val2, val3}, - getResources().getDrawable(R.drawable.star))); - } - - BarDataSet set1; - - if (chart.getData() != null && - chart.getData().getDataSetCount() > 0) { - set1 = (BarDataSet) chart.getData().getDataSetByIndex(0); - set1.setEntries(values); - chart.getData().notifyDataChanged(); - chart.notifyDataSetChanged(); - } else { - set1 = new BarDataSet(values, "Statistics Vienna 2014"); - set1.setDrawIcons(false); - set1.setColors(getColors()); - set1.setStackLabels(new String[]{"Births", "Divorces", "Marriages"}); - - ArrayList dataSets = new ArrayList<>(); - dataSets.add(set1); - - BarData data = new BarData(dataSets); - data.setValueFormatter(new MyValueFormatter()); - data.setValueTextColor(Color.WHITE); - - chart.setData(data); - } - - chart.setFitBars(true); - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bar, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/StackedBarActivity.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData() - .getDataSets(); - - for (IBarDataSet iSet : sets) { - - BarDataSet set = (BarDataSet) iSet; - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - List sets = chart.getData() - .getDataSets(); - - for (IBarDataSet iSet : sets) { - - BarDataSet set = (BarDataSet) iSet; - set.setDrawIcons(!set.isDrawIconsEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if (chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - chart.setPinchZoom(!chart.isPinchZoomEnabled()); - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleBarBorders: { - for (IBarDataSet set : chart.getData().getDataSets()) - ((BarDataSet) set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); - - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(2000); - break; - } - case R.id.animateY: { - chart.animateY(2000); - break; - } - case R.id.animateXY: { - - chart.animateXY(2000, 2000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "StackedBarActivity"); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) {} - - @Override - public void onStopTrackingTouch(SeekBar seekBar) {} - - @Override - public void onValueSelected(Entry e, Highlight h) { - - BarEntry entry = (BarEntry) e; - - if (entry.getYVals() != null) - Log.i("VAL SELECTED", "Value: " + entry.getYVals()[h.getStackIndex()]); - else - Log.i("VAL SELECTED", "Value: " + entry.getY()); - } - - @Override - public void onNothingSelected() {} - - private int[] getColors() { - - // have as many colors as stack-values per entry - int[] colors = new int[3]; - - System.arraycopy(ColorTemplate.MATERIAL_COLORS, 0, colors, 0, 3); - - return colors; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/StackedBarActivity.kt b/app/src/main/java/info/appdev/chartexample/StackedBarActivity.kt new file mode 100644 index 0000000000..c671602a90 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/StackedBarActivity.kt @@ -0,0 +1,266 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import android.widget.SeekBar +import android.widget.SeekBar.OnSeekBarChangeListener +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ColorTemplate +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.custom.MyAxisValueFormatter +import info.appdev.chartexample.custom.MyValueFormatter +import info.appdev.chartexample.notimportant.DemoBase +import androidx.core.net.toUri + +class StackedBarActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelectedListener { + private var chart: BarChart? = null + private var seekBarX: SeekBar? = null + private var seekBarY: SeekBar? = null + private var tvX: TextView? = null + private var tvY: TextView? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) + setContentView(R.layout.activity_barchart) + + setTitle("StackedBarActivity") + + tvX = findViewById(R.id.tvXMax) + tvY = findViewById(R.id.tvYMax) + + seekBarX = findViewById(R.id.seekBarX) + seekBarX!!.setOnSeekBarChangeListener(this) + + seekBarY = findViewById(R.id.seekBarY) + seekBarY!!.setOnSeekBarChangeListener(this) + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + + chart!!.description.isEnabled = false + + // if more than 60 entries are displayed in the chart, no values will be + // drawn + chart!!.setMaxVisibleValueCount(40) + + // scaling can now only be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + chart!!.drawGridBackground = false + chart!!.isDrawBarShadowEnabled = false + + chart!!.isDrawValueAboveBarEnabled = false + chart!!.isHighlightFullBarEnabled = false + + // change the position of the y-labels + val leftAxis = chart!!.axisLeft + leftAxis.valueFormatter = MyAxisValueFormatter() + leftAxis.axisMinimum = 0f // this replaces setStartAtZero(true) + chart!!.axisRight.isEnabled = false + + val xLabels = chart!!.xAxis + xLabels.position = XAxisPosition.TOP + + // chart.setDrawXLabels(false); + // chart.setDrawYLabels(false); + + // setting data + seekBarX!!.progress = 12 + seekBarY!!.progress = 100 + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + l.formSize = 8f + l.formToTextSpace = 4f + l.xEntrySpace = 6f + + // chart.setDrawLegend(false); + } + + override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) { + tvX!!.text = seekBarX!!.progress.toString() + tvY!!.text = seekBarY!!.progress.toString() + + val values = ArrayList() + val sampleValues = getValues(100 + 2) + + for (i in 0.. 0 + ) { + set1 = chart!!.data!!.getDataSetByIndex(0) as BarDataSet + set1.entries = values + chart!!.data!!.notifyDataChanged() + chart!!.notifyDataSetChanged() + } else { + set1 = BarDataSet(values, "Statistics Vienna 2014") + set1.isDrawIconsEnabled = false + set1.setColors(*this.colors) + set1.setStackLabels(arrayOf("Births", "Divorces", "Marriages")) + + val dataSets = ArrayList() + dataSets.add(set1) + + val data = BarData(dataSets) + data.setValueFormatter(MyValueFormatter()) + data.setValueTextColor(Color.WHITE) + + chart!!.setData(data) + } + + chart!!.setFitBars(true) + chart!!.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.bar, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/StackedBarActivity.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as BarDataSet + set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleIcons -> { + val sets: MutableList = chart!!.data!! + .dataSets + + for (iSet in sets) { + val set = iSet as BarDataSet + set.isDrawIconsEnabled = !set.isDrawIconsEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionTogglePinch -> { + chart!!.setPinchZoom(!chart!!.isPinchZoomEnabled) + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionToggleBarBorders -> { + for (set in chart!!.data!!.dataSets) (set as BarDataSet).setBarBorderWidth(if (set.barBorderWidth == 1f) 0f else 1f) + + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(2000) + } + + R.id.animateY -> { + chart!!.animateY(2000) + } + + R.id.animateXY -> { + chart!!.animateXY(2000, 2000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "StackedBarActivity") + } + + override fun onStartTrackingTouch(seekBar: SeekBar?) {} + + override fun onStopTrackingTouch(seekBar: SeekBar?) {} + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null || h == null) return + + val entry = e as BarEntry + + if (entry.yVals != null) Log.i("VAL SELECTED", "Value: " + entry.yVals!![h.stackIndex]) + else Log.i("VAL SELECTED", "Value: " + entry.y) + } + + override fun onNothingSelected() {} + + private val colors: IntArray + get() { + // have as many colors as stack-values per entry + + val colors = IntArray(3) + + System.arraycopy(ColorTemplate.MATERIAL_COLORS, 0, colors, 0, 3) + + return colors + } +} diff --git a/app/src/main/java/info/appdev/chartexample/StackedBarActivityNegative.java b/app/src/main/java/info/appdev/chartexample/StackedBarActivityNegative.java deleted file mode 100644 index 5c63f41495..0000000000 --- a/app/src/main/java/info/appdev/chartexample/StackedBarActivityNegative.java +++ /dev/null @@ -1,267 +0,0 @@ - -package info.appdev.chartexample; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.graphics.Color; -import android.net.Uri; -import android.os.Bundle; - -import androidx.core.content.ContextCompat; - -import android.util.Log; -import android.view.Menu; -import android.view.MenuItem; -import android.view.WindowManager; - -import com.github.mikephil.charting.charts.HorizontalBarChart; -import com.github.mikephil.charting.components.AxisBase; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.formatter.IValueFormatter; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.listener.OnChartValueSelectedListener; -import com.github.mikephil.charting.utils.ViewPortHandler; - -import info.appdev.chartexample.notimportant.DemoBase; - -import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.List; - -public class StackedBarActivityNegative extends DemoBase implements - OnChartValueSelectedListener { - - private HorizontalBarChart chart; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, - WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_age_distribution); - - setTitle("StackedBarActivityNegative"); - - chart = findViewById(R.id.chart1); - chart.setOnChartValueSelectedListener(this); - chart.setDrawGridBackground(false); - chart.getDescription().setEnabled(false); - - // scaling can now only be done on x- and y-axis separately - chart.setPinchZoom(false); - - chart.setDrawBarShadow(false); - chart.setDrawValueAboveBar(true); - chart.setHighlightFullBarEnabled(false); - - chart.getAxisLeft().setEnabled(false); - chart.getAxisRight().setAxisMaximum(25f); - chart.getAxisRight().setAxisMinimum(-25f); - chart.getAxisRight().setDrawGridLines(false); - chart.getAxisRight().setDrawZeroLine(true); - chart.getAxisRight().setLabelCount(7, false); - chart.getAxisRight().setValueFormatter(new CustomFormatter()); - chart.getAxisRight().setTextSize(9f); - - XAxis xAxis = chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTH_SIDED); - xAxis.setDrawGridLines(false); - xAxis.setDrawAxisLine(false); - xAxis.setTextSize(9f); - xAxis.setAxisMinimum(0f); - xAxis.setAxisMaximum(110f); - xAxis.setCenterAxisLabels(true); - xAxis.setLabelCount(12); - xAxis.setGranularity(10f); - xAxis.setValueFormatter(new IAxisValueFormatter() { - - private final DecimalFormat format = new DecimalFormat("###"); - - @Override - public String getFormattedValue(float value, AxisBase axis) { - return format.format(value) + "-" + format.format(value + 10); - } - }); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.BOTTOM); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.HORIZONTAL); - l.setDrawInside(false); - l.setFormSize(8f); - l.setFormToTextSpace(4f); - l.setXEntrySpace(6f); - - // IMPORTANT: When using negative values in stacked bars, always make sure the negative values are in the array first - ArrayList values = new ArrayList<>(); - values.add(new BarEntry(5, new float[]{-10, 10})); - values.add(new BarEntry(15, new float[]{-12, 13})); - values.add(new BarEntry(25, new float[]{-15, 15})); - values.add(new BarEntry(35, new float[]{-17, 17})); - values.add(new BarEntry(45, new float[]{-19, 20})); - values.add(new BarEntry(45, new float[]{-19, 20}, getResources().getDrawable(R.drawable.star))); - values.add(new BarEntry(55, new float[]{-19, 19})); - values.add(new BarEntry(65, new float[]{-16, 16})); - values.add(new BarEntry(75, new float[]{-13, 14})); - values.add(new BarEntry(85, new float[]{-10, 11})); - values.add(new BarEntry(95, new float[]{-5, 6})); - values.add(new BarEntry(105, new float[]{-1, 2})); - - BarDataSet set = new BarDataSet(values, "Age Distribution"); - set.setDrawIcons(false); - set.setValueFormatter(new CustomFormatter()); - set.setValueTextSize(7f); - set.setAxisDependency(YAxis.AxisDependency.RIGHT); - set.setColors(Color.rgb(67, 67, 72), Color.rgb(124, 181, 236)); - set.setStackLabels(new String[]{ - "Men", "Women" - }); - - BarData data = new BarData(set); - data.setBarWidth(8.5f); - chart.setData(data); - chart.invalidate(); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.bar, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - - switch (item.getItemId()) { - case R.id.viewGithub: { - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/StackedBarActivityNegative.java")); - startActivity(i); - break; - } - case R.id.actionToggleValues: { - List sets = chart.getData().getDataSets(); - - for (IBarDataSet iSet : sets) { - BarDataSet set = (BarDataSet) iSet; - set.setDrawValues(!set.isDrawValuesEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleIcons: { - List sets = chart.getData().getDataSets(); - - for (IBarDataSet iSet : sets) { - - BarDataSet set = (BarDataSet) iSet; - set.setDrawIcons(!set.isDrawIconsEnabled()); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleHighlight: { - if (chart.getData() != null) { - chart.getData().setHighlightEnabled(!chart.getData().isHighlightEnabled()); - chart.invalidate(); - } - break; - } - case R.id.actionTogglePinch: { - if (chart.isPinchZoomEnabled()) { - chart.setPinchZoom(false); - } else { - chart.setPinchZoom(true); - } - - chart.invalidate(); - break; - } - case R.id.actionToggleAutoScaleMinMax: { - chart.setAutoScaleMinMaxEnabled(!chart.isAutoScaleMinMaxEnabled()); - chart.notifyDataSetChanged(); - break; - } - case R.id.actionToggleBarBorders: { - for (IBarDataSet set : chart.getData().getDataSets()) { - ((BarDataSet) set).setBarBorderWidth(set.getBarBorderWidth() == 1.f ? 0.f : 1.f); - } - - chart.invalidate(); - break; - } - case R.id.animateX: { - chart.animateX(3000); - break; - } - case R.id.animateY: { - chart.animateY(3000); - break; - } - case R.id.animateXY: { - - chart.animateXY(3000, 3000); - break; - } - case R.id.actionSave: { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { - saveToGallery(); - } else { - requestStoragePermission(chart); - } - break; - } - } - return true; - } - - @Override - protected void saveToGallery() { - saveToGallery(chart, "StackedBarActivityNegative"); - } - - @Override - public void onValueSelected(Entry e, Highlight h) { - BarEntry entry = (BarEntry) e; - Log.i("VAL SELECTED", - "Value: " + Math.abs(entry.getYVals()[h.getStackIndex()])); - } - - @Override - public void onNothingSelected() { - Log.i("NOTING SELECTED", ""); - } - - private class CustomFormatter implements IValueFormatter, IAxisValueFormatter { - - private final DecimalFormat mFormat; - - CustomFormatter() { - mFormat = new DecimalFormat("###"); - } - - // data - @Override - public String getFormattedValue(float value, Entry entry, int dataSetIndex, ViewPortHandler viewPortHandler) { - return mFormat.format(Math.abs(value)) + "m"; - } - - // YAxis - @Override - public String getFormattedValue(float value, AxisBase axis) { - return mFormat.format(Math.abs(value)) + "m"; - } - } -} diff --git a/app/src/main/java/info/appdev/chartexample/StackedBarActivityNegative.kt b/app/src/main/java/info/appdev/chartexample/StackedBarActivityNegative.kt new file mode 100644 index 0000000000..2516b44616 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/StackedBarActivityNegative.kt @@ -0,0 +1,247 @@ +package info.appdev.chartexample + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.graphics.Color +import android.os.Bundle +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.WindowManager +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.net.toUri +import com.github.mikephil.charting.charts.HorizontalBarChart +import com.github.mikephil.charting.components.AxisBase +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.components.YAxis.AxisDependency +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.formatter.IValueFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.listener.OnChartValueSelectedListener +import com.github.mikephil.charting.utils.ViewPortHandler +import info.appdev.chartexample.notimportant.DemoBase +import java.text.DecimalFormat +import kotlin.math.abs + +class StackedBarActivityNegative : DemoBase(), OnChartValueSelectedListener { + private var chart: HorizontalBarChart? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + window.setFlags( + WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN + ) + setContentView(R.layout.activity_age_distribution) + + setTitle("StackedBarActivityNegative") + + chart = findViewById(R.id.chart1) + chart!!.setOnChartValueSelectedListener(this) + chart!!.drawGridBackground = false + chart!!.description.isEnabled = false + + // scaling can now only be done on x- and y-axis separately + chart!!.setPinchZoom(false) + + chart!!.isDrawBarShadowEnabled = false + chart!!.isDrawValueAboveBarEnabled = true + chart!!.isHighlightFullBarEnabled = false + + chart!!.axisLeft.isEnabled = false + chart!!.axisRight.axisMaximum = 25f + chart!!.axisRight.axisMinimum = -25f + chart!!.axisRight.setDrawGridLines(false) + chart!!.axisRight.setDrawZeroLine(true) + chart!!.axisRight.setLabelCount(7, false) + chart!!.axisRight.valueFormatter = CustomFormatter() + chart!!.axisRight.textSize = 9f + + val xAxis = chart!!.xAxis + xAxis.position = XAxisPosition.BOTH_SIDED + xAxis.setDrawGridLines(false) + xAxis.setDrawAxisLine(false) + xAxis.textSize = 9f + xAxis.axisMinimum = 0f + xAxis.axisMaximum = 110f + xAxis.setCenterAxisLabels(true) + xAxis.labelCount = 12 + xAxis.granularity = 10f + xAxis.valueFormatter = object : IAxisValueFormatter { + private val format = DecimalFormat("###") + + override fun getFormattedValue(value: Float, axis: AxisBase?): String { + return format.format(value.toDouble()) + "-" + format.format((value + 10).toDouble()) + } + } + + val l = chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.HORIZONTAL + l.setDrawInside(false) + l.formSize = 8f + l.formToTextSpace = 4f + l.xEntrySpace = 6f + + // IMPORTANT: When using negative values in stacked bars, always make sure the negative values are in the array first + val values = ArrayList() + values.add(BarEntry(5f, floatArrayOf(-10f, 10f))) + values.add(BarEntry(15f, floatArrayOf(-12f, 13f))) + values.add(BarEntry(25f, floatArrayOf(-15f, 15f))) + values.add(BarEntry(35f, floatArrayOf(-17f, 17f))) + values.add(BarEntry(45f, floatArrayOf(-19f, 20f))) + values.add(BarEntry(45f, floatArrayOf(-19f, 20f), ResourcesCompat.getDrawable(resources, R.drawable.star, theme))) + values.add(BarEntry(55f, floatArrayOf(-19f, 19f))) + values.add(BarEntry(65f, floatArrayOf(-16f, 16f))) + values.add(BarEntry(75f, floatArrayOf(-13f, 14f))) + values.add(BarEntry(85f, floatArrayOf(-10f, 11f))) + values.add(BarEntry(95f, floatArrayOf(-5f, 6f))) + values.add(BarEntry(105f, floatArrayOf(-1f, 2f))) + + val set = BarDataSet(values, "Age Distribution") + set.isDrawIconsEnabled = false + set.valueFormatter = CustomFormatter() + set.valueTextSize = 7f + set.axisDependency = AxisDependency.RIGHT + set.setColors(Color.rgb(67, 67, 72), Color.rgb(124, 181, 236)) + set.setStackLabels( + arrayOf( + "Men", "Women" + ) + ) + + val data = BarData(set) + data.barWidth = 8.5f + chart!!.setData(data) + chart!!.invalidate() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.bar, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.viewGithub -> { + val i = Intent(Intent.ACTION_VIEW) + i.setData("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/StackedBarActivityNegative.java".toUri()) + startActivity(i) + } + + R.id.actionToggleValues -> { + val sets: MutableList = chart!!.data!!.dataSets + + for (iSet in sets) { + val set = iSet as BarDataSet + set.isDrawValuesEnabled = !set.isDrawValuesEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleIcons -> { + val sets: MutableList = chart!!.data!!.dataSets + + for (iSet in sets) { + val set = iSet as BarDataSet + set.isDrawIconsEnabled = !set.isDrawIconsEnabled + } + + chart!!.invalidate() + } + + R.id.actionToggleHighlight -> { + if (chart!!.data != null) { + chart!!.data!!.isHighlightEnabled = !chart!!.data!!.isHighlightEnabled + chart!!.invalidate() + } + } + + R.id.actionTogglePinch -> { + if (chart!!.isPinchZoomEnabled) { + chart!!.setPinchZoom(false) + } else { + chart!!.setPinchZoom(true) + } + + chart!!.invalidate() + } + + R.id.actionToggleAutoScaleMinMax -> { + chart!!.isAutoScaleMinMaxEnabled = !chart!!.isAutoScaleMinMaxEnabled + chart!!.notifyDataSetChanged() + } + + R.id.actionToggleBarBorders -> { + for (set in chart!!.data!!.dataSets) { + (set as BarDataSet).setBarBorderWidth(if (set.barBorderWidth == 1f) 0f else 1f) + } + + chart!!.invalidate() + } + + R.id.animateX -> { + chart!!.animateX(3000) + } + + R.id.animateY -> { + chart!!.animateY(3000) + } + + R.id.animateXY -> { + chart!!.animateXY(3000, 3000) + } + + R.id.actionSave -> { + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) { + saveToGallery() + } else { + requestStoragePermission(chart) + } + } + } + return true + } + + override fun saveToGallery() { + saveToGallery(chart, "StackedBarActivityNegative") + } + + override fun onValueSelected(e: Entry?, h: Highlight?) { + if (e == null || h == null) return + + val entry = e as BarEntry + Log.i( + "VAL SELECTED", + "Value: " + abs(entry.yVals!![h.stackIndex]) + ) + } + + override fun onNothingSelected() { + Log.i("NOTING SELECTED", "") + } + + private inner class CustomFormatter : IValueFormatter, IAxisValueFormatter { + private val mFormat = DecimalFormat("###") + + // data + override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + return mFormat.format(abs(value).toDouble()) + "m" + } + + // YAxis + override fun getFormattedValue(value: Float, axis: AxisBase?): String { + return mFormat.format(abs(value).toDouble()) + "m" + } + } +} diff --git a/app/src/main/java/info/appdev/chartexample/custom/CustomScatterShapeRenderer.java b/app/src/main/java/info/appdev/chartexample/custom/CustomScatterShapeRenderer.java deleted file mode 100644 index 873f438632..0000000000 --- a/app/src/main/java/info/appdev/chartexample/custom/CustomScatterShapeRenderer.java +++ /dev/null @@ -1,31 +0,0 @@ -package info.appdev.chartexample.custom; - -import android.graphics.Canvas; -import android.graphics.Paint; - -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.renderer.scatter.IShapeRenderer; -import com.github.mikephil.charting.utils.Utils; -import com.github.mikephil.charting.utils.ViewPortHandler; - -/** - * Custom shape renderer that draws a single line. - * Created by philipp on 26/06/16. - */ -public class CustomScatterShapeRenderer implements IShapeRenderer -{ - - @Override - public void renderShape(Canvas c, IScatterDataSet dataSet, ViewPortHandler viewPortHandler, - float posX, float posY, Paint renderPaint) { - - final float shapeHalf = Utils.convertDpToPixel(dataSet.getScatterShapeSize()) / 2f; - - c.drawLine( - posX - shapeHalf, - posY - shapeHalf, - posX + shapeHalf, - posY + shapeHalf, - renderPaint); - } -} diff --git a/app/src/main/java/info/appdev/chartexample/custom/CustomScatterShapeRenderer.kt b/app/src/main/java/info/appdev/chartexample/custom/CustomScatterShapeRenderer.kt new file mode 100644 index 0000000000..f4a8dd3415 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/custom/CustomScatterShapeRenderer.kt @@ -0,0 +1,29 @@ +package info.appdev.chartexample.custom + +import android.graphics.Canvas +import android.graphics.Paint +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.renderer.scatter.IShapeRenderer +import com.github.mikephil.charting.utils.Utils.convertDpToPixel +import com.github.mikephil.charting.utils.ViewPortHandler + +/** + * Custom shape renderer that draws a single line. + * Created by philipp on 26/06/16. + */ +class CustomScatterShapeRenderer : IShapeRenderer { + override fun renderShape( + c: Canvas?, dataSet: IScatterDataSet, viewPortHandler: ViewPortHandler, + posX: Float, posY: Float, renderPaint: Paint + ) { + val shapeHalf = convertDpToPixel(dataSet.scatterShapeSize) / 2f + + c?.drawLine( + posX - shapeHalf, + posY - shapeHalf, + posX + shapeHalf, + posY + shapeHalf, + renderPaint + ) + } +} diff --git a/app/src/main/java/info/appdev/chartexample/custom/DayAxisValueFormatter.kt b/app/src/main/java/info/appdev/chartexample/custom/DayAxisValueFormatter.kt index 3dd38d72ee..abf3bbefb7 100644 --- a/app/src/main/java/info/appdev/chartexample/custom/DayAxisValueFormatter.kt +++ b/app/src/main/java/info/appdev/chartexample/custom/DayAxisValueFormatter.kt @@ -5,7 +5,7 @@ import com.github.mikephil.charting.components.AxisBase import com.github.mikephil.charting.formatter.IAxisValueFormatter import kotlin.math.max -class DayAxisValueFormatter(private val chart: BarLineChartBase<*>) : IAxisValueFormatter { +class DayAxisValueFormatter(private val chart: BarLineChartBase<*, *, *>) : IAxisValueFormatter { private val months = arrayOf( "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ) diff --git a/app/src/main/java/info/appdev/chartexample/custom/MyFillFormatter.kt b/app/src/main/java/info/appdev/chartexample/custom/MyFillFormatter.kt index a63f824cb5..4b6db9f627 100644 --- a/app/src/main/java/info/appdev/chartexample/custom/MyFillFormatter.kt +++ b/app/src/main/java/info/appdev/chartexample/custom/MyFillFormatter.kt @@ -6,7 +6,7 @@ import com.github.mikephil.charting.interfaces.datasets.ILineDataSet @Suppress("unused") class MyFillFormatter(private val fillPos: Float) : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider?): Float { + override fun getFillLinePosition(dataSet: ILineDataSet, dataProvider: LineDataProvider): Float { // your logic could be here return fillPos } diff --git a/app/src/main/java/info/appdev/chartexample/custom/MyMarkerView.java b/app/src/main/java/info/appdev/chartexample/custom/MyMarkerView.java deleted file mode 100644 index 5161c53793..0000000000 --- a/app/src/main/java/info/appdev/chartexample/custom/MyMarkerView.java +++ /dev/null @@ -1,52 +0,0 @@ - -package info.appdev.chartexample.custom; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.widget.TextView; - -import com.github.mikephil.charting.components.MarkerView; -import com.github.mikephil.charting.data.CandleEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.Utils; - -import info.appdev.chartexample.R; - -/** - * Custom implementation of the MarkerView. - * - * @author Philipp Jahoda - */ -@SuppressLint("ViewConstructor") -public class MyMarkerView extends MarkerView { - - private final TextView tvContent; - - public MyMarkerView(Context context, int layoutResource) { - super(context, layoutResource); - - tvContent = findViewById(R.id.tvContent); - } - - // runs every time the MarkerView is redrawn, can be used to update the - // content (user-interface) - @Override - public void refreshContent(Entry e, Highlight highlight) { - - if (e instanceof CandleEntry ce) { - tvContent.setText(Utils.formatNumber(ce.getHigh(), 0, true)); - } else { - - tvContent.setText(Utils.formatNumber(e.getY(), 0, true)); - } - - super.refreshContent(e, highlight); - } - - @Override - public MPPointF getOffset() { - return new MPPointF(-(getWidth() / 2), -getHeight()); - } -} diff --git a/app/src/main/java/info/appdev/chartexample/custom/MyMarkerView.kt b/app/src/main/java/info/appdev/chartexample/custom/MyMarkerView.kt new file mode 100644 index 0000000000..04a2e693cd --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/custom/MyMarkerView.kt @@ -0,0 +1,37 @@ +package info.appdev.chartexample.custom + +import android.annotation.SuppressLint +import android.content.Context +import android.widget.TextView +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.CandleEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Utils.formatNumber +import info.appdev.chartexample.R + +/** + * Custom implementation of the MarkerView. + * + * @author Philipp Jahoda + */ +@SuppressLint("ViewConstructor") +class MyMarkerView(context: Context, layoutResource: Int) : MarkerView(context, layoutResource) { + private val tvContent = findViewById(R.id.tvContent) + + // runs every time the MarkerView is redrawn, can be used to update the + // content (user-interface) + override fun refreshContent(e: Entry, highlight: Highlight) { + if (e is CandleEntry) { + tvContent?.text = formatNumber(e.high, 0, true) + } else { + tvContent?.text = formatNumber(e.y, 0, true) + } + + super.refreshContent(e, highlight) + } + + override val offset: MPPointF + get() = MPPointF(-(width / 2).toFloat(), -height.toFloat()) +} diff --git a/app/src/main/java/info/appdev/chartexample/custom/RadarMarkerView.java b/app/src/main/java/info/appdev/chartexample/custom/RadarMarkerView.java deleted file mode 100644 index 686634dad5..0000000000 --- a/app/src/main/java/info/appdev/chartexample/custom/RadarMarkerView.java +++ /dev/null @@ -1,49 +0,0 @@ - -package info.appdev.chartexample.custom; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Typeface; -import android.widget.TextView; - -import com.github.mikephil.charting.components.MarkerView; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.MPPointF; - -import java.text.DecimalFormat; - -import info.appdev.chartexample.R; - -/** - * Custom implementation of the MarkerView. - * - * @author Philipp Jahoda - */ -@SuppressLint("ViewConstructor") -public class RadarMarkerView extends MarkerView { - - private final TextView tvContent; - private final DecimalFormat format = new DecimalFormat("##0"); - - public RadarMarkerView(Context context, int layoutResource) { - super(context, layoutResource); - - tvContent = findViewById(R.id.tvContent); - tvContent.setTypeface(Typeface.createFromAsset(context.getAssets(), "OpenSans-Light.ttf")); - } - - // runs every time the MarkerView is redrawn, can be used to update the - // content (user-interface) - @Override - public void refreshContent(Entry e, Highlight highlight) { - tvContent.setText(String.format("%s %%", format.format(e.getY()))); - - super.refreshContent(e, highlight); - } - - @Override - public MPPointF getOffset() { - return new MPPointF(-(getWidth() / 2), -getHeight() - 10); - } -} diff --git a/app/src/main/java/info/appdev/chartexample/custom/RadarMarkerView.kt b/app/src/main/java/info/appdev/chartexample/custom/RadarMarkerView.kt new file mode 100644 index 0000000000..c14ffd14d0 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/custom/RadarMarkerView.kt @@ -0,0 +1,38 @@ +package info.appdev.chartexample.custom + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Typeface +import android.widget.TextView +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF +import info.appdev.chartexample.R +import java.text.DecimalFormat + +/** + * Custom implementation of the MarkerView. + * + * @author Philipp Jahoda + */ +@SuppressLint("ViewConstructor") +class RadarMarkerView(context: Context, layoutResource: Int) : MarkerView(context, layoutResource) { + private val tvContent = findViewById(R.id.tvContent) + private val format = DecimalFormat("##0") + + init { + tvContent?.setTypeface(Typeface.createFromAsset(context.assets, "OpenSans-Light.ttf")) + } + + // runs every time the MarkerView is redrawn, can be used to update the + // content (user-interface) + override fun refreshContent(e: Entry, highlight: Highlight) { + tvContent?.text = String.format("%s %%", format.format(e.y.toDouble())) + + super.refreshContent(e, highlight) + } + + override val offset: MPPointF + get() = MPPointF(-(width / 2).toFloat(), (-height - 10).toFloat()) +} diff --git a/app/src/main/java/info/appdev/chartexample/custom/StackedBarsMarkerView.java b/app/src/main/java/info/appdev/chartexample/custom/StackedBarsMarkerView.java deleted file mode 100644 index e56a8896a6..0000000000 --- a/app/src/main/java/info/appdev/chartexample/custom/StackedBarsMarkerView.java +++ /dev/null @@ -1,62 +0,0 @@ - -package info.appdev.chartexample.custom; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.widget.TextView; - -import com.github.mikephil.charting.components.MarkerView; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.MPPointF; -import com.github.mikephil.charting.utils.Utils; - -import info.appdev.chartexample.R; - -/** - * Custom implementation of the MarkerView. - * - * @author Philipp Jahoda - */ -@SuppressWarnings("unused") -@SuppressLint("ViewConstructor") -public class StackedBarsMarkerView extends MarkerView { - - private TextView tvContent; - - public StackedBarsMarkerView(Context context, int layoutResource) { - super(context, layoutResource); - - tvContent = findViewById(R.id.tvContent); - } - - // runs every time the MarkerView is redrawn, can be used to update the - // content (user-interface) - @Override - public void refreshContent(Entry e, Highlight highlight) { - - if (e instanceof BarEntry) { - - BarEntry be = (BarEntry) e; - - if(be.getYVals() != null) { - - // draw the stack value - tvContent.setText(Utils.formatNumber(be.getYVals()[highlight.getStackIndex()], 0, true)); - } else { - tvContent.setText(Utils.formatNumber(be.getY(), 0, true)); - } - } else { - - tvContent.setText(Utils.formatNumber(e.getY(), 0, true)); - } - - super.refreshContent(e, highlight); - } - - @Override - public MPPointF getOffset() { - return new MPPointF(-(getWidth() / 2), -getHeight()); - } -} diff --git a/app/src/main/java/info/appdev/chartexample/custom/StackedBarsMarkerView.kt b/app/src/main/java/info/appdev/chartexample/custom/StackedBarsMarkerView.kt new file mode 100644 index 0000000000..b69ca47ebe --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/custom/StackedBarsMarkerView.kt @@ -0,0 +1,46 @@ +package info.appdev.chartexample.custom + +import android.annotation.SuppressLint +import android.content.Context +import android.widget.TextView +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF +import com.github.mikephil.charting.utils.Utils.formatNumber +import info.appdev.chartexample.R + +/** + * Custom implementation of the MarkerView. + * + * @author Philipp Jahoda + */ +@Suppress("unused") +@SuppressLint("ViewConstructor") +class StackedBarsMarkerView(context: Context, layoutResource: Int) : MarkerView(context, layoutResource) { + private val tvContent = findViewById(R.id.tvContent) + + // runs every time the MarkerView is redrawn, can be used to update the + // content (user-interface) + override fun refreshContent(e: Entry, highlight: Highlight) { + if (e is BarEntry) { + val be = e + + if (be.yVals != null) { + // draw the stack value + + tvContent?.text = formatNumber(be.yVals!![highlight.stackIndex], 0, true) + } else { + tvContent?.text = formatNumber(be.y, 0, true) + } + } else { + tvContent?.text = formatNumber(e.y, 0, true) + } + + super.refreshContent(e, highlight) + } + + override val offset: MPPointF + get() = MPPointF(-(width / 2).toFloat(), -height.toFloat()) +} diff --git a/app/src/main/java/info/appdev/chartexample/custom/XYMarkerView.java b/app/src/main/java/info/appdev/chartexample/custom/XYMarkerView.java deleted file mode 100644 index 7bebd17ded..0000000000 --- a/app/src/main/java/info/appdev/chartexample/custom/XYMarkerView.java +++ /dev/null @@ -1,53 +0,0 @@ - -package info.appdev.chartexample.custom; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.widget.TextView; - -import com.github.mikephil.charting.components.MarkerView; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.formatter.IAxisValueFormatter; -import com.github.mikephil.charting.highlight.Highlight; -import com.github.mikephil.charting.utils.MPPointF; - -import java.text.DecimalFormat; - -import info.appdev.chartexample.R; - -/** - * Custom implementation of the MarkerView. - * - * @author Philipp Jahoda - */ -@SuppressLint("ViewConstructor") -public class XYMarkerView extends MarkerView { - - private final TextView tvContent; - private final IAxisValueFormatter xAxisValueFormatter; - - private final DecimalFormat format; - - public XYMarkerView(Context context, IAxisValueFormatter xAxisValueFormatter) { - super(context, R.layout.custom_marker_view); - - this.xAxisValueFormatter = xAxisValueFormatter; - tvContent = findViewById(R.id.tvContent); - format = new DecimalFormat("###.0"); - } - - // runs every time the MarkerView is redrawn, can be used to update the - // content (user-interface) - @Override - public void refreshContent(Entry e, Highlight highlight) { - - tvContent.setText(String.format("x: %s, y: %s", xAxisValueFormatter.getFormattedValue(e.getX(), null), format.format(e.getY()))); - - super.refreshContent(e, highlight); - } - - @Override - public MPPointF getOffset() { - return new MPPointF(-(getWidth() / 2), -getHeight()); - } -} diff --git a/app/src/main/java/info/appdev/chartexample/custom/XYMarkerView.kt b/app/src/main/java/info/appdev/chartexample/custom/XYMarkerView.kt new file mode 100644 index 0000000000..5ecf9c281f --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/custom/XYMarkerView.kt @@ -0,0 +1,35 @@ +package info.appdev.chartexample.custom + +import android.annotation.SuppressLint +import android.content.Context +import android.widget.TextView +import com.github.mikephil.charting.components.MarkerView +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.formatter.IAxisValueFormatter +import com.github.mikephil.charting.highlight.Highlight +import com.github.mikephil.charting.utils.MPPointF +import info.appdev.chartexample.R +import java.text.DecimalFormat + +/** + * Custom implementation of the MarkerView. + * + * @author Philipp Jahoda + */ +@SuppressLint("ViewConstructor") +class XYMarkerView(context: Context, private val xAxisValueFormatter: IAxisValueFormatter) : MarkerView(context, R.layout.custom_marker_view) { + private val tvContent = findViewById(R.id.tvContent) + + private val format = DecimalFormat("###.0") + + // runs every time the MarkerView is redrawn, can be used to update the + // content (user-interface) + override fun refreshContent(e: Entry, highlight: Highlight) { + tvContent?.text = String.format("x: %s, y: %s", xAxisValueFormatter.getFormattedValue(e.x, null), format.format(e.y.toDouble())) + + super.refreshContent(e, highlight) + } + + override val offset: MPPointF + get() = MPPointF(-(width / 2).toFloat(), -height.toFloat()) +} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/BarChartFrag.java b/app/src/main/java/info/appdev/chartexample/fragments/BarChartFrag.java deleted file mode 100644 index 053211a060..0000000000 --- a/app/src/main/java/info/appdev/chartexample/fragments/BarChartFrag.java +++ /dev/null @@ -1,113 +0,0 @@ -package info.appdev.chartexample.fragments; -import android.graphics.Typeface; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.listener.ChartTouchListener; -import com.github.mikephil.charting.listener.OnChartGestureListener; - -import info.appdev.chartexample.R; -import info.appdev.chartexample.custom.MyMarkerView; - - -public class BarChartFrag extends SimpleFragment implements OnChartGestureListener { - - @NonNull - public static Fragment newInstance() { - return new BarChartFrag(); - } - - private BarChart chart; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.frag_simple_bar, container, false); - - // create a new chart object - chart = new BarChart(getActivity()); - chart.getDescription().setEnabled(false); - chart.setOnChartGestureListener(this); - - MyMarkerView mv = new MyMarkerView(getActivity(), R.layout.custom_marker_view); - mv.setChartView(chart); // For bounds control - chart.setMarker(mv); - - chart.setDrawGridBackground(false); - chart.setDrawBarShadow(false); - - Typeface tf = Typeface.createFromAsset(requireContext().getAssets(), "OpenSans-Light.ttf"); - - chart.setData(generateBarData(1, 20000)); - - Legend l = chart.getLegend(); - l.setTypeface(tf); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tf); - leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - chart.getAxisRight().setEnabled(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setEnabled(false); - - // programmatically add the chart - FrameLayout parent = v.findViewById(R.id.parentLayout); - parent.addView(chart); - - return v; - } - - @Override - public void onChartGestureStart(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) { - Log.i("Gesture", "START"); - } - - @Override - public void onChartGestureEnd(MotionEvent me, ChartTouchListener.ChartGesture lastPerformedGesture) { - Log.i("Gesture", "END"); - chart.highlightValues(null); - } - - @Override - public void onChartLongPressed(MotionEvent me) { - Log.i("LongPress", "Chart long pressed."); - } - - @Override - public void onChartDoubleTapped(MotionEvent me) { - Log.i("DoubleTap", "Chart double-tapped."); - } - - @Override - public void onChartSingleTapped(MotionEvent me) { - Log.i("SingleTap", "Chart single-tapped."); - } - - @Override - public void onChartFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { - Log.i("Fling", "Chart fling. VelocityX: " + velocityX + ", VelocityY: " + velocityY); - } - - @Override - public void onChartScale(MotionEvent me, float scaleX, float scaleY) { - Log.i("Scale / Zoom", "ScaleX: " + scaleX + ", ScaleY: " + scaleY); - } - - @Override - public void onChartTranslate(MotionEvent me, float dX, float dY) { - Log.i("Translate / Move", "dX: " + dX + ", dY: " + dY); - } - -} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/BarChartFrag.kt b/app/src/main/java/info/appdev/chartexample/fragments/BarChartFrag.kt new file mode 100644 index 0000000000..baefbdbc50 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/fragments/BarChartFrag.kt @@ -0,0 +1,97 @@ +package info.appdev.chartexample.fragments + +import android.graphics.Typeface +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import androidx.fragment.app.Fragment +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.listener.ChartTouchListener.ChartGesture +import com.github.mikephil.charting.listener.OnChartGestureListener +import info.appdev.chartexample.R +import info.appdev.chartexample.custom.MyMarkerView + +class BarChartFrag : SimpleFragment(), OnChartGestureListener { + private var chart: BarChart? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val v = inflater.inflate(R.layout.frag_simple_bar, container, false) + + // create a new chart object + chart = BarChart(requireActivity()) + chart?.description?.isEnabled = false + chart?.onChartGestureListener = this + + val mv = MyMarkerView(requireContext(), R.layout.custom_marker_view) + mv.chartView = chart // For bounds control + chart?.setMarker(mv) + + chart?.drawGridBackground = false + chart?.isDrawBarShadowEnabled = false + + val tf = Typeface.createFromAsset(requireContext().assets, "OpenSans-Light.ttf") + + chart?.setData(generateBarData(1, 20000f)) + + val l = chart?.legend + l?.typeface = tf + + val leftAxis = chart?.axisLeft + leftAxis?.typeface = tf + leftAxis?.axisMinimum = 0f // this replaces setStartAtZero(true) + + chart?.axisRight?.isEnabled = false + + val xAxis = chart?.xAxis + xAxis?.isEnabled = false + + // programmatically add the chart + val parent = v.findViewById(R.id.parentLayout) + parent?.addView(chart) + + return v + } + + override fun onChartGestureStart(me: MotionEvent?, lastPerformedGesture: ChartGesture?) { + Log.i("Gesture", "START") + } + + override fun onChartGestureEnd(me: MotionEvent?, lastPerformedGesture: ChartGesture?) { + Log.i("Gesture", "END") + chart?.highlightValues(null) + } + + override fun onChartLongPressed(me: MotionEvent?) { + Log.i("LongPress", "Chart long pressed.") + } + + override fun onChartDoubleTapped(me: MotionEvent?) { + Log.i("DoubleTap", "Chart double-tapped.") + } + + override fun onChartSingleTapped(me: MotionEvent?) { + Log.i("SingleTap", "Chart single-tapped.") + } + + override fun onChartFling(me1: MotionEvent?, me2: MotionEvent?, velocityX: Float, velocityY: Float) { + Log.i("Fling", "Chart fling. VelocityX: $velocityX, VelocityY: $velocityY") + } + + override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) { + Log.i("Scale / Zoom", "ScaleX: $scaleX, ScaleY: $scaleY") + } + + override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) { + Log.i("Translate / Move", "dX: $dX, dY: $dY") + } + + companion object { + fun newInstance(): Fragment { + return BarChartFrag() + } + } +} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/ComplexityFragment.java b/app/src/main/java/info/appdev/chartexample/fragments/ComplexityFragment.java deleted file mode 100644 index fd6aeb4cc0..0000000000 --- a/app/src/main/java/info/appdev/chartexample/fragments/ComplexityFragment.java +++ /dev/null @@ -1,56 +0,0 @@ -package info.appdev.chartexample.fragments; -import android.graphics.Typeface; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import info.appdev.chartexample.R; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; - - -public class ComplexityFragment extends SimpleFragment { - - @NonNull - public static Fragment newInstance() { - return new ComplexityFragment(); - } - - @SuppressWarnings("FieldCanBeLocal") - private LineChart chart; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.frag_simple_line, container, false); - - chart = v.findViewById(R.id.lineChart1); - - chart.getDescription().setEnabled(false); - - chart.setDrawGridBackground(false); - - chart.setData(getComplexity()); - chart.animateX(3000); - - Typeface tf = Typeface.createFromAsset(requireContext().getAssets(), "OpenSans-Light.ttf"); - - Legend l = chart.getLegend(); - l.setTypeface(tf); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tf); - - chart.getAxisRight().setEnabled(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setEnabled(false); - - return v; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/ComplexityFragment.kt b/app/src/main/java/info/appdev/chartexample/fragments/ComplexityFragment.kt new file mode 100644 index 0000000000..645365b2fc --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/fragments/ComplexityFragment.kt @@ -0,0 +1,48 @@ +package info.appdev.chartexample.fragments + +import android.graphics.Typeface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.github.mikephil.charting.charts.LineChart +import info.appdev.chartexample.R + +class ComplexityFragment : SimpleFragment() { + private var chart: LineChart? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val v = inflater.inflate(R.layout.frag_simple_line, container, false) + + chart = v.findViewById(R.id.lineChart1) + + chart?.description?.isEnabled = false + + chart?.drawGridBackground = false + + chart?.setData(complexity) + chart?.animateX(3000) + + val tf = Typeface.createFromAsset(requireContext().assets, "OpenSans-Light.ttf") + + val l = chart?.legend + l?.typeface = tf + + val leftAxis = chart?.axisLeft + leftAxis?.typeface = tf + + chart?.axisRight?.isEnabled = false + + val xAxis = chart?.xAxis + xAxis?.isEnabled = false + + return v + } + + companion object { + fun newInstance(): Fragment { + return ComplexityFragment() + } + } +} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/PieChartFrag.java b/app/src/main/java/info/appdev/chartexample/fragments/PieChartFrag.java deleted file mode 100644 index bd698348c0..0000000000 --- a/app/src/main/java/info/appdev/chartexample/fragments/PieChartFrag.java +++ /dev/null @@ -1,65 +0,0 @@ -package info.appdev.chartexample.fragments; -import android.graphics.Color; -import android.graphics.Typeface; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import info.appdev.chartexample.R; - -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.text.style.RelativeSizeSpan; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.Legend; - - -public class PieChartFrag extends SimpleFragment { - - @NonNull - public static Fragment newInstance() { - return new PieChartFrag(); - } - - @SuppressWarnings("FieldCanBeLocal") - private PieChart chart; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.frag_simple_pie, container, false); - - chart = v.findViewById(R.id.pieChart1); - chart.getDescription().setEnabled(false); - - Typeface tf = Typeface.createFromAsset(requireContext().getAssets(), "OpenSans-Light.ttf"); - - chart.setCenterTextTypeface(tf); - chart.setCenterText(generateCenterText()); - chart.setCenterTextSize(10f); - chart.setCenterTextTypeface(tf); - - // radius of the center hole in percent of maximum radius - chart.setHoleRadius(45f); - chart.setTransparentCircleRadius(50f); - - Legend l = chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(false); - - chart.setData(generatePieData()); - - return v; - } - - private SpannableString generateCenterText() { - SpannableString s = new SpannableString("Revenues\nQuarters 2015"); - s.setSpan(new RelativeSizeSpan(2f), 0, 8, 0); - s.setSpan(new ForegroundColorSpan(Color.GRAY), 8, s.length(), 0); - return s; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/PieChartFrag.kt b/app/src/main/java/info/appdev/chartexample/fragments/PieChartFrag.kt new file mode 100644 index 0000000000..e05e464ff7 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/fragments/PieChartFrag.kt @@ -0,0 +1,60 @@ +package info.appdev.chartexample.fragments + +import android.graphics.Color +import android.graphics.Typeface +import android.os.Bundle +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.components.Legend +import info.appdev.chartexample.R + +class PieChartFrag : SimpleFragment() { + private var chart: PieChart? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val v = inflater.inflate(R.layout.frag_simple_pie, container, false) + + chart = v.findViewById(R.id.pieChart1) + chart?.description?.isEnabled = false + + val tf = Typeface.createFromAsset(requireContext().assets, "OpenSans-Light.ttf") + + chart?.setCenterTextTypeface(tf) + chart?.centerText = generateCenterText() + chart?.setCenterTextSize(10f) + chart?.setCenterTextTypeface(tf) + + // radius of the center hole in percent of maximum radius + chart?.holeRadius = 45f + chart?.transparentCircleRadius = 50f + + val l = chart?.legend + l?.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l?.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l?.orientation = Legend.LegendOrientation.VERTICAL + l?.setDrawInside(false) + + chart?.setData(generatePieData()) + + return v + } + + private fun generateCenterText(): SpannableString { + val s = SpannableString("Revenues\nQuarters 2015") + s.setSpan(RelativeSizeSpan(2f), 0, 8, 0) + s.setSpan(ForegroundColorSpan(Color.GRAY), 8, s.length, 0) + return s + } + + companion object { + fun newInstance(): Fragment { + return PieChartFrag() + } + } +} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/ScatterChartFrag.java b/app/src/main/java/info/appdev/chartexample/fragments/ScatterChartFrag.java deleted file mode 100644 index f7c7c3d1f3..0000000000 --- a/app/src/main/java/info/appdev/chartexample/fragments/ScatterChartFrag.java +++ /dev/null @@ -1,70 +0,0 @@ -package info.appdev.chartexample.fragments; - -import android.graphics.Typeface; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.mikephil.charting.charts.ScatterChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; - -import info.appdev.chartexample.R; -import info.appdev.chartexample.custom.MyMarkerView; - - -public class ScatterChartFrag extends SimpleFragment { - - @NonNull - public static Fragment newInstance() { - return new ScatterChartFrag(); - } - - @SuppressWarnings("FieldCanBeLocal") - private ScatterChart chart; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.frag_simple_scatter, container, false); - - chart = v.findViewById(R.id.scatterChart1); - chart.getDescription().setEnabled(false); - - Typeface tf = Typeface.createFromAsset(requireContext().getAssets(), "OpenSans-Light.ttf"); - - MyMarkerView mv = new MyMarkerView(getActivity(), R.layout.custom_marker_view); - mv.setChartView(chart); // For bounds control - chart.setMarker(mv); - - chart.setDrawGridBackground(false); - chart.setData(generateScatterData(6, 10000)); - - XAxis xAxis = chart.getXAxis(); - xAxis.setEnabled(true); - xAxis.setPosition(XAxisPosition.BOTTOM); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tf); - - YAxis rightAxis = chart.getAxisRight(); - rightAxis.setTypeface(tf); - rightAxis.setDrawGridLines(false); - - Legend l = chart.getLegend(); - l.setWordWrapEnabled(true); - l.setTypeface(tf); - l.setFormSize(14f); - l.setTextSize(9f); - - // increase the space between legend & bottom and legend & content - l.setYOffset(13f); - chart.setExtraBottomOffset(16f); - - return v; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/ScatterChartFrag.kt b/app/src/main/java/info/appdev/chartexample/fragments/ScatterChartFrag.kt new file mode 100644 index 0000000000..6fe8651fa8 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/fragments/ScatterChartFrag.kt @@ -0,0 +1,61 @@ +package info.appdev.chartexample.fragments + +import android.graphics.Typeface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.github.mikephil.charting.charts.ScatterChart +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import info.appdev.chartexample.R +import info.appdev.chartexample.custom.MyMarkerView + +class ScatterChartFrag : SimpleFragment() { + private var chart: ScatterChart? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val v = inflater.inflate(R.layout.frag_simple_scatter, container, false) + + chart = v.findViewById(R.id.scatterChart1) + chart?.description?.isEnabled = false + + val tf = Typeface.createFromAsset(requireContext().assets, "OpenSans-Light.ttf") + + val mv = MyMarkerView(requireContext(), R.layout.custom_marker_view) + mv.chartView = chart // For bounds control + chart?.setMarker(mv) + + chart?.drawGridBackground = false + chart?.setData(generateScatterData(6, 10000f)) + + val xAxis = chart?.xAxis + xAxis?.isEnabled = true + xAxis?.position = XAxisPosition.BOTTOM + + val leftAxis = chart?.axisLeft + leftAxis?.typeface = tf + + val rightAxis = chart?.axisRight + rightAxis?.typeface = tf + rightAxis?.setDrawGridLines(false) + + val l = chart?.legend + l?.isWordWrapEnabled = true + l?.typeface = tf + l?.formSize = 14f + l?.textSize = 9f + + // increase the space between legend & bottom and legend & content + l?.yOffset = 13f + chart?.extraBottomOffset = 16f + + return v + } + + companion object { + fun newInstance(): Fragment { + return ScatterChartFrag() + } + } +} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/SimpleFragment.java b/app/src/main/java/info/appdev/chartexample/fragments/SimpleFragment.java deleted file mode 100644 index 249c4f5ba2..0000000000 --- a/app/src/main/java/info/appdev/chartexample/fragments/SimpleFragment.java +++ /dev/null @@ -1,194 +0,0 @@ -package info.appdev.chartexample.fragments; - -import android.graphics.Color; -import android.graphics.Typeface; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.mikephil.charting.charts.ScatterChart; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.BarDataSet; -import com.github.mikephil.charting.data.BarEntry; -import com.github.mikephil.charting.data.Entry; -import com.github.mikephil.charting.data.LineData; -import com.github.mikephil.charting.data.LineDataSet; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.data.PieDataSet; -import com.github.mikephil.charting.data.PieEntry; -import com.github.mikephil.charting.data.ScatterData; -import com.github.mikephil.charting.data.ScatterDataSet; -import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; -import com.github.mikephil.charting.interfaces.datasets.ILineDataSet; -import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet; -import com.github.mikephil.charting.utils.ColorTemplate; -import com.github.mikephil.charting.utils.FileUtils; -import info.appdev.chartexample.DataTools; - -import java.util.ArrayList; - -@SuppressWarnings({"SameParameterValue", "WeakerAccess"}) -public abstract class SimpleFragment extends Fragment { - - private Typeface tf; - - public SimpleFragment() { - - } - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - tf = Typeface.createFromAsset(requireContext().getAssets(), "OpenSans-Regular.ttf"); - return super.onCreateView(inflater, container, savedInstanceState); - } - - protected BarData generateBarData(int dataSets, float range) { - int count = 12; - Double[] values = DataTools.Companion.getValues(count); - ArrayList sets = new ArrayList<>(); - - for(int i = 0; i < dataSets; i++) { - - ArrayList entries = new ArrayList<>(); - - for(int j = 0; j < count; j++) { - entries.add(new BarEntry(j, (float) (values[j].floatValue() * range) + range / 4)); - } - - BarDataSet ds = new BarDataSet(entries, getLabel(i)); - ds.setColors(ColorTemplate.VORDIPLOM_COLORS); - sets.add(ds); - } - - BarData d = new BarData(sets); - d.setValueTypeface(tf); - return d; - } - - protected ScatterData generateScatterData(int dataSets, float range) { - int count = 100; - Double[] values = DataTools.Companion.getValues(count); - ArrayList sets = new ArrayList<>(); - - ScatterChart.ScatterShape[] shapes = ScatterChart.ScatterShape.getAllDefaultShapes(); - - for(int i = 0; i < dataSets; i++) { - - ArrayList entries = new ArrayList<>(); - - for(int j = 0; j < count; j++) { - entries.add(new Entry(j, (float) (values[j].floatValue() * range) + range / 4)); - } - - ScatterDataSet ds = new ScatterDataSet(entries, getLabel(i)); - ds.setScatterShapeSize(12f); - ds.setScatterShape(shapes[i % shapes.length]); - ds.setColors(ColorTemplate.COLORFUL_COLORS); - ds.setScatterShapeSize(9f); - sets.add(ds); - } - - ScatterData d = new ScatterData(sets); - d.setValueTypeface(tf); - return d; - } - - /** - * generates less data (1 DataSet, 4 values) - * @return PieData - */ - protected PieData generatePieData() { - - int count = 4; - Double[] values = DataTools.Companion.getValues(count); - ArrayList entries1 = new ArrayList<>(); - - for(int i = 0; i < count; i++) { - entries1.add(new PieEntry((float) ((values[i].floatValue() * 60) + 40), "Quarter " + (i+1))); - } - - PieDataSet ds1 = new PieDataSet(entries1, "Quarterly Revenues 2015"); - ds1.setColors(ColorTemplate.VORDIPLOM_COLORS); - ds1.setSliceSpace(2f); - ds1.setValueTextColor(Color.WHITE); - ds1.setValueTextSize(12f); - - PieData d = new PieData(ds1); - d.setValueTypeface(tf); - - return d; - } - - protected LineData generateLineData() { - - ArrayList sets = new ArrayList<>(); - LineDataSet ds1 = new LineDataSet(FileUtils.loadEntriesFromAssets(requireContext().getAssets(), "sine.txt"), "Sine function"); - LineDataSet ds2 = new LineDataSet(FileUtils.loadEntriesFromAssets(requireContext().getAssets(), "cosine.txt"), "Cosine function"); - - ds1.setLineWidth(2f); - ds2.setLineWidth(2f); - - ds1.setDrawCircles(false); - ds2.setDrawCircles(false); - - ds1.setColor(ColorTemplate.VORDIPLOM_COLORS[0]); - ds2.setColor(ColorTemplate.VORDIPLOM_COLORS[1]); - - // load DataSets from files in assets folder - sets.add(ds1); - sets.add(ds2); - - LineData d = new LineData(sets); - d.setValueTypeface(tf); - return d; - } - - protected LineData getComplexity() { - - ArrayList sets = new ArrayList<>(); - - LineDataSet ds1 = new LineDataSet(FileUtils.loadEntriesFromAssets(requireContext().getAssets(), "n.txt"), "O(n)"); - LineDataSet ds2 = new LineDataSet(FileUtils.loadEntriesFromAssets(requireContext().getAssets(), "nlogn.txt"), "O(nlogn)"); - LineDataSet ds3 = new LineDataSet(FileUtils.loadEntriesFromAssets(requireContext().getAssets(), "square.txt"), "O(n\u00B2)"); - LineDataSet ds4 = new LineDataSet(FileUtils.loadEntriesFromAssets(requireContext().getAssets(), "three.txt"), "O(n\u00B3)"); - - ds1.setColor(ColorTemplate.VORDIPLOM_COLORS[0]); - ds2.setColor(ColorTemplate.VORDIPLOM_COLORS[1]); - ds3.setColor(ColorTemplate.VORDIPLOM_COLORS[2]); - ds4.setColor(ColorTemplate.VORDIPLOM_COLORS[3]); - - ds1.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[0]); - ds2.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[1]); - ds3.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[2]); - ds4.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[3]); - - ds1.setLineWidth(2.5f); - ds1.setCircleRadius(3f); - ds2.setLineWidth(2.5f); - ds2.setCircleRadius(3f); - ds3.setLineWidth(2.5f); - ds3.setCircleRadius(3f); - ds4.setLineWidth(2.5f); - ds4.setCircleRadius(3f); - - - // load DataSets from files in assets folder - sets.add(ds1); - sets.add(ds2); - sets.add(ds3); - sets.add(ds4); - - LineData d = new LineData(sets); - d.setValueTypeface(tf); - return d; - } - - private final String[] mLabels = new String[] { "Company A", "Company B", "Company C", "Company D", "Company E", "Company F" }; - - private String getLabel(int i) { - return mLabels[i]; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/SimpleFragment.kt b/app/src/main/java/info/appdev/chartexample/fragments/SimpleFragment.kt new file mode 100644 index 0000000000..a411cb7732 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/fragments/SimpleFragment.kt @@ -0,0 +1,184 @@ +package info.appdev.chartexample.fragments + +import android.graphics.Color +import android.graphics.Typeface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.github.mikephil.charting.charts.ScatterChart.ScatterShape +import com.github.mikephil.charting.charts.ScatterChart.ScatterShape.Companion.allDefaultShapes +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.BarDataSet +import com.github.mikephil.charting.data.BarEntry +import com.github.mikephil.charting.data.Entry +import com.github.mikephil.charting.data.LineData +import com.github.mikephil.charting.data.LineDataSet +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.data.PieDataSet +import com.github.mikephil.charting.data.PieEntry +import com.github.mikephil.charting.data.ScatterData +import com.github.mikephil.charting.data.ScatterDataSet +import com.github.mikephil.charting.interfaces.datasets.IBarDataSet +import com.github.mikephil.charting.interfaces.datasets.ILineDataSet +import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.FileUtils.loadEntriesFromAssets +import info.appdev.chartexample.DataTools.Companion.getValues + +abstract class SimpleFragment : Fragment() { + private var tf: Typeface? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + tf = Typeface.createFromAsset(requireContext().assets, "OpenSans-Regular.ttf") + return super.onCreateView(inflater, container, savedInstanceState) + } + + protected fun generateBarData(dataSets: Int, range: Float): BarData { + val count = 12 + val values = getValues(count) + val sets = ArrayList() + + for (i in 0..() + + for (j in 0..() + + val shapes: Array = allDefaultShapes + + for (i in 0..() + + for (j in 0..() + + for (i in 0..() + val ds1 = LineDataSet(loadEntriesFromAssets(requireContext().assets, "sine.txt"), "Sine function") + val ds2 = LineDataSet(loadEntriesFromAssets(requireContext().assets, "cosine.txt"), "Cosine function") + + ds1.lineWidth = 2f + ds2.lineWidth = 2f + + ds1.isDrawCirclesEnabled = false + ds2.isDrawCirclesEnabled = false + + ds1.setColor(ColorTemplate.VORDIPLOM_COLORS[0]) + ds2.setColor(ColorTemplate.VORDIPLOM_COLORS[1]) + + // load DataSets from files in assets folder + sets.add(ds1) + sets.add(ds2) + + val d = LineData(sets) + d.setValueTypeface(tf) + return d + } + + protected val complexity: LineData + get() { + val sets = ArrayList() + + val ds1 = + LineDataSet(loadEntriesFromAssets(requireContext().assets, "n.txt"), "O(n)") + val ds2 = + LineDataSet(loadEntriesFromAssets(requireContext().assets, "nlogn.txt"), "O(nlogn)") + val ds3 = + LineDataSet(loadEntriesFromAssets(requireContext().assets, "square.txt"), "O(n\u00B2)") + val ds4 = + LineDataSet(loadEntriesFromAssets(requireContext().assets, "three.txt"), "O(n\u00B3)") + + ds1.setColor(ColorTemplate.VORDIPLOM_COLORS[0]) + ds2.setColor(ColorTemplate.VORDIPLOM_COLORS[1]) + ds3.setColor(ColorTemplate.VORDIPLOM_COLORS[2]) + ds4.setColor(ColorTemplate.VORDIPLOM_COLORS[3]) + + ds1.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[0]) + ds2.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[1]) + ds3.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[2]) + ds4.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[3]) + + ds1.lineWidth = 2.5f + ds1.circleRadius = 3f + ds2.lineWidth = 2.5f + ds2.circleRadius = 3f + ds3.lineWidth = 2.5f + ds3.circleRadius = 3f + ds4.lineWidth = 2.5f + ds4.circleRadius = 3f + + + // load DataSets from files in assets folder + sets.add(ds1) + sets.add(ds2) + sets.add(ds3) + sets.add(ds4) + + val d = LineData(sets) + d.setValueTypeface(tf) + return d + } + + private val mLabels: Array = arrayOf("Company A", "Company B", "Company C", "Company D", "Company E", "Company F") + + private fun getLabel(i: Int): String { + return mLabels[i] + } +} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/SineCosineFragment.java b/app/src/main/java/info/appdev/chartexample/fragments/SineCosineFragment.java deleted file mode 100644 index 68858b64cb..0000000000 --- a/app/src/main/java/info/appdev/chartexample/fragments/SineCosineFragment.java +++ /dev/null @@ -1,58 +0,0 @@ -package info.appdev.chartexample.fragments; -import android.graphics.Typeface; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import info.appdev.chartexample.R; - -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.YAxis; - - -public class SineCosineFragment extends SimpleFragment { - - @NonNull - public static Fragment newInstance() { - return new SineCosineFragment(); - } - - @SuppressWarnings("FieldCanBeLocal") - private LineChart chart; - - @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View v = inflater.inflate(R.layout.frag_simple_line, container, false); - - chart = v.findViewById(R.id.lineChart1); - - chart.getDescription().setEnabled(false); - - chart.setDrawGridBackground(false); - - chart.setData(generateLineData()); - chart.animateX(3000); - - Typeface tf = Typeface.createFromAsset(requireContext().getAssets(), "OpenSans-Light.ttf"); - - Legend l = chart.getLegend(); - l.setTypeface(tf); - - YAxis leftAxis = chart.getAxisLeft(); - leftAxis.setTypeface(tf); - leftAxis.setAxisMaximum(1.2f); - leftAxis.setAxisMinimum(-1.2f); - - chart.getAxisRight().setEnabled(false); - - XAxis xAxis = chart.getXAxis(); - xAxis.setEnabled(false); - - return v; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/SineCosineFragment.kt b/app/src/main/java/info/appdev/chartexample/fragments/SineCosineFragment.kt new file mode 100644 index 0000000000..d7c4fb1a4e --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/fragments/SineCosineFragment.kt @@ -0,0 +1,50 @@ +package info.appdev.chartexample.fragments + +import android.graphics.Typeface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.github.mikephil.charting.charts.LineChart +import info.appdev.chartexample.R + +class SineCosineFragment : SimpleFragment() { + private var chart: LineChart? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val v = inflater.inflate(R.layout.frag_simple_line, container, false) + + chart = v.findViewById(R.id.lineChart1) + + chart?.description?.isEnabled = false + + chart?.drawGridBackground = false + + chart?.setData(generateLineData()) + chart?.animateX(3000) + + val tf = Typeface.createFromAsset(requireContext().assets, "OpenSans-Light.ttf") + + val l = chart?.legend + l?.typeface = tf + + val leftAxis = chart?.axisLeft + leftAxis?.typeface = tf + leftAxis?.axisMaximum = 1.2f + leftAxis?.axisMinimum = -1.2f + + chart?.axisRight?.isEnabled = false + + val xAxis = chart?.xAxis + xAxis?.isEnabled = false + + return v + } + + companion object { + fun newInstance(): Fragment { + return SineCosineFragment() + } + } +} diff --git a/app/src/main/java/info/appdev/chartexample/fragments/ViewPagerSimpleChartDemo.kt b/app/src/main/java/info/appdev/chartexample/fragments/ViewPagerSimpleChartDemo.kt index eb179f3cfc..f229ab4574 100644 --- a/app/src/main/java/info/appdev/chartexample/fragments/ViewPagerSimpleChartDemo.kt +++ b/app/src/main/java/info/appdev/chartexample/fragments/ViewPagerSimpleChartDemo.kt @@ -1,12 +1,16 @@ package info.appdev.chartexample.fragments import android.content.Intent -import android.net.Uri import android.os.Bundle -import android.view.* +import android.view.Gravity +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.WindowManager import android.widget.FrameLayout import android.widget.Toast import androidx.coordinatorlayout.widget.CoordinatorLayout +import androidx.core.net.toUri import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentPagerAdapter @@ -24,23 +28,23 @@ class ViewPagerSimpleChartDemo : DemoBase() { window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN) setContentView(R.layout.activity_awesomedesign) val pager = findViewById(R.id.pager) - pager.offscreenPageLimit = 3 - pager.adapter = PageAdapter(supportFragmentManager) + pager?.offscreenPageLimit = 3 + pager?.adapter = PageAdapter(supportFragmentManager) showSnackbar("Swipe left and right for more awesome design examples!") } - private inner class PageAdapter(fm: FragmentManager?) : FragmentPagerAdapter(fm!!) { + private inner class PageAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) { override fun getItem(pos: Int): Fragment { - var f: Fragment? = null - when (pos) { - 0 -> f = SineCosineFragment.newInstance() - 1 -> f = ComplexityFragment.newInstance() - 2 -> f = BarChartFrag.newInstance() - 3 -> f = ScatterChartFrag.newInstance() - 4 -> f = PieChartFrag.newInstance() + val f = when (pos) { + 0 -> SineCosineFragment.newInstance() + 1 -> ComplexityFragment.newInstance() + 2 -> BarChartFrag.newInstance() + 3 -> ScatterChartFrag.newInstance() + 4 -> PieChartFrag.newInstance() + else -> throw ArrayIndexOutOfBoundsException(pos) } - return f!! + return f } override fun getCount(): Int { @@ -57,7 +61,8 @@ class ViewPagerSimpleChartDemo : DemoBase() { when (item.itemId) { R.id.viewGithub -> { val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse("https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/fragments/SimpleChartDemo.java") + i.data = + "https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/com/xxmassdeveloper/mpchartexample/fragments/SimpleChartDemo.java".toUri() startActivity(i) } } @@ -68,7 +73,7 @@ class ViewPagerSimpleChartDemo : DemoBase() { public override fun saveToGallery() = Unit private fun showSnackbar(text: String) { - val viewPos : View = findViewById(android.R.id.content) + val viewPos = findViewById(android.R.id.content) ?: return val snackbar = Snackbar.make(viewPos, text, Snackbar.LENGTH_SHORT) val view = snackbar.view when (val params = view.layoutParams) { diff --git a/app/src/main/java/info/appdev/chartexample/listviewitems/BarChartItem.java b/app/src/main/java/info/appdev/chartexample/listviewitems/BarChartItem.java deleted file mode 100644 index 20e34da0f4..0000000000 --- a/app/src/main/java/info/appdev/chartexample/listviewitems/BarChartItem.java +++ /dev/null @@ -1,92 +0,0 @@ -package info.appdev.chartexample.listviewitems; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Typeface; -import android.view.LayoutInflater; -import android.view.View; - -import com.github.mikephil.charting.charts.BarChart; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.BarData; -import com.github.mikephil.charting.data.ChartData; - -import info.appdev.chartexample.R; - -public class BarChartItem extends ChartItem { - - private final Typeface mTf; - - public BarChartItem(ChartData cd, Context c) { - super(cd); - - mTf = Typeface.createFromAsset(c.getAssets(), "OpenSans-Regular.ttf"); - } - - @Override - public int getItemType() { - return TYPE_BARCHART; - } - - @SuppressLint("InflateParams") - @Override - public View getView(int position, View convertView, Context c) { - - ViewHolder holder; - - if (convertView == null) { - - holder = new ViewHolder(); - - convertView = LayoutInflater.from(c).inflate( - R.layout.list_item_barchart, null); - holder.chart = convertView.findViewById(R.id.chart); - - convertView.setTag(holder); - - } else { - holder = (ViewHolder) convertView.getTag(); - } - - // apply styling - holder.chart.getDescription().setEnabled(false); - holder.chart.setDrawGridBackground(false); - holder.chart.setDrawBarShadow(false); - - XAxis xAxis = holder.chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTTOM); - xAxis.setTypeface(mTf); - xAxis.setDrawGridLines(false); - xAxis.setDrawAxisLine(true); - - YAxis leftAxis = holder.chart.getAxisLeft(); - leftAxis.setTypeface(mTf); - leftAxis.setLabelCount(5, false); - leftAxis.setSpaceTop(20f); - leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - YAxis rightAxis = holder.chart.getAxisRight(); - rightAxis.setTypeface(mTf); - rightAxis.setLabelCount(5, false); - rightAxis.setSpaceTop(20f); - rightAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - mChartData.setValueTypeface(mTf); - - // set data - holder.chart.setData((BarData) mChartData); - holder.chart.setFitBars(true); - - // do not forget to refresh the chart -// holder.chart.invalidate(); - holder.chart.animateY(700); - - return convertView; - } - - private static class ViewHolder { - BarChart chart; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/listviewitems/BarChartItem.kt b/app/src/main/java/info/appdev/chartexample/listviewitems/BarChartItem.kt new file mode 100644 index 0000000000..546b86c603 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/listviewitems/BarChartItem.kt @@ -0,0 +1,77 @@ +package info.appdev.chartexample.listviewitems + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.View +import com.github.mikephil.charting.charts.BarChart +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.BarData +import com.github.mikephil.charting.data.ChartData +import info.appdev.chartexample.R + +class BarChartItem(cd: ChartData<*, *>, c: Context) : ChartItem(cd) { + private val mTf = Typeface.createFromAsset(c.assets, "OpenSans-Regular.ttf") + + override val itemType: Int + get() = TYPE_BARCHART + + @SuppressLint("InflateParams") + override fun getView(position: Int, convertView: View?, c: Context): View { + var convertView = convertView + val holder: ViewHolder + + if (convertView == null) { + holder = ViewHolder() + + convertView = LayoutInflater.from(c).inflate( + R.layout.list_item_barchart, null + ) + holder.chart = convertView.findViewById(R.id.chart) + + convertView.tag = holder + } else { + holder = convertView.tag as ViewHolder + } + + // apply styling + holder.chart?.description?.isEnabled = false + holder.chart?.drawGridBackground = false + holder.chart?.isDrawBarShadowEnabled = false + + val xAxis = holder.chart?.xAxis + xAxis?.position = XAxisPosition.BOTTOM + xAxis?.typeface = mTf + xAxis?.setDrawGridLines(false) + xAxis?.setDrawAxisLine(true) + + val leftAxis = holder.chart?.axisLeft + leftAxis?.typeface = mTf + leftAxis?.setLabelCount(5, false) + leftAxis?.spaceTop = 20f + leftAxis?.axisMinimum = 0f // this replaces setStartAtZero(true) + + val rightAxis = holder.chart?.axisRight + rightAxis?.typeface = mTf + rightAxis?.setLabelCount(5, false) + rightAxis?.spaceTop = 20f + rightAxis?.axisMinimum = 0f // this replaces setStartAtZero(true) + + mChartData.setValueTypeface(mTf) + + // set data + holder.chart?.setData(mChartData as BarData?) + holder.chart?.setFitBars(true) + + // do not forget to refresh the chart +// holder.chart.invalidate(); + holder.chart?.animateY(700) + + return convertView + } + + private class ViewHolder { + var chart: BarChart? = null + } +} diff --git a/app/src/main/java/info/appdev/chartexample/listviewitems/ChartItem.java b/app/src/main/java/info/appdev/chartexample/listviewitems/ChartItem.java deleted file mode 100644 index eb5300c2ae..0000000000 --- a/app/src/main/java/info/appdev/chartexample/listviewitems/ChartItem.java +++ /dev/null @@ -1,29 +0,0 @@ -package info.appdev.chartexample.listviewitems; - -import android.content.Context; -import android.view.View; - -import com.github.mikephil.charting.data.ChartData; - -/** - * Base class of the Chart ListView items - * @author philipp - * - */ -@SuppressWarnings("unused") -public abstract class ChartItem { - - static final int TYPE_BARCHART = 0; - static final int TYPE_LINECHART = 1; - static final int TYPE_PIECHART = 2; - - ChartData mChartData; - - ChartItem(ChartData cd) { - this.mChartData = cd; - } - - public abstract int getItemType(); - - public abstract View getView(int position, View convertView, Context c); -} diff --git a/app/src/main/java/info/appdev/chartexample/listviewitems/ChartItem.kt b/app/src/main/java/info/appdev/chartexample/listviewitems/ChartItem.kt new file mode 100644 index 0000000000..ce08c1c55b --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/listviewitems/ChartItem.kt @@ -0,0 +1,24 @@ +package info.appdev.chartexample.listviewitems + +import android.content.Context +import android.view.View +import com.github.mikephil.charting.data.ChartData + +/** + * Base class of the Chart ListView items + * @author philipp + */ +@Suppress("unused") +abstract class ChartItem internal constructor(cd: ChartData<*, *>) { + var mChartData: ChartData<*, *> = cd + + abstract val itemType: Int + + abstract fun getView(position: Int, convertView: View?, c: Context): View + + companion object { + const val TYPE_BARCHART: Int = 0 + const val TYPE_LINECHART: Int = 1 + const val TYPE_PIECHART: Int = 2 + } +} diff --git a/app/src/main/java/info/appdev/chartexample/listviewitems/LineChartItem.java b/app/src/main/java/info/appdev/chartexample/listviewitems/LineChartItem.java deleted file mode 100644 index 7d95061314..0000000000 --- a/app/src/main/java/info/appdev/chartexample/listviewitems/LineChartItem.java +++ /dev/null @@ -1,89 +0,0 @@ - -package info.appdev.chartexample.listviewitems; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Typeface; -import android.view.LayoutInflater; -import android.view.View; - -import com.github.mikephil.charting.charts.LineChart; -import com.github.mikephil.charting.components.XAxis; -import com.github.mikephil.charting.components.XAxis.XAxisPosition; -import com.github.mikephil.charting.components.YAxis; -import com.github.mikephil.charting.data.ChartData; -import com.github.mikephil.charting.data.LineData; - -import info.appdev.chartexample.R; - -public class LineChartItem extends ChartItem { - - private final Typeface mTf; - - public LineChartItem(ChartData cd, Context c) { - super(cd); - - mTf = Typeface.createFromAsset(c.getAssets(), "OpenSans-Regular.ttf"); - } - - @Override - public int getItemType() { - return TYPE_LINECHART; - } - - @SuppressLint("InflateParams") - @Override - public View getView(int position, View convertView, Context c) { - - ViewHolder holder; - - if (convertView == null) { - - holder = new ViewHolder(); - - convertView = LayoutInflater.from(c).inflate( - R.layout.list_item_linechart, null); - holder.chart = convertView.findViewById(R.id.chart); - - convertView.setTag(holder); - - } else { - holder = (ViewHolder) convertView.getTag(); - } - - // apply styling - // holder.chart.setValueTypeface(mTf); - holder.chart.getDescription().setEnabled(false); - holder.chart.setDrawGridBackground(false); - - XAxis xAxis = holder.chart.getXAxis(); - xAxis.setPosition(XAxisPosition.BOTTOM); - xAxis.setTypeface(mTf); - xAxis.setDrawGridLines(false); - xAxis.setDrawAxisLine(true); - - YAxis leftAxis = holder.chart.getAxisLeft(); - leftAxis.setTypeface(mTf); - leftAxis.setLabelCount(5, false); - leftAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - YAxis rightAxis = holder.chart.getAxisRight(); - rightAxis.setTypeface(mTf); - rightAxis.setLabelCount(5, false); - rightAxis.setDrawGridLines(false); - rightAxis.setAxisMinimum(0f); // this replaces setStartAtZero(true) - - // set data - holder.chart.setData((LineData) mChartData); - - // do not forget to refresh the chart - // holder.chart.invalidate(); - holder.chart.animateX(750); - - return convertView; - } - - private static class ViewHolder { - LineChart chart; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/listviewitems/LineChartItem.kt b/app/src/main/java/info/appdev/chartexample/listviewitems/LineChartItem.kt new file mode 100644 index 0000000000..c89572e659 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/listviewitems/LineChartItem.kt @@ -0,0 +1,73 @@ +package info.appdev.chartexample.listviewitems + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.View +import com.github.mikephil.charting.charts.LineChart +import com.github.mikephil.charting.components.XAxis.XAxisPosition +import com.github.mikephil.charting.data.ChartData +import com.github.mikephil.charting.data.LineData +import info.appdev.chartexample.R + +class LineChartItem(cd: ChartData<*, *>, c: Context) : ChartItem(cd) { + private val mTf = Typeface.createFromAsset(c.assets, "OpenSans-Regular.ttf") + + override val itemType: Int + get() = TYPE_LINECHART + + @SuppressLint("InflateParams") + override fun getView(position: Int, convertView: View?, c: Context): View { + var convertView = convertView + val holder: ViewHolder + + if (convertView == null) { + holder = ViewHolder() + + convertView = LayoutInflater.from(c).inflate( + R.layout.list_item_linechart, null + ) + holder.chart = convertView.findViewById(R.id.chart) + + convertView.tag = holder + } else { + holder = convertView.tag as ViewHolder + } + + // apply styling + // holder.chart.setValueTypeface(mTf); + holder.chart!!.description.isEnabled = false + holder.chart!!.drawGridBackground = false + + val xAxis = holder.chart!!.xAxis + xAxis.position = XAxisPosition.BOTTOM + xAxis.typeface = mTf + xAxis.setDrawGridLines(false) + xAxis.setDrawAxisLine(true) + + val leftAxis = holder.chart!!.axisLeft + leftAxis.typeface = mTf + leftAxis.setLabelCount(5, false) + leftAxis.axisMinimum = 0f // this replaces setStartAtZero(true) + + val rightAxis = holder.chart!!.axisRight + rightAxis.typeface = mTf + rightAxis.setLabelCount(5, false) + rightAxis.setDrawGridLines(false) + rightAxis.axisMinimum = 0f // this replaces setStartAtZero(true) + + // set data + holder.chart!!.setData(mChartData as LineData?) + + // do not forget to refresh the chart + // holder.chart.invalidate(); + holder.chart!!.animateX(750) + + return convertView + } + + private class ViewHolder { + var chart: LineChart? = null + } +} diff --git a/app/src/main/java/info/appdev/chartexample/listviewitems/PieChartItem.java b/app/src/main/java/info/appdev/chartexample/listviewitems/PieChartItem.java deleted file mode 100644 index c46916f7f5..0000000000 --- a/app/src/main/java/info/appdev/chartexample/listviewitems/PieChartItem.java +++ /dev/null @@ -1,106 +0,0 @@ - -package info.appdev.chartexample.listviewitems; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Typeface; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; -import android.text.style.RelativeSizeSpan; -import android.view.LayoutInflater; -import android.view.View; - -import com.github.mikephil.charting.charts.PieChart; -import com.github.mikephil.charting.components.Legend; -import com.github.mikephil.charting.data.ChartData; -import com.github.mikephil.charting.data.PieData; -import com.github.mikephil.charting.formatter.PercentFormatter; -import com.github.mikephil.charting.utils.ColorTemplate; - -import info.appdev.chartexample.R; - -public class PieChartItem extends ChartItem { - - private final Typeface mTf; - private final SpannableString mCenterText; - - public PieChartItem(ChartData cd, Context c) { - super(cd); - - mTf = Typeface.createFromAsset(c.getAssets(), "OpenSans-Regular.ttf"); - mCenterText = generateCenterText(); - } - - @Override - public int getItemType() { - return TYPE_PIECHART; - } - - @SuppressLint("InflateParams") - @Override - public View getView(int position, View convertView, Context c) { - - ViewHolder holder; - - if (convertView == null) { - - holder = new ViewHolder(); - - convertView = LayoutInflater.from(c).inflate( - R.layout.list_item_piechart, null); - holder.chart = convertView.findViewById(R.id.chart); - - convertView.setTag(holder); - - } else { - holder = (ViewHolder) convertView.getTag(); - } - - // apply styling - holder.chart.getDescription().setEnabled(false); - holder.chart.setHoleRadius(52f); - holder.chart.setTransparentCircleRadius(57f); - holder.chart.setCenterText(mCenterText); - holder.chart.setCenterTextTypeface(mTf); - holder.chart.setCenterTextSize(9f); - holder.chart.setUsePercentValues(true); - holder.chart.setExtraOffsets(5, 10, 50, 10); - - mChartData.setValueFormatter(new PercentFormatter()); - mChartData.setValueTypeface(mTf); - mChartData.setValueTextSize(11f); - mChartData.setValueTextColor(Color.WHITE); - // set data - holder.chart.setData((PieData) mChartData); - - Legend l = holder.chart.getLegend(); - l.setVerticalAlignment(Legend.LegendVerticalAlignment.TOP); - l.setHorizontalAlignment(Legend.LegendHorizontalAlignment.RIGHT); - l.setOrientation(Legend.LegendOrientation.VERTICAL); - l.setDrawInside(false); - l.setYEntrySpace(0f); - l.setYOffset(0f); - - // do not forget to refresh the chart - // holder.chart.invalidate(); - holder.chart.animateY(900); - - return convertView; - } - - private SpannableString generateCenterText() { - SpannableString s = new SpannableString("MPAndroidChart\ncreated by\nPhilipp Jahoda"); - s.setSpan(new RelativeSizeSpan(1.6f), 0, 14, 0); - s.setSpan(new ForegroundColorSpan(ColorTemplate.VORDIPLOM_COLORS[0]), 0, 14, 0); - s.setSpan(new RelativeSizeSpan(.9f), 14, 25, 0); - s.setSpan(new ForegroundColorSpan(Color.GRAY), 14, 25, 0); - s.setSpan(new RelativeSizeSpan(1.4f), 25, s.length(), 0); - s.setSpan(new ForegroundColorSpan(ColorTemplate.getHoloBlue()), 25, s.length(), 0); - return s; - } - - private static class ViewHolder { - PieChart chart; - } -} diff --git a/app/src/main/java/info/appdev/chartexample/listviewitems/PieChartItem.kt b/app/src/main/java/info/appdev/chartexample/listviewitems/PieChartItem.kt new file mode 100644 index 0000000000..a0794f7d20 --- /dev/null +++ b/app/src/main/java/info/appdev/chartexample/listviewitems/PieChartItem.kt @@ -0,0 +1,96 @@ +package info.appdev.chartexample.listviewitems + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Color +import android.graphics.Typeface +import android.text.SpannableString +import android.text.style.ForegroundColorSpan +import android.text.style.RelativeSizeSpan +import android.view.LayoutInflater +import android.view.View +import com.github.mikephil.charting.charts.PieChart +import com.github.mikephil.charting.components.Legend +import com.github.mikephil.charting.data.ChartData +import com.github.mikephil.charting.data.PieData +import com.github.mikephil.charting.formatter.PercentFormatter +import com.github.mikephil.charting.utils.ColorTemplate +import com.github.mikephil.charting.utils.ColorTemplate.holoBlue +import info.appdev.chartexample.R + +class PieChartItem(cd: ChartData<*, *>, c: Context) : ChartItem(cd) { + private val mTf = Typeface.createFromAsset(c.assets, "OpenSans-Regular.ttf") + private val mCenterText: SpannableString + + init { + mCenterText = generateCenterText() + } + + override val itemType: Int + get() = TYPE_PIECHART + + @SuppressLint("InflateParams") + override fun getView(position: Int, convertView: View?, c: Context): View { + var convertView = convertView + val holder: ViewHolder + + if (convertView == null) { + holder = ViewHolder() + + convertView = LayoutInflater.from(c).inflate( + R.layout.list_item_piechart, null + ) + holder.chart = convertView.findViewById(R.id.chart) + + convertView.tag = holder + } else { + holder = convertView.tag as ViewHolder + } + + // apply styling + holder.chart!!.description.isEnabled = false + holder.chart!!.holeRadius = 52f + holder.chart!!.transparentCircleRadius = 57f + holder.chart!!.centerText = mCenterText + holder.chart!!.setCenterTextTypeface(mTf) + holder.chart!!.setCenterTextSize(9f) + holder.chart!!.setUsePercentValues(true) + holder.chart!!.setExtraOffsets(5f, 10f, 50f, 10f) + + mChartData.setValueFormatter(PercentFormatter()) + mChartData.setValueTypeface(mTf) + mChartData.setValueTextSize(11f) + mChartData.setValueTextColor(Color.WHITE) + // set data + holder.chart!!.setData(mChartData as PieData?) + + val l = holder.chart!!.legend + l.verticalAlignment = Legend.LegendVerticalAlignment.TOP + l.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + l.orientation = Legend.LegendOrientation.VERTICAL + l.setDrawInside(false) + l.yEntrySpace = 0f + l.yOffset = 0f + + // do not forget to refresh the chart + // holder.chart.invalidate(); + holder.chart!!.animateY(900) + + return convertView + } + + private fun generateCenterText(): SpannableString { + val s = SpannableString("MPAndroidChart\ncreated by\nPhilipp Jahoda") + s.setSpan(RelativeSizeSpan(1.6f), 0, 14, 0) + s.setSpan(ForegroundColorSpan(ColorTemplate.VORDIPLOM_COLORS[0]), 0, 14, 0) + s.setSpan(RelativeSizeSpan(.9f), 14, 25, 0) + s.setSpan(ForegroundColorSpan(Color.GRAY), 14, 25, 0) + s.setSpan(RelativeSizeSpan(1.4f), 25, s.length, 0) + s.setSpan(ForegroundColorSpan(holoBlue), 25, s.length, 0) + return s + } + + private class ViewHolder { + var chart: PieChart? = null + } +} diff --git a/app/src/main/java/info/appdev/chartexample/notimportant/DemoBase.kt b/app/src/main/java/info/appdev/chartexample/notimportant/DemoBase.kt index d9a372fe4e..7058d2e131 100644 --- a/app/src/main/java/info/appdev/chartexample/notimportant/DemoBase.kt +++ b/app/src/main/java/info/appdev/chartexample/notimportant/DemoBase.kt @@ -14,6 +14,8 @@ import com.github.mikephil.charting.charts.Chart import com.google.android.material.snackbar.Snackbar import info.appdev.chartexample.R import java.text.DateFormatSymbols +import androidx.core.view.size +import androidx.core.view.get abstract class DemoBase : AppCompatActivity(), ActivityCompat.OnRequestPermissionsResultCallback { @@ -41,14 +43,15 @@ abstract class DemoBase : AppCompatActivity(), ActivityCompat.OnRequestPermissio override fun onPrepareOptionsMenu(menu: Menu?): Boolean { menu?.let { - for (i in 0 until menu.size()) { - val menuItem: MenuItem = menu.getItem(i) + for (i in 0 until menu.size) { + val menuItem: MenuItem = menu[i] optionMenus.add(menuItem.title.toString()) } } return super.onPrepareOptionsMenu(menu) } + @Deprecated("This method has been deprecated in favor of using the\n {@link OnBackPressedDispatcher} via {@link #getOnBackPressedDispatcher()}.\n The OnBackPressedDispatcher controls how back button events are dispatched\n to one or more {@link OnBackPressedCallback} objects.") override fun onBackPressed() { super.onBackPressed() overridePendingTransition(R.anim.move_left_in_activity, R.anim.move_right_out_activity) @@ -82,7 +85,7 @@ abstract class DemoBase : AppCompatActivity(), ActivityCompat.OnRequestPermissio } } - protected fun saveToGallery(chart: Chart<*>?, name: String) { + protected fun saveToGallery(chart: Chart<*, *, *>?, name: String) { chart?.let { if (chart.saveToGallery(name + "_" + System.currentTimeMillis(), 70)) Toast.makeText(applicationContext, "Saving SUCCESSFUL!", Toast.LENGTH_SHORT).show() diff --git a/app/src/main/java/info/appdev/chartexample/notimportant/MainActivity.kt b/app/src/main/java/info/appdev/chartexample/notimportant/MainActivity.kt index fd4405ffec..78eddcdcbb 100644 --- a/app/src/main/java/info/appdev/chartexample/notimportant/MainActivity.kt +++ b/app/src/main/java/info/appdev/chartexample/notimportant/MainActivity.kt @@ -46,6 +46,7 @@ import info.appdev.chartexample.ScrollViewActivity import info.appdev.chartexample.SpecificPositionsLineChartActivity import info.appdev.chartexample.StackedBarActivity import info.appdev.chartexample.StackedBarActivityNegative +import androidx.core.net.toUri class MainActivity : AppCompatActivity(), OnItemClickListener { @@ -58,8 +59,8 @@ class MainActivity : AppCompatActivity(), OnItemClickListener { Utils.init(this) val adapter = MenuAdapter(this, menuItems) val lv = findViewById(R.id.listViewMain) - lv.adapter = adapter - lv.onItemClickListener = this + lv?.adapter = adapter + lv?.onItemClickListener = this } override fun onItemClick(av: AdapterView<*>?, v: View, pos: Int, arg3: Long) { @@ -78,7 +79,7 @@ class MainActivity : AppCompatActivity(), OnItemClickListener { when (item.itemId) { R.id.viewGithub -> { i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse("https://github.com/AppDevNext/AndroidChart") + i.data = "https://github.com/AppDevNext/AndroidChart".toUri() startActivity(i) } R.id.report -> { @@ -90,7 +91,7 @@ class MainActivity : AppCompatActivity(), OnItemClickListener { } R.id.website -> { i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse("http://at.linkedin.com/in/philippjahoda") + i.data = "http://at.linkedin.com/in/philippjahoda".toUri() startActivity(i) } } diff --git a/app/src/main/java/info/appdev/chartexample/notimportant/MenuAdapter.kt b/app/src/main/java/info/appdev/chartexample/notimportant/MenuAdapter.kt index 256da36206..f9cea8eaba 100644 --- a/app/src/main/java/info/appdev/chartexample/notimportant/MenuAdapter.kt +++ b/app/src/main/java/info/appdev/chartexample/notimportant/MenuAdapter.kt @@ -10,7 +10,7 @@ import android.widget.ArrayAdapter import android.widget.TextView import info.appdev.chartexample.R -internal class MenuAdapter(context: Context, objects: List?>?) : ArrayAdapter?>(context, 0, objects!!) { +internal class MenuAdapter(context: Context, objects: List>) : ArrayAdapter?>(context, 0, objects) { private val mTypeFaceLight: Typeface = Typeface.createFromAsset(context.assets, "OpenSans-Light.ttf") private val mTypeFaceRegular: Typeface = Typeface.createFromAsset(context.assets, "OpenSans-Regular.ttf")