package com.github.mikephil.charting.renderer; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import com.github.mikephil.charting.animation.ChartAnimator; import com.github.mikephil.charting.buffer.BarBuffer; 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.BarDataProvider; import com.github.mikephil.charting.utils.Highlight; import com.github.mikephil.charting.utils.Transformer; import com.github.mikephil.charting.utils.Utils; import com.github.mikephil.charting.utils.ValueFormatter; import com.github.mikephil.charting.utils.ViewPortHandler; import java.util.List; public class BarChartRenderer extends DataRenderer { protected BarDataProvider mChart; /** the rect object that is used for drawing the bars */ protected RectF mBarRect = new RectF(); protected BarBuffer[] mBarBuffers; protected Paint mShadowPaint; public BarChartRenderer(BarDataProvider chart, ChartAnimator animator, ViewPortHandler viewPortHandler) { super(animator, viewPortHandler); this.mChart = chart; mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mHighlightPaint.setStyle(Paint.Style.FILL); mHighlightPaint.setColor(Color.rgb(0, 0, 0)); // set alpha after color mHighlightPaint.setAlpha(120); mShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mShadowPaint.setStyle(Paint.Style.FILL); } @Override public void initBuffers() { BarData barData = mChart.getBarData(); mBarBuffers = new BarBuffer[barData.getDataSetCount()]; for (int i = 0; i < mBarBuffers.length; i++) { BarDataSet set = barData.getDataSetByIndex(i); mBarBuffers[i] = new BarBuffer(set.getValueCount() * 4 * set.getStackSize(), barData.getGroupSpace(), barData.getDataSetCount(), set.isStacked()); } } @Override public void drawData(Canvas c) { BarData barData = mChart.getBarData(); for (int i = 0; i < barData.getDataSetCount(); i++) { BarDataSet set = barData.getDataSetByIndex(i); if (set.isVisible()) { drawDataSet(c, set, i); } } } protected void drawDataSet(Canvas c, BarDataSet dataSet, int index) { Transformer trans = mChart.getTransformer(dataSet.getAxisDependency()); calcXBounds(trans); mShadowPaint.setColor(dataSet.getBarShadowColor()); float phaseX = mAnimator.getPhaseX(); float phaseY = mAnimator.getPhaseY(); List<BarEntry> entries = dataSet.getYVals(); // initialize the buffer BarBuffer buffer = mBarBuffers[index]; buffer.setPhases(phaseX, phaseY); buffer.setBarSpace(dataSet.getBarSpace()); buffer.setDataSet(index); buffer.setInverted(mChart.isInverted(dataSet.getAxisDependency())); buffer.feed(entries); trans.pointValuesToPixel(buffer.buffer); // if multiple colors if (dataSet.getColors().size() > 1) { for (int j = 0; j < buffer.size(); j += 4) { if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) continue; if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) break; if (mChart.isDrawBarShadowEnabled()) { c.drawRect(buffer.buffer[j], mViewPortHandler.contentTop(), buffer.buffer[j + 2], mViewPortHandler.contentBottom(), mShadowPaint); } // Set the color for the currently drawn value. If the index // is // out of bounds, reuse colors. mRenderPaint.setColor(dataSet.getColor(j / 4)); c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3], mRenderPaint); } } else { mRenderPaint.setColor(dataSet.getColor()); for (int j = 0; j < buffer.size(); j += 4) { if (!mViewPortHandler.isInBoundsLeft(buffer.buffer[j + 2])) continue; if (!mViewPortHandler.isInBoundsRight(buffer.buffer[j])) break; if (mChart.isDrawBarShadowEnabled()) { c.drawRect(buffer.buffer[j], mViewPortHandler.contentTop(), buffer.buffer[j + 2], mViewPortHandler.contentBottom(), mShadowPaint); } c.drawRect(buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3], mRenderPaint); } } } /** * Prepares a bar for being highlighted. * * @param x the x-position * @param y the y-position * @param barspace the space between bars * @param from * @param trans */ protected void prepareBarHighlight(float x, float y, float barspaceHalf, float from, Transformer trans) { float barWidth = 0.5f; float left = x - barWidth + barspaceHalf; float right = x + barWidth - barspaceHalf; float top = y >= from ? y : from; float bottom = y <= from ? y : from; mBarRect.set(left, top, right, bottom); trans.rectValueToPixel(mBarRect, mAnimator.getPhaseY()); } @Override public void drawValues(Canvas c) { // if values are drawn if (passesCheck()) { List<BarDataSet> dataSets = mChart.getBarData().getDataSets(); final float valueOffsetPlus = Utils.convertDpToPixel(5f); float posOffset = 0f; float negOffset = 0f; boolean drawValueAboveBar = mChart.isDrawValueAboveBarEnabled(); for (int i = 0; i < mChart.getBarData().getDataSetCount(); i++) { BarDataSet dataSet = dataSets.get(i); if (!dataSet.isDrawValuesEnabled()) continue; boolean isInverted = mChart.isInverted(dataSet.getAxisDependency()); // calculate the correct offset depending on the draw position of // the value float valueTextHeight = Utils.calcTextHeight(mValuePaint, "8"); posOffset = (drawValueAboveBar ? -valueOffsetPlus : valueTextHeight + valueOffsetPlus); negOffset = (drawValueAboveBar ? valueTextHeight + valueOffsetPlus : -valueOffsetPlus); if (isInverted) { posOffset = -posOffset - valueTextHeight; negOffset = -negOffset - valueTextHeight; } // apply the text-styling defined by the DataSet applyValueTextStyle(dataSet); ValueFormatter formatter = dataSet.getValueFormatter(); Transformer trans = mChart.getTransformer(dataSet.getAxisDependency()); List<BarEntry> entries = dataSet.getYVals(); float[] valuePoints = getTransformedValues(trans, entries, i); // if only single values are drawn (sum) if (!mChart.isDrawValuesForWholeStackEnabled()) { for (int j = 0; j < valuePoints.length * mAnimator.getPhaseX(); j += 2) { if (!mViewPortHandler.isInBoundsRight(valuePoints[j])) break; if (!mViewPortHandler.isInBoundsY(valuePoints[j + 1]) || !mViewPortHandler.isInBoundsLeft(valuePoints[j])) continue; float val = entries.get(j / 2).getVal(); drawValue(c, formatter.getFormattedValue(val), valuePoints[j], valuePoints[j + 1] + (val >= 0 ? posOffset : negOffset)); } // if each value of a potential stack should be drawn } else { for (int j = 0; j < (valuePoints.length - 1) * mAnimator.getPhaseX(); j += 2) { BarEntry e = entries.get(j / 2); float[] vals = e.getVals(); // we still draw stacked bars, but there is one // non-stacked // in between if (vals == null) { if (!mViewPortHandler.isInBoundsRight(valuePoints[j])) break; if (!mViewPortHandler.isInBoundsY(valuePoints[j + 1]) || !mViewPortHandler.isInBoundsLeft(valuePoints[j])) continue; drawValue(c, formatter.getFormattedValue(e.getVal()), valuePoints[j], valuePoints[j + 1] + (e.getVal() >= 0 ? posOffset : negOffset)); } else { float[] transformed = new float[vals.length * 2]; int cnt = 0; float add = e.getVal(); for (int k = 0; k < transformed.length; k += 2) { add -= vals[cnt]; transformed[k + 1] = (vals[cnt] + add) * mAnimator.getPhaseY(); cnt++; } trans.pointValuesToPixel(transformed); for (int k = 0; k < transformed.length; k += 2) { float x = valuePoints[j]; float y = transformed[k + 1] + (vals[k / 2] >= 0 ? posOffset : negOffset); if (!mViewPortHandler.isInBoundsRight(x)) break; if (!mViewPortHandler.isInBoundsY(y) || !mViewPortHandler.isInBoundsLeft(x)) continue; drawValue(c, formatter.getFormattedValue(vals[k / 2]), x, y); } } } } } } } /** * Draws a value at the specified x and y position. * * @param value * @param xPos * @param yPos * @formatter */ protected void drawValue(Canvas c, String value, float xPos, float yPos) { c.drawText(value, xPos, yPos, mValuePaint); } @Override public void drawExtras(Canvas c) { } @Override public void drawHighlighted(Canvas c, Highlight[] indices) { int setCount = mChart.getBarData().getDataSetCount(); for (int i = 0; i < indices.length; i++) { Highlight h = indices[i]; int index = h.getXIndex(); int dataSetIndex = h.getDataSetIndex(); BarDataSet set = mChart.getBarData().getDataSetByIndex(dataSetIndex); if (set == null) continue; float barspaceHalf = set.getBarSpace() / 2f; Transformer trans = mChart.getTransformer(set.getAxisDependency()); mHighlightPaint.setColor(set.getHighLightColor()); mHighlightPaint.setAlpha(set.getHighLightAlpha()); // check outofbounds if (index < mChart.getBarData().getYValCount() && index >= 0 && index < (mChart.getXChartMax() * mAnimator.getPhaseX()) / setCount) { BarEntry e = mChart.getBarData().getDataSetByIndex(dataSetIndex) .getEntryForXIndex(index); if (e == null) continue; float groupspace = mChart.getBarData().getGroupSpace(); boolean isStack = h.getStackIndex() < 0 ? false : true; // calculate the correct x-position float x = index * setCount + dataSetIndex + groupspace / 2f + groupspace * index; float y = isStack ? e.getVals()[h.getStackIndex()] + e.getBelowSum(h.getStackIndex()) : e.getVal(); // this is where the bar starts float from = isStack ? e.getBelowSum(h.getStackIndex()) : 0f; prepareBarHighlight(x, y, barspaceHalf, from, trans); c.drawRect(mBarRect, mHighlightPaint); if (mChart.isDrawHighlightArrowEnabled()) { mHighlightPaint.setAlpha(255); // distance between highlight arrow and bar float offsetY = mAnimator.getPhaseY() * 0.07f; Path arrow = new Path(); arrow.moveTo(x + 0.5f, y + offsetY * 0.3f); arrow.lineTo(x + 0.2f, y + offsetY); arrow.lineTo(x + 0.8f, y + offsetY); trans.pathValueToPixel(arrow); c.drawPath(arrow, mHighlightPaint); } } } } public float[] getTransformedValues(Transformer trans, List<BarEntry> entries, int dataSetIndex) { return trans.generateTransformedValuesBarChart(entries, dataSetIndex, mChart.getBarData(), mAnimator.getPhaseY()); } protected boolean passesCheck() { return mChart.getBarData().getYValCount() < mChart.getMaxVisibleCount() * mViewPortHandler.getScaleX(); } }