package com.github.mikephil.charting.utils; import android.content.res.Resources; import android.graphics.Paint; import android.graphics.PointF; import android.graphics.Rect; import android.util.DisplayMetrics; import android.util.Log; import com.github.mikephil.charting.components.YAxis.AxisDependency; import java.text.DecimalFormat; 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 */ public abstract class Utils { private static DisplayMetrics mMetrics; /** * initialize method, called inside the Chart.init() method. * * @param res */ public static void init(Resources res) { mMetrics = res.getDisplayMetrics(); } /** * format a number properly with the given number of digits * * @param number the number to format * @param digits the number of digits * @return */ public static String formatDecimal(double number, int digits) { StringBuffer a = new StringBuffer(); for (int i = 0; i < digits; i++) { if (i == 0) a.append("."); a.append("0"); } DecimalFormat nf = new DecimalFormat("###,###,###,##0" + a.toString()); String formatted = nf.format(number); return formatted; } /** * 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; // throw new IllegalStateException( // "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before calling Utils.convertDpToPixel(...)."); } DisplayMetrics metrics = mMetrics; float px = dp * (metrics.densityDpi / 160f); return px; } /** * 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; // throw new IllegalStateException( // "Utils NOT INITIALIZED. You need to call Utils.init(...) at least once before calling Utils.convertPixelsToDp(...)."); } DisplayMetrics metrics = mMetrics; float dp = px / (metrics.densityDpi / 160f); return dp; } /** * 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); } /** * 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 = new Rect(); paint.getTextBounds(demoText, 0, demoText.length(), r); return r.height(); } // /** // * returns the appropriate number of format digits for a delta value // * // * @param delta // * @return // */ // public static int getFormatDigits(float delta) { // // if (delta < 0.1) { // return 6; // } else if (delta <= 1) { // return 4; // } else if (delta < 4) { // return 3; // } else if (delta < 20) { // return 2; // } else if (delta < 60) { // return 1; // } else { // return 0; // } // } /** * returns the appropriate number of format digits for a legend value * * @param delta * @param bonus - additional digits * @return */ public static int getLegendFormatDigits(float step, int bonus) { if (step < 0.0000099) { return 6 + bonus; } else if (step < 0.000099) { return 5 + bonus; } else if (step < 0.00099) { return 4 + bonus; } else if (step < 0.0099) { return 3 + bonus; } else if (step < 0.099) { return 2 + bonus; } else if (step < 0.99) { return 1 + bonus; } else { return 0 + bonus; } } /** * 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 }; /** * 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 separateTousands set this to true to separate thousands values * @return */ public static String formatNumber(float number, int digitCount, boolean separateThousands) { char[] out = new char[35]; boolean neg = false; if (number == 0) { return "0"; } boolean zero = false; if (number < 1 && number > -1) { zero = true; } 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--] = '.'; charCount++; } } else { if ((charCount - digitCount) % 4 == 3) { out[ind--] = '.'; 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) { 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); 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<Integer> integers) { int[] ret = new int[integers.size()]; for (int i = 0; i < ret.length; i++) { ret[i] = integers.get(i).intValue(); } return ret; } /** * Converts the provided String List to a String array. * * @param labels * @return */ public static String[] convertStrings(List<String> 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 the index of the DataSet that contains the closest value on the * y-axis. This is needed for highlighting. * * @param valsAtIndex all the values at a specific index * @return */ public static int getClosestDataSetIndex(List<SelInfo> valsAtIndex, float val, AxisDependency axis) { int index = -1; float distance = Float.MAX_VALUE; for (int i = 0; i < valsAtIndex.size(); i++) { SelInfo sel = valsAtIndex.get(i); if (axis == null || sel.dataSet.getAxisDependency() == axis) { float cdistance = Math.abs((float) sel.val - val); if (cdistance < distance) { index = valsAtIndex.get(i).dataSetIndex; distance = cdistance; } } } // Log.i(LOG_TAG, "Closest DataSet index: " + index); return index; } /** * Returns the minimum distance from a touch-y-value (in pixels) to the * closest y-value (in pixels) that is displayed in the chart. * * @param valsAtIndex * @param val * @param axis * @return */ public static float getMinimumDistance(List<SelInfo> valsAtIndex, float val, AxisDependency axis) { float distance = Float.MAX_VALUE; for (int i = 0; i < valsAtIndex.size(); i++) { SelInfo sel = valsAtIndex.get(i); if (sel.dataSet.getAxisDependency() == axis) { float cdistance = Math.abs((float) sel.val - val); if (cdistance < distance) { distance = cdistance; } } } return distance; } /** * 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 PointF getPosition(PointF center, float dist, float angle) { PointF p = new PointF((float) (center.x + dist * Math.cos(Math.toRadians(angle))), (float) (center.y + dist * Math.sin(Math.toRadians(angle)))); return p; } }