package pt.rupeal.invoicexpress.charts; import java.text.NumberFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.List; import org.achartengine.renderer.XYMultipleSeriesRenderer; import pt.rupeal.invoicexpress.R; import pt.rupeal.invoicexpress.server.InvoiceXpress; import pt.rupeal.invoicexpress.utils.StringUtil; import android.content.Context; public class BarChartInvoiceXpressRenderer extends XYMultipleSeriesRenderer { private static final long serialVersionUID = -8797680821311119577L; private float barWidth; private int legendBackgroundColor; public BarChartInvoiceXpressRenderer(Context context, int width, int height, double[] ...values) { setBarSpacing(0.5); setChartTitle(context.getResources().getString(R.string.dashboard_title) + " " + InvoiceXpress.getInstance().getActiveAccountDetails().getCurrencySymbol()); setXLabels(0); // Invoicing chart length will be 6 // Treasury chart length will be 7 int yLabelsCounter = values[0].length; clearXTextLabels(); addXTextLabel(0, ""); // x min axis value, always 0 setXAxisMin(0); // init and fill array to find the min and max values for Y axis double[] yAxisMinArray = initArrayYMinMax(yLabelsCounter); double[] yAxisMaxArray = initArrayYMinMax(yLabelsCounter); fillArrayYMinMax(yAxisMinArray, yAxisMaxArray, values); // get min e max for Y axis double yAxisMin = getMinFromArrayY(yAxisMinArray); double yAxisMax = getMaxFromArrayY(yAxisMaxArray); // set number of Y axis labels // setYLabels(yAxisMin == 0 ? yLabelsCounter + 1 : yLabelsCounter + 2); setYLabels(yLabelsCounter); double[] labelParams = computeLabels(yAxisMin, yAxisMax, getYLabels()); // set the Y min e max values after labels generation // this allow us to have grid X after y values // y max axis value setYAxisMax(labelParams[1]); // y min axis value setYAxisMin(labelParams[0]); // get y label length and convert it to thousand or million or billion List<Double> yLabels = getLabels(labelParams); for (Double yLabel : yLabels) { addYTextLabel(yLabel, StringUtil.convertNumberToThounsandMillionBillion(yLabel, true)); } // set label text size X & Y setLabelsTextSize(height / 50); setLabelsColor(context.getResources().getColor(R.color.dashboard_labels)); setApplyBackgroundColor(true); setBackgroundColor(context.getResources().getColor(R.color.background)); setMarginsColor(context.getResources().getColor(R.color.background)); setShowAxes(false); // top, left, bottom right // this value should be calculated since the width and height values // my device has width 720 and height 1280, but with my action bar we have height 882 // setMargins(new int[]{height / 8, width / 5, height / 12, width / 8}); setMargins(new int[]{0, width / 5, height / 12, width / 8}); // disable pan and zoom setPanEnabled(false, false); setZoomEnabled(false, false); setShowGridX(true); setGridColor(context.getResources().getColor(R.color.dashboard_line_horizontal)); // show legend and properties (text size and background) setShowLegend(true); setLegendHeight(height / 16); setLegendBackgroundColor(context.getResources().getColor(R.color.dashboard_legend_background)); } private static double[] initArrayYMinMax(int length) { double[] yAxisArray = new double[length]; for (int i = 0; i < yAxisArray.length; i++) { yAxisArray[i] = 0; } return yAxisArray; } private static void fillArrayYMinMax(double[] yAxisMinArray, double[] yAxisMaxArray, double[] ...values) { for (int i = 0; i < values.length; i++) { for (int j = 0; j < values[i].length; j++) { if(values[i][j] < 0) { yAxisMinArray[j] += values[i][j]; } if(values[i][j] > 0) { yAxisMaxArray[j] += values[i][j]; } } } } private static double getMinFromArrayY(double[] yAxisArray) { double yMin = 0; for (int i = 0; i < yAxisArray.length; i++) { yMin = Math.min(yMin, yAxisArray[i]); } return yMin; } private static double getMaxFromArrayY(double[] yAxisArray) { double yMax = 0; for (int i = 0; i < yAxisArray.length; i++) { yMax = Math.max(yMax, yAxisArray[i]); } return yMax; } public float getBarWidth() { return barWidth; } public void setBarWidth(float barWidth) { this.barWidth = barWidth; } public int getLegendBackgroundColor() { return legendBackgroundColor; } public void setLegendBackgroundColor(int legendBackgroundColor) { this.legendBackgroundColor = legendBackgroundColor; } private static final NumberFormat FORMAT = NumberFormat.getNumberInstance(); /** * Computes a reasonable set of labels for a data interval and number of * labels. * * @param start start value * @param end final value * @param approxNumLabels desired number of labels * @return collection containing {start value, end value, increment} */ public static List<Double> getLabels(final double[] labelParams) { FORMAT.setMaximumFractionDigits(5); List<Double> labels = new ArrayList<Double>(); // when the start > end the inc will be negative so it will still work int numLabels = 1 + (int) ((labelParams[1] - labelParams[0]) / labelParams[2]); // we want the range to be inclusive but we don't want to blow up when // looping for the case where the min and max are the same. So we loop // on // numLabels not on the values. for (int i = 0; i < numLabels; i++) { double z = labelParams[0] + i * labelParams[2]; try { // this way, we avoid a label value like 0.4000000000000000001 instead // of 0.4 z = FORMAT.parse(FORMAT.format(z)).doubleValue(); } catch (ParseException e) { // do nothing here } labels.add(z); } return labels; } /** * Computes a reasonable number of labels for a data range. * * @param start start value * @param end final value * @param approxNumLabels desired number of labels * @return double[] array containing {start value, end value, increment} */ private static double[] computeLabels(final double start, final double end, final int approxNumLabels) { if (Math.abs(start - end) < 0.0000001f) { return new double[] { start, start, 0 }; } double s = start; double e = end; // calculate step between Y values double yStep = roundUp(Math.abs(s - e) / approxNumLabels); // calculate y start value // the y start value has to be always minor then s double yStart = 0; if(s == 0) { yStart = 0; } else if(s < 0) { while(yStart > s) { yStart -= yStep; } } // calculate y end value // the y end value has to be always greater then e double yEnd = yStep * Math.round(e / yStep); while(yEnd < e) { yEnd += yStep; } // return start, end and step y values return new double[] { yStart, yEnd, yStep }; } /** * Given a number, round up to the nearest power of ten times 1, 2, or 5. The * argument must be strictly positive. */ private static double roundUp(final double val) { int exponent = (int) Math.floor(Math.log10(val)); double rval = val * Math.pow(10, -exponent); if (rval > 5.0) { rval = 10.0; } else if (rval > 2.0) { rval = 5.0; } else if (rval > 1.0) { rval = 2.0; } rval *= Math.pow(10, exponent); return rval; } }