diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/CanvasUtils.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/CanvasUtils.kt index d4dd4b62e..657664ded 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/CanvasUtils.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/CanvasUtils.kt @@ -113,14 +113,6 @@ fun Canvas.drawXAxisValue( paint.textAlign = originalTextAlign } -/** - * calculates the approximate width of a text, depending on a demo text - * avoid repeated calls (e.g. inside drawing methods) - */ -fun Paint.calcTextWidth(demoText: String?): Int { - return measureText(demoText).toInt() -} - /** * Returns a recyclable FSize instance. * Represents size of a rotated rectangle by degrees. diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/ContextUtils.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/ContextUtils.kt new file mode 100644 index 000000000..555c0b7b2 --- /dev/null +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/ContextUtils.kt @@ -0,0 +1,23 @@ +package info.appdev.charting.utils + +import android.content.Context +import android.os.Build +import android.util.DisplayMetrics +import android.view.ViewConfiguration +import kotlin.Float + +var metrics: DisplayMetrics? = null +var minimumFlingVelocity = 0 +var maximumFlingVelocity = 0 + +fun Context.initUtils() { + val viewConfiguration = ViewConfiguration.get(this) + minimumFlingVelocity = viewConfiguration.scaledMinimumFlingVelocity + maximumFlingVelocity = viewConfiguration.scaledMaximumFlingVelocity + + metrics = this.resources.displayMetrics +} + +fun getSDKInt() = Build.VERSION.SDK_INT + +fun Context.convertDpToPixel(dp: Float) = dp * this.resources.displayMetrics.density diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/NumberUtils.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/NumberUtils.kt index fb7570838..920a7d953 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/NumberUtils.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/NumberUtils.kt @@ -1,5 +1,139 @@ package info.appdev.charting.utils +import timber.log.Timber +import kotlin.Boolean +import kotlin.Char +import kotlin.CharArray +import kotlin.Float +import kotlin.Int +import kotlin.String +import kotlin.code +import kotlin.isInfinite +import kotlin.math.ceil +import kotlin.math.log10 +import kotlin.math.pow +import kotlin.math.roundToInt + +/** + * Returns the appropriate number of decimals to be used for the provided number. + */ +fun Float.getDecimals(): Int { + val i = this.toDouble().roundToNextSignificant() + + if (i.isInfinite()) { + return 0 + } + + return ceil(-log10(i.toDouble())).toInt() + 2 +} + +/** + * rounds the given number to the next significant number + */ +fun Double.roundToNextSignificant(): Float { + if (this.isInfinite() || + this.isNaN() || this == 0.0 + ) { + return 0f + } + + val d = ceil(log10(if (this < 0) -this else this).toFloat().toDouble()).toFloat() + val pw = 1 - d.toInt() + val magnitude = 10.0.pow(pw.toDouble()).toFloat() + val shifted = (this * magnitude).roundToInt() + return shifted / magnitude +} + +/** + * This method converts dp unit to equivalent pixels, depending on device + * density. NEEDS UTILS TO BE INITIALIZED BEFORE USAGE. + * + * 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 + */ +fun Float.convertDpToPixel(): Float { + if (metrics == null) { + Timber.e("Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before calling Utils.convertDpToPixel(...). Otherwise conversion does not take place.") + return this + } else + return this * metrics!!.density +} + +fun Float.formatNumber(digitCount: Int, separateThousands: Boolean, separateChar: Char = '.'): String { + var number = this + 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 > Utils.POW_10.size) { + digitCount = Utils.POW_10.size - 1 + } + + number *= Utils.POW_10[digitCount].toFloat() + var lVal = Math.round(number).toLong() + var ind = out.size - 1 + var charCount = 0 + var decimalPointAdded = false + + while (lVal != 0L || charCount < (digitCount + 1)) { + val digit = (lVal % 10).toInt() + 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) +} + /** * returns an angle between 0.f < 360.f (not less than zero, less than 360) */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/PaintUtils.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/PaintUtils.kt new file mode 100644 index 000000000..277e26ae9 --- /dev/null +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/PaintUtils.kt @@ -0,0 +1,74 @@ +package info.appdev.charting.utils + +import android.graphics.Paint +import android.graphics.Rect + +/** + * calculates the approximate width of a text, depending on a demo text + * avoid repeated calls (e.g. inside drawing methods) + */ +fun Paint.calcTextWidth(demoText: String?): Int { + return measureText(demoText).toInt() +} + +private val mCalcTextHeightRect = Rect() + +/** + * calculates the approximate height of a text, depending on a demo text + * avoid repeated calls (e.g. inside drawing methods) + */ +fun Paint.calcTextHeight(demoText: String): Int { + val r = mCalcTextHeightRect + r.set(0, 0, 0, 0) + this.getTextBounds(demoText, 0, demoText.length, r) + return r.height() +} + +private val mFontMetrics = Paint.FontMetrics() + +fun Paint.getLineHeight(): Float { + return this.getLineHeight(mFontMetrics) +} + +fun Paint.getLineHeight(fontMetrics: Paint.FontMetrics): Float { + this.getFontMetrics(fontMetrics) + return fontMetrics.descent - fontMetrics.ascent +} + +fun Paint.getLineSpacing(): Float { + return this.getLineSpacing(mFontMetrics) +} + +fun Paint.getLineSpacing(fontMetrics: Paint.FontMetrics): Float { + this.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) + * + * @return A Recyclable FSize instance + */ +fun Paint.calcTextSize(demoText: String): FSize { + val result = FSize.getInstance(0f, 0f) + calcTextSize(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 outputFSize An output variable, modified by the function. + */ +fun Paint.calcTextSize(demoText: String, outputFSize: FSize) { + val r = mCalcTextSizeRect + r.set(0, 0, 0, 0) + this.getTextBounds(demoText, 0, demoText.length, r) + outputFSize.width = r.width().toFloat() + outputFSize.height = r.height().toFloat() +} diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/UtilsKt.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/UtilsKt.kt deleted file mode 100644 index b6760d4d2..000000000 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/UtilsKt.kt +++ /dev/null @@ -1,218 +0,0 @@ -package info.appdev.charting.utils - -import android.content.Context -import android.graphics.Paint -import android.graphics.Rect -import android.os.Build -import android.util.DisplayMetrics -import android.view.ViewConfiguration -import timber.log.Timber -import kotlin.Boolean -import kotlin.Char -import kotlin.CharArray -import kotlin.Float -import kotlin.Int -import kotlin.String -import kotlin.code -import kotlin.math.ceil -import kotlin.math.log10 -import kotlin.math.pow -import kotlin.math.roundToInt - -var metrics: DisplayMetrics? = null -var minimumFlingVelocity = 0 -var maximumFlingVelocity = 0 - -fun Context.initUtils() { - val viewConfiguration = ViewConfiguration.get(this) - minimumFlingVelocity = viewConfiguration.scaledMinimumFlingVelocity - maximumFlingVelocity = viewConfiguration.scaledMaximumFlingVelocity - - metrics = this.resources.displayMetrics -} - -/** - * Returns the appropriate number of decimals to be used for the provided number. - */ -fun Float.getDecimals(): Int { - val i = this.toDouble().roundToNextSignificant() - - if (i.isInfinite()) { - return 0 - } - - return ceil(-log10(i.toDouble())).toInt() + 2 -} - -/** - * rounds the given number to the next significant number - */ -fun Double.roundToNextSignificant(): Float { - if (this.isInfinite() || - this.isNaN() || this == 0.0 - ) { - return 0f - } - - val d = ceil(log10(if (this < 0) -this else this).toFloat().toDouble()).toFloat() - val pw = 1 - d.toInt() - val magnitude = 10.0.pow(pw.toDouble()).toFloat() - val shifted = (this * magnitude).roundToInt() - return shifted / magnitude -} - -/** - * This method converts dp unit to equivalent pixels, depending on device - * density. NEEDS UTILS TO BE INITIALIZED BEFORE USAGE. - * - * 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 - */ -fun Float.convertDpToPixel(): Float { - if (metrics == null) { - Timber.e("Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before calling Utils.convertDpToPixel(...). Otherwise conversion does not take place.") - return this - } else - return this * metrics!!.density -} - -fun getSDKInt() = Build.VERSION.SDK_INT - -fun Context.convertDpToPixel(dp: Float) = dp * this.resources.displayMetrics.density - -fun Float.formatNumber(digitCount: Int, separateThousands: Boolean, separateChar: Char = '.'): String { - var number = this - 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 > Utils.POW_10.size) { - digitCount = Utils.POW_10.size - 1 - } - - number *= Utils.POW_10[digitCount].toFloat() - var lval = Math.round(number).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) -} - -private val mCalcTextHeightRect = Rect() - -/** - * calculates the approximate height of a text, depending on a demo text - * avoid repeated calls (e.g. inside drawing methods) - */ -fun Paint.calcTextHeight(demoText: String): Int { - val r = mCalcTextHeightRect - r.set(0, 0, 0, 0) - this.getTextBounds(demoText, 0, demoText.length, r) - return r.height() -} - -private val mFontMetrics = Paint.FontMetrics() - -fun Paint.getLineHeight(): Float { - return this.getLineHeight(mFontMetrics) -} - -fun Paint.getLineHeight(fontMetrics: Paint.FontMetrics): Float { - this.getFontMetrics(fontMetrics) - return fontMetrics.descent - fontMetrics.ascent -} - -fun Paint.getLineSpacing(): Float { - return this.getLineSpacing(mFontMetrics) -} - -fun Paint.getLineSpacing(fontMetrics: Paint.FontMetrics): Float { - this.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) - * - * @return A Recyclable FSize instance - */ -fun Paint.calcTextSize(demoText: String): FSize { - val result = FSize.getInstance(0f, 0f) - calcTextSize(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 outputFSize An output variable, modified by the function. - */ -fun Paint.calcTextSize(demoText: String, outputFSize: FSize) { - val r = mCalcTextSizeRect - r.set(0, 0, 0, 0) - this.getTextBounds(demoText, 0, demoText.length, r) - outputFSize.width = r.width().toFloat() - outputFSize.height = r.height().toFloat() -}