package com.github.mikephil.charting.charts;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
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.data.DataSet;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.utils.Highlight;
import com.github.mikephil.charting.utils.Utils;
import java.util.ArrayList;
/**
* Chart that draws bars.
*
* @author Philipp Jahoda
*/
public class BarChart extends BarLineChartBase<BarData> {
/** indicates the angle of the 3d effect */
private float mSkew = 0.3f;
/** indicates how much the 3d effect goes back */
private float mDepth = 0.3f;
/** flag the enables or disables 3d bars */
private boolean m3DEnabled = false;
/** flag that enables or disables the highlighting arrow */
private boolean mDrawHighlightArrow = false;
/**
* if set to true, all values are drawn above their bars, instead of below
* their top
*/
private boolean mDrawValueAboveBar = true;
/**
* if set to true, all values of a stack are drawn individually, and not
* just their sum
*/
private boolean mDrawValuesForWholeStack = true;
/**
* if set to true, a grey area is darawn behind each bar that indicates the
* maximum value
*/
private boolean mDrawBarShadow = true;
/** the rect object that is used for drawing the bar shadow */
private RectF mBarShadow = new RectF();
/** the rect object that is used for drawing the bars */
private RectF mBarRect = new RectF();
public BarChart(Context context) {
super(context);
}
public BarChart(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BarChart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
@Override
protected void init() {
super.init();
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);
// calculate3DColors();
}
@Override
protected void calcMinMax(boolean fixedValues) {
super.calcMinMax(fixedValues);
// increase deltax by 1 because the bars have a width of 1
mDeltaX++;
// extend xDelta to make space for multiple datasets (if ther are one)
mDeltaX *= mOriginalData.getDataSetCount();
int maxEntry = 0;
for (int i = 0; i < mOriginalData.getDataSetCount(); i++) {
DataSet<? extends Entry> set = mOriginalData.getDataSetByIndex(i);
if (maxEntry < set.getEntryCount())
maxEntry = set.getEntryCount();
}
float groupSpace = mOriginalData.getGroupSpace();
mDeltaX += maxEntry * groupSpace;
}
@Override
protected void drawHighlights() {
int setCount = mOriginalData.getDataSetCount();
for (int i = 0; i < mIndicesToHightlight.length; i++) {
Highlight h = mIndicesToHightlight[i];
int index = h.getXIndex();
int dataSetIndex = h.getDataSetIndex();
BarDataSet set = (BarDataSet) mCurrentData.getDataSetByIndex(dataSetIndex);
if (set == null)
continue;
mHighlightPaint.setColor(set.getHighLightColor());
mHighlightPaint.setAlpha(set.getHighLightAlpha());
// check outofbounds
if (index < mCurrentData.getYValCount() && index >= 0
&& index < (mDeltaX * mPhaseX) / mOriginalData.getDataSetCount()) {
Entry e = getEntryByDataSetIndex(index, dataSetIndex);
if (e == null)
continue;
// calculate the correct x-position
float x = index * setCount + dataSetIndex + mOriginalData.getGroupSpace() / 2f
+ mOriginalData.getGroupSpace() * index;
float y = e.getVal();
prepareBar(x, y, set.getBarSpace());
mDrawCanvas.drawRect(mBarRect, mHighlightPaint);
if (mDrawHighlightArrow) {
mHighlightPaint.setAlpha(255);
// distance between highlight arrow and bar
float offsetY = mDeltaY * 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);
transformPath(arrow);
mDrawCanvas.drawPath(arrow, mHighlightPaint);
}
}
}
}
@Override
protected void drawData() {
ArrayList<BarDataSet> dataSets = mOriginalData.getDataSets();
int setCount = mOriginalData.getDataSetCount();
// the space between bar-groups
float space = mOriginalData.getGroupSpace();
// 2D drawing
for (int i = 0; i < setCount; i++) {
BarDataSet dataSet = dataSets.get(i);
boolean noStacks = dataSet.getStackSize() == 1 ? true : false;
ArrayList<BarEntry> entries = dataSet.getYVals();
// do the drawing
for (int j = 0; j < dataSet.getEntryCount() * mPhaseX; j++) {
BarEntry e = entries.get(j);
// calculate the x-position, depending on datasetcount
float x = e.getXIndex() + j * (setCount - 1) + i + space * j + space / 2f;
float y = e.getVal();
// no stacks
if (noStacks) {
prepareBar(x, y, dataSet.getBarSpace());
// avoid drawing outofbounds values
if (isOffContentRight(mBarRect.left))
break;
if (isOffContentLeft(mBarRect.right)) {
continue;
}
// if drawing the bar shadow is enabled
if (mDrawBarShadow) {
mRenderPaint.setColor(dataSet.getBarShadowColor());
mDrawCanvas.drawRect(mBarShadow, mRenderPaint);
}
// Set the color for the currently drawn value. If the index
// is
// out of bounds, reuse colors.
mRenderPaint.setColor(dataSet.getColor(j));
mDrawCanvas.drawRect(mBarRect, mRenderPaint);
} else { // stacked bars
float[] vals = e.getVals();
// we still draw stacked bars, but there could be one
// non-stacked
// in between
if (vals == null) {
prepareBar(x, y, dataSet.getBarSpace());
// if drawing the bar shadow is enabled
if (mDrawBarShadow) {
mRenderPaint.setColor(dataSet.getBarShadowColor());
mDrawCanvas.drawRect(mBarShadow, mRenderPaint);
}
mRenderPaint.setColor(dataSet.getColor(0));
mDrawCanvas.drawRect(mBarRect, mRenderPaint);
} else {
float all = e.getVal();
// if drawing the bar shadow is enabled
if (mDrawBarShadow) {
prepareBar(x, y, dataSet.getBarSpace());
mRenderPaint.setColor(dataSet.getBarShadowColor());
mDrawCanvas.drawRect(mBarShadow, mRenderPaint);
}
// draw the stack
for (int k = 0; k < vals.length; k++) {
all -= vals[k];
prepareBar(x, vals[k] + all, dataSet.getBarSpace());
mRenderPaint.setColor(dataSet.getColor(k));
mDrawCanvas.drawRect(mBarRect, mRenderPaint);
}
}
// avoid drawing outofbounds values
if (isOffContentRight(mBarRect.left))
break;
}
}
}
}
/**
* Prepares a bar for drawing on the specified x-index and y-position. Also
* prepares the shadow-bar if enabled.
*
* @param x the x-position
* @param y the y-position
* @param barspace the space between bars
*/
private void prepareBar(float x, float y, float barspace) {
float spaceHalf = barspace / 2f;
float left = x + spaceHalf;
float right = x + 1f - spaceHalf;
float top = y >= 0 ? y : 0;
float bottom = y <= 0 ? y : 0;
mBarRect.set(left, top, right, bottom);
transformRectWithPhase(mBarRect);
// if a shadow is drawn, prepare it too
if (mDrawBarShadow) {
mBarShadow.set(mBarRect.left, mOffsetTop, mBarRect.right, getHeight() - mOffsetBottom);
}
}
@Override
protected void drawXLabels(float yPos) {
// pre allocate to save performance (dont allocate in loop)
float[] position = new float[] {
0f, 0f
};
int step = mCurrentData.getDataSetCount();
for (int i = 0; i < mCurrentData.getXValCount(); i += mXLabels.mXAxisLabelModulus) {
position[0] = i * step + i * mOriginalData.getGroupSpace()
+ mOriginalData.getGroupSpace() / 2f;
// center the text
if (mXLabels.isCenterXLabelsEnabled())
position[0] += (step / 2f);
transformPointArray(position);
if (position[0] >= mOffsetLeft && position[0] <= getWidth() - mOffsetRight) {
String label = mCurrentData.getXVals().get(i);
if (mXLabels.isAvoidFirstLastClippingEnabled()) {
// avoid clipping of the last
if (i == mCurrentData.getXValCount() - 1) {
float width = Utils.calcTextWidth(mXLabelPaint, label);
if (width > getOffsetRight() * 2 && position[0] + width > getWidth())
position[0] -= width / 2;
// avoid clipping of the first
} else if (i == 0) {
float width = Utils.calcTextWidth(mXLabelPaint, label);
position[0] += width / 2;
}
}
mDrawCanvas.drawText(label, position[0],
yPos,
mXLabelPaint);
}
}
}
@Override
protected void drawVerticalGrid() {
if (!mDrawVerticalGrid || mCurrentData == null)
return;
float[] position = new float[] {
0f, 0f
};
// take into consideration that multiple DataSets increase mDeltaX
int step = mCurrentData.getDataSetCount();
for (int i = 0; i < mCurrentData.getXValCount(); i += mXLabels.mXAxisLabelModulus) {
position[0] = i * step + i * mOriginalData.getGroupSpace();
transformPointArray(position);
if (position[0] >= mOffsetLeft && position[0] <= getWidth()) {
mDrawCanvas.drawLine(position[0], mOffsetTop, position[0], getHeight()
- mOffsetBottom, mGridPaint);
}
}
}
@Override
protected void drawValues() {
// if values are drawn
if (mDrawYValues && mCurrentData.getYValCount() < mMaxVisibleCount * mScaleX) {
ArrayList<BarDataSet> dataSets = ((BarData) mCurrentData).getDataSets();
float posOffset = 0f; float negOffset = 0f;
// calculate the correct offset depending on the draw position of
// the value
if (mDrawValueAboveBar) {
posOffset = -Utils.convertDpToPixel(5);
negOffset = Utils.calcTextHeight(mValuePaint, "8") * 1.5f;
} else {
posOffset = Utils.calcTextHeight(mValuePaint, "8") * 1.5f;
negOffset = -Utils.convertDpToPixel(5);
}
for (int i = 0; i < mCurrentData.getDataSetCount(); i++) {
BarDataSet dataSet = dataSets.get(i);
ArrayList<BarEntry> entries = dataSet.getYVals();
float[] valuePoints = generateTransformedValuesBarChart(entries, i);
// if only single values are drawn (sum)
if (!mDrawValuesForWholeStack) {
for (int j = 0; j < valuePoints.length * mPhaseX; j += 2) {
if (isOffContentRight(valuePoints[j]))
break;
if (isOffContentLeft(valuePoints[j]) || isOffContentTop(valuePoints[j + 1])
|| isOffContentBottom(valuePoints[j + 1]))
continue;
float val = entries.get(j / 2).getVal();
drawValue(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) * mPhaseX; j += 2) {
if (isOffContentRight(valuePoints[j]))
break;
if (isOffContentLeft(valuePoints[j]) || isOffContentTop(valuePoints[j + 1])
|| isOffContentBottom(valuePoints[j + 1]))
continue;
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) {
drawValue(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) * mPhaseY;
cnt++;
}
transformPointArray(transformed);
for (int k = 0; k < transformed.length; k += 2) {
drawValue(vals[k / 2], valuePoints[j],
transformed[k + 1] + (vals[k / 2] >= 0 ? posOffset : negOffset));
}
}
}
}
}
}
}
/**
* Draws a value at the specified x and y position.
*
* @param value
* @param xPos
* @param yPos
*/
private void drawValue(float val, float xPos, float yPos) {
String value = mValueFormatter.getFormattedValue(val);
if (mDrawUnitInChart) {
mDrawCanvas.drawText(value + mUnit, xPos, yPos,
mValuePaint);
} else {
mDrawCanvas.drawText(value, xPos, yPos,
mValuePaint);
}
}
/**
* Returns the Highlight object (contains x-index and DataSet index) of the
* selected value at the given touch point inside the BarChart.
*
* @param x
* @param y
* @return
*/
@Override
public Highlight getHighlightByTouchPoint(float x, float y) {
if (mDataNotSet || mCurrentData == null) {
Log.e(LOG_TAG, "Can't select by touch. No data set.");
return null;
}
// create an array of the touch-point
float[] pts = new float[2];
pts[0] = x;
pts[1] = y;
Matrix tmp = new Matrix();
// invert all matrixes to convert back to the original value
mMatrixOffset.invert(tmp);
tmp.mapPoints(pts);
mMatrixTouch.invert(tmp);
tmp.mapPoints(pts);
mMatrixValueToPx.invert(tmp);
tmp.mapPoints(pts);
// for barchart, we only need x-val
double xTouchVal = pts[0];
double base = xTouchVal;
if (xTouchVal < 0 || xTouchVal > mDeltaX)
return null;
if (base < 0)
base = 0;
if (base >= mDeltaX)
base = mDeltaX - 1;
int setCount = mOriginalData.getDataSetCount();
int valCount = setCount * mOriginalData.getXValCount();
// calculate the amount of bar-space between index 0 and touch position
float space = (float) (((float) valCount / (float) setCount) / (mDeltaX / base));
float reduction = (float) space * mOriginalData.getGroupSpace();
int xIndex = (int) ((base - reduction) / setCount);
int dataSetIndex = ((int) (base - reduction)) % setCount;
if (dataSetIndex == -1)
return null;
return new Highlight(xIndex, dataSetIndex);
}
/**
* Returns the bounding box of the specified Entry in the specified DataSet.
* Returns null if the Entry could not be found in the charts data.
*
* @param e
* @param dataSetIndex
* @return
*/
public RectF getBarBounds(BarEntry e) {
BarDataSet set = mOriginalData.getDataSetForEntry(e);
if (set == null)
return null;
float barspace = set.getBarSpace();
float y = e.getVal();
float x = e.getXIndex();
float spaceHalf = barspace / 2f;
float left = x + spaceHalf;
float right = x + 1f - spaceHalf;
float top = y >= 0 ? y : 0;
float bottom = y <= 0 ? y : 0;
RectF bounds = new RectF(left, top, right, bottom);
transformRect(bounds);
return bounds;
}
/**
* sets the skew (default 0.3f), the skew indicates how much the 3D effect
* of the chart is turned to the right
*
* @param skew
*/
public void setSkew(float skew) {
this.mSkew = skew;
}
/**
* returns the skew value that indicates how much the 3D effect is turned to
* the right
*
* @return
*/
public float getSkew() {
return mSkew;
}
/**
* set the depth of the chart (default 0.3f), the depth indicates how much
* the 3D effect of the chart goes back
*
* @param depth
*/
public void setDepth(float depth) {
this.mDepth = depth;
}
/**
* returhs the depth, which indicates how much the 3D effect goes back
*
* @return
*/
public float getDepth() {
return mDepth;
}
/**
* if enabled, chart will be drawn in 3d
*
* @param enabled
*/
public void set3DEnabled(boolean enabled) {
this.m3DEnabled = enabled;
}
/**
* returns true if 3d bars is enabled, false if not
*
* @return
*/
public boolean is3DEnabled() {
return m3DEnabled;
}
/**
* set this to true to draw the highlightning arrow
*
* @param enabled
*/
public void setDrawHighlightArrow(boolean enabled) {
mDrawHighlightArrow = enabled;
}
/**
* returns true if drawing the highlighting arrow is enabled, false if not
*
* @return
*/
public boolean isDrawHighlightArrowEnabled() {
return mDrawHighlightArrow;
}
/**
* If set to true, all values are drawn above their bars, instead of below
* their top.
*
* @param enabled
*/
public void setDrawValueAboveBar(boolean enabled) {
mDrawValueAboveBar = enabled;
}
/**
* returns true if drawing values above bars is enabled, false if not
*
* @return
*/
public boolean isDrawValueAboveBarEnabled() {
return mDrawValueAboveBar;
}
/**
* if set to true, all values of a stack are drawn individually, and not
* just their sum
*
* @param enabled
*/
public void setDrawValuesForWholeStack(boolean enabled) {
mDrawValuesForWholeStack = enabled;
}
/**
* returns true if all values of a stack are drawn, and not just their sum
*
* @return
*/
public boolean isDrawValuesForWholeStackEnabled() {
return mDrawValuesForWholeStack;
}
/**
* If set to true, a grey area is drawn behind each bar that indicates the
* maximum value. Enabling his will reduce performance by about 50%.
*
* @param enabled
*/
public void setDrawBarShadow(boolean enabled) {
mDrawBarShadow = enabled;
}
/**
* returns true if drawing shadows (maxvalue) for each bar is enabled, false
* if not
*
* @return
*/
public boolean isDrawBarShadowEnabled() {
return mDrawBarShadow;
}
@Override
protected void drawAdditional() {
}
}