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
// public void setColorTemplate(ColorTemplate ct) {
// super.setColorTemplate(ct);
//
// calculate3DColors();
// }
// /** array that holds all the colors for the top 3D effect */
// private ArrayList<ArrayList<Integer>> mTopColors;
//
// /** array that holds all the colors for the side 3D effect */
// private ArrayList<ArrayList<Integer>> mSideColors;
//
// /**
// * calculates the 3D color arrays
// */
// protected void calculate3DColors() {
//
// // generate the colors for the 3D effect
// mTopColors = new ArrayList<ArrayList<Integer>>();
// mSideColors = new ArrayList<ArrayList<Integer>>();
//
// float[] hsv = new float[3];
//
// for (int i = 0; i < mCt.getColors().size(); i++) {
//
// // Get the colors for the DataSet at the current index. If the index
// // is out of bounds, reuse DataSet colors.
// ArrayList<Integer> colors = mCt.getDataSetColors(i);
// ArrayList<Integer> topColors = new ArrayList<Integer>();
// ArrayList<Integer> sideColors = new ArrayList<Integer>();
//
// for (int j = 0; j < colors.size(); j++) {
//
// // extract the color
// int c = colors.get(j);
// Color.colorToHSV(c, hsv); // convert to hsv
//
// // make brighter
// hsv[1] = hsv[1] - 0.1f; // less saturation
// hsv[2] = hsv[2] + 0.1f; // more brightness
//
// // convert back
// c = Color.HSVToColor(hsv);
//
// // assign
// topColors.add(c);
//
// // get color again
// c = colors.get(j);
//
// // convert
// Color.colorToHSV(c, hsv);
//
// // make darker
// hsv[1] = hsv[1] + 0.1f; // more saturation
// hsv[2] = hsv[2] - 0.1f; // less brightness
//
// // reassing
// c = Color.HSVToColor(hsv);
//
// sideColors.add(c);
// }
//
// mTopColors.add(topColors);
// mSideColors.add(sideColors);
// }
// }
@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 ds = (BarDataSet) mCurrentData.getDataSetByIndex(dataSetIndex);
mHighlightPaint.setColor(ds.getHighLightColor());
mHighlightPaint.setAlpha(ds.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, ds.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 drawData() {
//
// ArrayList<Path> topPaths = new ArrayList<Path>();
// ArrayList<Path> sidePaths = new ArrayList<Path>();
//
// ArrayList<BarDataSet> dataSets = (ArrayList<BarDataSet>)
// mCurrentData.getDataSets();
//
// // preparations for 3D bars
// if (m3DEnabled) {
//
// float[] pts = new float[] {
// 0f, 0f, 1f, 0f
// };
//
// // calculate the depth depending on scale
//
// transformPointArray(pts);
//
// pts[3] = pts[2] - pts[0];
// pts[2] = 0f;
// pts[1] = 0f;
// pts[0] = 0f;
//
// Matrix invert = new Matrix();
//
// mMatrixOffset.invert(invert);
// invert.mapPoints(pts);
//
// mMatrixTouch.invert(invert);
// invert.mapPoints(pts);
//
// mMatrixValueToPx.invert(invert);
// invert.mapPoints(pts);
//
// float depth = Math.abs(pts[3] - pts[1]) * mDepth;
//
// for (int i = 0; i < mCurrentData.getDataSetCount(); i++) {
//
// DataSet dataSet = dataSets.get(i);
// ArrayList<Entry> series = dataSet.getYVals();
//
// for (int j = 0; j < series.size(); j++) {
//
// float x = series.get(j).getXIndex();
// float y = series.get(j).getVal();
// float left = x + mBarSpace / 2f;
// float right = x + 1f - mBarSpace / 2f;
// float top = y >= 0 ? y : 0;
//
// // create the 3D effect paths for the top and side
// Path topPath = new Path();
// topPath.moveTo(left, top);
// topPath.lineTo(left + mSkew, top + depth);
// topPath.lineTo(right + mSkew, top + depth);
// topPath.lineTo(right, top);
//
// topPaths.add(topPath);
//
// Path sidePath = new Path();
// sidePath.moveTo(right, top);
// sidePath.lineTo(right + mSkew, top + depth);
// sidePath.lineTo(right + mSkew, depth);
// sidePath.lineTo(right, 0);
//
// sidePaths.add(sidePath);
// }
// }
//
// transformPaths(topPaths);
// transformPaths(sidePaths);
// }
//
// int cnt = 0;
//
// // 2D drawing
// for (int i = 0; i < mCurrentData.getDataSetCount(); i++) {
//
// BarDataSet dataSet = dataSets.get(i);
// ArrayList<Entry> series = dataSet.getYVals();
//
// // Get the colors for the DataSet at the current index. If the
// // index
// // is out of bounds, reuse DataSet colors.
// ArrayList<Integer> colors = mCt.getDataSetColors(i %
// mCt.getColors().size());
// ArrayList<Integer> colors3DTop = mTopColors.get(i %
// mCt.getColors().size());
// ArrayList<Integer> colors3DSide = mSideColors.get(i %
// mCt.getColors().size());
//
// // do the drawing
// for (int j = 0; j < dataSet.getEntryCount(); j++) {
//
// // Set the color for the currently drawn value. If the index
// // is
// // out of bounds, reuse colors.
// mRenderPaint.setColor(colors.get(j % colors.size()));
//
// int x = series.get(j).getXIndex();
// float y = series.get(j).getVal();
// float left = x + mBarSpace / 2f;
// float right = x + 1f - mBarSpace / 2f;
// float top = y >= 0 ? y : 0;
// float bottom = y <= 0 ? y : 0;
//
// mBarRect.set(left, top, right, bottom);
//
// transformRect(mBarRect);
//
// // avoid drawing outofbounds values
// if (isOffContentRight(mBarRect.left))
// break;
//
// if (isOffContentLeft(mBarRect.right)) {
// cnt++;
// continue;
// }
//
// mDrawCanvas.drawRect(mBarRect, mRenderPaint);
//
// // 3D drawing
// if (m3DEnabled) {
//
// mRenderPaint.setColor(colors3DTop.get(j % colors3DTop.size()));
// mDrawCanvas.drawPath(topPaths.get(cnt), mRenderPaint);
//
// mRenderPaint.setColor(colors3DSide.get(j % colors3DSide.size()));
// mDrawCanvas.drawPath(sidePaths.get(cnt), mRenderPaint);
// }
//
// cnt++;
// }
// }
// }
@Override
protected void drawValues() {
// if values are drawn
if (mDrawYValues && mCurrentData.getYValCount() < mMaxVisibleCount * mScaleX) {
ArrayList<BarDataSet> dataSets = ((BarData) mCurrentData).getDataSets();
float offset = 0f;
// calculate the correct offset depending on the draw position of
// the value
if (mDrawValueAboveBar)
offset = -Utils.convertDpToPixel(5);
else
offset = Utils.calcTextHeight(mValuePaint, "8") * 1.5f;
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(mValueFormat.format(val), valuePoints[j],
valuePoints[j + 1] + offset);
}
// 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(mValueFormat.format(e.getVal()), valuePoints[j],
valuePoints[j + 1] + offset);
} 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(mValueFormat.format(vals[k / 2]), valuePoints[j],
transformed[k + 1] + offset);
}
}
}
}
}
}
}
/**
* Draws a value at the specified x and y position.
*
* @param value
* @param xPos
* @param yPos
*/
private void drawValue(String val, float xPos, float yPos) {
if (mDrawUnitInChart) {
mDrawCanvas.drawText(val + mUnit, xPos, yPos,
mValuePaint);
} else {
mDrawCanvas.drawText(val, 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() {
}
}