package pt.rupeal.invoicexpress.charts; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.SortedMap; import java.util.SortedSet; import java.util.TreeSet; import org.achartengine.chart.BarChart; import org.achartengine.model.XYMultipleSeriesDataset; import org.achartengine.model.XYSeries; import org.achartengine.renderer.DefaultRenderer; import org.achartengine.renderer.SimpleSeriesRenderer; import org.achartengine.renderer.XYMultipleSeriesRenderer; import org.achartengine.util.MathHelper; import pt.rupeal.invoicexpress.R; import pt.rupeal.invoicexpress.enums.FragmentTagsEnum; import pt.rupeal.invoicexpress.utils.ScreenLayoutUtil; import android.app.Activity; import android.app.Fragment; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Align; import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.Typeface; public class BarChartInvoiceXpress extends BarChart { private static final long serialVersionUID = 4326070658495433442L; private Context context; public BarChartInvoiceXpress(Context context, XYMultipleSeriesDataset dataset, XYMultipleSeriesRenderer renderer, Type type) { super(dataset, renderer, type); this.context = context; } @Override public void draw(Canvas canvas, int x, int y, int width, int height, Paint paint) { // disable draw when progress bar is visible Fragment progressBar = ((Activity) context).getFragmentManager().findFragmentByTag(FragmentTagsEnum.DIALOG_PROGRESS.getValue()); if(progressBar!= null && progressBar.isVisible()) { return; } paint.setAntiAlias(true); // design the main XY chart rect int legendSize = getLegendSize(mRenderer, height / 5, mRenderer.getAxisTitleTextSize()); // calculate left, top, right and bottom chart limits int[] margins = mRenderer.getMargins(); int left = x + margins[1]; int top = y + margins[0]; int right = x + width - margins[3]; int bottom = y + height - margins[2] - legendSize; // set rect screen if (getScreenR() == null) { setScreenR(new Rect()); } // rectangle for XY chart // getScreenR().set(left, top, right, bottom); getScreenR().set(left, top, right, bottom); drawBackground(mRenderer, canvas, x, y, width, height, paint, false, DefaultRenderer.NO_COLOR); if (paint.getTypeface() == null || !paint.getTypeface().toString().equals(mRenderer.getTextTypefaceName()) || paint.getTypeface().getStyle() != mRenderer.getTextTypefaceStyle()) { paint.setTypeface(Typeface.create(mRenderer.getTextTypefaceName(), mRenderer.getTextTypefaceStyle())); } // draw stuff over the margins such as data doesn't render on these areas drawBackground(mRenderer, canvas, x, bottom, width, height - bottom, paint, true, mRenderer.getMarginsColor()); drawBackground(mRenderer, canvas, x, y, width, margins[0], paint, true, mRenderer.getMarginsColor()); drawBackground(mRenderer, canvas, x, y, left - x, height - y, paint, true, mRenderer.getMarginsColor()); drawBackground(mRenderer, canvas, right, y, margins[3], height - y, paint, true, mRenderer.getMarginsColor()); // draw Chart Title float heightChartTitle = y + ScreenLayoutUtil.convertDpToPixels(context, 30); drawChartTitle(canvas, x + width / 2, heightChartTitle, paint); // set new top value based on the height Chart Title top = (int) (heightChartTitle + ScreenLayoutUtil.convertDpToPixels(context, 30)); // X and Y values double minX = mRenderer.getXAxisMin(); double maxX = mRenderer.getXAxisMax(); double minY = mRenderer.getYAxisMin(); double maxY = mRenderer.getYAxisMax(); // y height pixels float yPixelsPerUnit = (float) ((bottom - top) / (maxY - minY)); // x width pixels float xPixelsPerUnit = (float) ((right - left) / (maxX - minX)); // the bottom dosen't work for negative values // we have to create first line y attribute // drawYTextLabels float gridXfirstLineY = drawYLabels(canvas, left, right, bottom, minY, yPixelsPerUnit, paint); // save the base line y // for the first series the array just only has gridXfirstLineY value // the map saves the last top y value used by last bar Map<Double, Float> baseLineY = new HashMap<Double, Float>(); for (double key = 1; key <= maxX; key++) { baseLineY.put(Double.valueOf(key), Float.valueOf(gridXfirstLineY)); } // draw series SortedSet<Double> xBarPoints = new TreeSet<Double>(); int sLength = mDataset.getSeriesCount(); for (int i = 0; i < sLength; i++) { XYSeries series = mDataset.getSeriesAt(i); if (series.getItemCount() == 0) { continue; } SimpleSeriesRenderer seriesRenderer = mRenderer.getSeriesRendererAt(i); // points X and Y bar List<Float> points = new ArrayList<Float>(2); // range, key X value (1, 2, 3, ..) and value Y value SortedMap<Double, Double> range = series.getRange(minX, maxX, 1); // draw series bar, initiate x point double pointX = left; for (Entry<Double, Double> value : range.entrySet()) { // point X points.add((float) pointX); xBarPoints.add(pointX); // set the next x point pointX = pointX + xPixelsPerUnit; // point Y double yValue = value.getValue(); // draw series if yValue != 0 if(yValue != 0) { // get last point y float yMinValue = baseLineY.get(value.getKey()); // calculate new last point y if the y Value is positive and the last one is negative or vice versa if((yMinValue > gridXfirstLineY && yValue > 0) || (yMinValue < gridXfirstLineY && yValue < 0)) { yMinValue = gridXfirstLineY; } // calculate point y based on bottom value float pointY = (float) (bottom - yPixelsPerUnit * (yValue - minY)); // the new point y is the last calculated point y and the difference between min y value and the y line 0 pointY += yMinValue - gridXfirstLineY; points.add(pointY); // draw series drawSeries(canvas, paint, MathHelper.getFloats(points), seriesRenderer, yMinValue, xPixelsPerUnit); baseLineY.put(value.getKey(), Float.valueOf(pointY)); } points.clear(); } } // draw 0 grid X line // draw over then chart bars paint.setColor(mRenderer.getGridColor()); canvas.drawRect(left, gridXfirstLineY - 1, right, gridXfirstLineY + 1, paint); // draw X Labels drawXTextLabels(canvas, paint, xBarPoints, left, right, top, bottom, xPixelsPerUnit); // draw legend drawLegend(canvas, mRenderer, 0, right, y, width, height, legendSize, paint); } private void drawChartTitle(Canvas canvas, float x, float y, Paint paint) { paint.setTextAlign(Align.CENTER); paint.setColor(mRenderer.getLabelsColor()); paint.setTextSize(ScreenLayoutUtil.convertSpToPixels(context, 12)); canvas.drawText(mRenderer.getChartTitle(), x, y, paint); } private float drawYLabels(Canvas canvas, int left, int right, int bottom, double minY, float yPixelsPerUnit, Paint paint) { float gridXfirstLineY = 0; Double[] yTextLabelLocations = mRenderer.getYTextLabelLocations(); for (Double location : yTextLabelLocations) { float yLabel = (float) (bottom - yPixelsPerUnit * (location.doubleValue() - minY)); String label = location == 0 ? "0" : mRenderer.getYTextLabel(location); paint.setTextSize(mRenderer.getLabelsTextSize()); paint.setTextAlign(Align.RIGHT); paint.setColor(mRenderer.getLabelsColor()); // draw Y text Label int xLabel = left - (int) mRenderer.getLabelsTextSize(); canvas.drawText(label, xLabel, yLabel + (int) mRenderer.getLabelsTextSize() / 3, paint); // draw grid X paint.setColor(mRenderer.getGridColor()); int heightLineX = ScreenLayoutUtil.isLowerThanHdpi(context) ? 1 : 2; canvas.drawRect(left, yLabel - heightLineX, right, yLabel + heightLineX, paint); // the gridXfirstLineY var is used to draw the grid x line after then all series bar if(location == 0) { gridXfirstLineY = yLabel; } } return gridXfirstLineY; } private void drawSeries(Canvas canvas, Paint paint, float[] points, SimpleSeriesRenderer seriesRenderer, float yAxisValue, float xPixelsPerUnit) { // calculate margins float marginLeft = xPixelsPerUnit * 0.1f; float marginRight = xPixelsPerUnit * 0.1f; int length = points.length; paint.setColor(seriesRenderer.getColor()); paint.setStyle(Style.FILL); for (int i = 0; i < length; i += 2) { float x = points[i]; float y = points[i + 1]; drawBar(canvas, x + marginLeft, yAxisValue, x + (xPixelsPerUnit - marginRight), y, paint); } } private void drawBar(Canvas canvas, float xMin, float yMin, float xMax, float yMax, Paint paint) { if (Math.abs(yMin - yMax) < 1) { if (yMin < yMax) { yMax = yMin + 1; } else { yMax = yMin - 1; } } int top = Math.round(yMax); int bottom = Math.round(yMin); if(top > bottom) { int aux = top; top = bottom; bottom = aux; } canvas.drawRect(Math.round(xMin), top, Math.round(xMax), bottom, paint); } private void drawXTextLabels(Canvas canvas, Paint paint, SortedSet<Double> xBarPoints, int left, int right, int top, int bottom, float xPixelsPerUnit) { Double[] xPoints = new Double[xBarPoints.size()]; xBarPoints.toArray(xPoints); paint.setTextSize(mRenderer.getLabelsTextSize()); paint.setTextAlign(mRenderer.getXLabelsAlign()); paint.setColor(mRenderer.getLabelsColor()); for (int i = 1; i <= xBarPoints.size(); i++) { canvas.drawText(mRenderer.getXTextLabel(Double.valueOf(i)), Math.round(xPoints[i-1]) + (xPixelsPerUnit / 2), bottom + mRenderer.getLabelsTextSize() + ScreenLayoutUtil.convertDpToPixels(context, 5), paint); } } private void drawLegend(Canvas canvas, DefaultRenderer renderer, int left, int right, int y, int width, int height, int legendSize, Paint paint) { // draw top line paint.setColor(context.getResources().getColor(R.color.line_seperator_top_black)); canvas.drawLine(left, height - legendSize - 2, width, height - legendSize - 3, paint); // draw bottom line paint.setColor(context.getResources().getColor(R.color.line_seperator_bottom_black)); canvas.drawLine(left, height - legendSize, width, height - legendSize - 1, paint); // continue.. paint.setColor(((BarChartInvoiceXpressRenderer) renderer).getLegendBackgroundColor()); // draw legend rectangle - height calculated by legendSize canvas.drawRect(left, height, width, height - legendSize, paint); paint.setTextAlign(Align.CENTER); float textSize = ScreenLayoutUtil.convertSpToPixels(context, 12); paint.setTextSize(textSize); // get series count int numberOfSeries = mDataset.getSeriesCount(); // divide legends int widthForEachLegend = width / numberOfSeries; // calculate y value for legend data float yCircleLegend = (height - legendSize) + (legendSize / 2) + ScreenLayoutUtil.convertDpToPixels(context, 0); float yLabelLegend = (height - legendSize) + (legendSize / 2) + ScreenLayoutUtil.convertDpToPixels(context, 4); // set radius float radius = ScreenLayoutUtil.convertDpToPixels(context, 3); float xLabelLegend; float xCircleLegend; for (int i = 0; i < numberOfSeries; i++) { paint.setColor(renderer.getLabelsColor()); xLabelLegend = (widthForEachLegend / 2) * ((i * 2) + 1); String labelLegend = mDataset.getSeriesAt(i).getTitle(); canvas.drawText(labelLegend, xLabelLegend, yLabelLegend, paint); float labelWidth = paint.measureText(labelLegend, 0, labelLegend.length()); paint.setColor(renderer.getSeriesRendererAt(i).getColor()); xCircleLegend = xLabelLegend - ScreenLayoutUtil.convertDpToPixels(context, 8) - (labelWidth / 2); canvas.drawCircle(xCircleLegend, yCircleLegend, radius, paint); } } }