package com.github.mikephil.charting.charts;
import android.content.ContentValues;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.provider.MediaStore.Images;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewParent;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.ChartData;
import com.github.mikephil.charting.data.DataSet;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.PieDataSet;
import com.github.mikephil.charting.interfaces.OnChartGestureListener;
import com.github.mikephil.charting.interfaces.OnChartValueSelectedListener;
import com.github.mikephil.charting.utils.Highlight;
import com.github.mikephil.charting.utils.Legend;
import com.github.mikephil.charting.utils.Legend.LegendPosition;
import com.github.mikephil.charting.utils.MarkerView;
import com.github.mikephil.charting.utils.SelInfo;
import com.github.mikephil.charting.utils.Utils;
import com.github.mikephil.charting.utils.ValueFormatter;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.ValueAnimator;
import com.nineoldandroids.animation.ValueAnimator.AnimatorUpdateListener;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.DecimalFormat;
import java.util.ArrayList;
/**
* Baseclass of all Chart-Views.
*
* @author Philipp Jahoda
*/
public abstract class Chart<T extends ChartData<? extends DataSet<? extends Entry>>> extends View
implements AnimatorUpdateListener {
public static final String LOG_TAG = "MPChart";
/** flag that indicates if logging is enabled or not */
protected boolean mLogEnabled = false;
/**
* string that is drawn next to the values in the chart, indicating their
* unit
*/
protected String mUnit = "";
/** custom formatter that is used instead of the auto-formatter if set */
protected ValueFormatter mValueFormatter = null;
/**
* flag that indicates if the default formatter should be used or if a
* custom one is set
*/
private boolean mUseDefaultFormatter = true;
/** chart offset to the left */
protected float mOffsetLeft = 12;
/** chart toffset to the top */
protected float mOffsetTop = 12;
/** chart offset to the right */
protected float mOffsetRight = 12;
/** chart offset to the bottom */
protected float mOffsetBottom = 12;
/**
* object that holds all data relevant for the chart (x-vals, y-vals, ...)
* that are currently displayed
*/
protected T mCurrentData = null;
/**
* object that holds all data that was originally set for the chart, before
* it was modified or any filtering algorithms had been applied
*/
protected T mOriginalData = null;
/** the canvas that is used for drawing on the bitmap */
protected Canvas mDrawCanvas;
/** the lowest value the chart can display */
protected float mYChartMin = 0.0f;
/** the highest value the chart can display */
protected float mYChartMax = 0.0f;
/** paint for the x-label values */
protected Paint mXLabelPaint;
/** paint for the y-label values */
protected Paint mYLabelPaint;
/** paint used for highlighting values */
protected Paint mHighlightPaint;
/**
* paint object used for drawing the description text in the bottom right
* corner of the chart
*/
protected Paint mDescPaint;
/**
* paint object for drawing the information text when there are no values in
* the chart
*/
protected Paint mInfoPaint;
/**
* paint object for drawing values (text representing values of chart
* entries)
*/
protected Paint mValuePaint;
/** this is the paint object used for drawing the data onto the chart */
protected Paint mRenderPaint;
/** paint for the legend labels */
protected Paint mLegendLabelPaint;
/** paint used for the legend forms */
protected Paint mLegendFormPaint;
/** paint used for the limit lines */
protected Paint mLimitLinePaint;
/** description text that appears in the bottom right corner of the chart */
protected String mDescription = "Description.";
/** flag that indicates if the chart has been fed with data yet */
protected boolean mDataNotSet = true;
/** if true, units are drawn next to the values in the chart */
protected boolean mDrawUnitInChart = false;
/** the range of y-values the chart displays */
protected float mDeltaY = 1f;
/** the number of x-values the chart displays */
protected float mDeltaX = 1f;
/** matrix to map the values to the screen pixels */
protected Matrix mMatrixValueToPx = new Matrix();
/** matrix for handling the different offsets of the chart */
protected Matrix mMatrixOffset = new Matrix();
/** matrix used for touch events */
protected final Matrix mMatrixTouch = new Matrix();
/** if true, touch gestures are enabled on the chart */
protected boolean mTouchEnabled = true;
/** if true, y-values are drawn on the chart */
protected boolean mDrawYValues = true;
/** if true, value highlightning is enabled */
protected boolean mHighlightEnabled = true;
/** flag indicating if the legend is drawn of not */
protected boolean mDrawLegend = true;
/** this rectangle defines the area in which graph values can be drawn */
protected RectF mContentRect = new RectF();
/** the legend object containing all data associated with the legend */
protected Legend mLegend;
/** listener that is called when a value on the chart is selected */
protected OnChartValueSelectedListener mSelectionListener;
/** text that is displayed when the chart is empty */
private String mNoDataText = "No chart data available.";
/**
* Gesture listener for custom callbacks when making gestures on the chart.
*/
private OnChartGestureListener mGestureListener;
/**
* text that is displayed when the chart is empty that describes why the
* chart is empty
*/
private String mNoDataTextDescription;
/** default constructor for initialization in code */
public Chart(Context context) {
super(context);
init();
}
/** constructor for initialization in xml */
public Chart(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
/** even more awesome constructor */
public Chart(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* initialize all paints and stuff
*/
protected void init() {
// setLayerType(View.LAYER_TYPE_SOFTWARE, null);
// initialize the utils
Utils.init(getContext().getResources());
// do screen density conversions
mOffsetBottom = (int) Utils.convertDpToPixel(mOffsetBottom);
mOffsetLeft = (int) Utils.convertDpToPixel(mOffsetLeft);
mOffsetRight = (int) Utils.convertDpToPixel(mOffsetRight);
mOffsetTop = (int) Utils.convertDpToPixel(mOffsetTop);
mRenderPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mRenderPaint.setStyle(Style.FILL);
mDescPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mDescPaint.setColor(Color.BLACK);
mDescPaint.setTextAlign(Align.RIGHT);
mDescPaint.setTextSize(Utils.convertDpToPixel(9f));
mInfoPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInfoPaint.setColor(Color.rgb(247, 189, 51)); // orange
mInfoPaint.setTextAlign(Align.CENTER);
mInfoPaint.setTextSize(Utils.convertDpToPixel(12f));
mValuePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mValuePaint.setColor(Color.rgb(63, 63, 63));
mValuePaint.setTextAlign(Align.CENTER);
mValuePaint.setTextSize(Utils.convertDpToPixel(9f));
mLegendFormPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLegendFormPaint.setStyle(Paint.Style.FILL);
mLegendFormPaint.setStrokeWidth(3f);
mLegendLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLegendLabelPaint.setTextSize(Utils.convertDpToPixel(9f));
mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mHighlightPaint.setStyle(Paint.Style.STROKE);
mHighlightPaint.setStrokeWidth(2f);
mHighlightPaint.setColor(Color.rgb(255, 187, 115));
mXLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mXLabelPaint.setColor(Color.BLACK);
mXLabelPaint.setTextAlign(Align.CENTER);
mXLabelPaint.setTextSize(Utils.convertDpToPixel(10f));
mYLabelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mYLabelPaint.setColor(Color.BLACK);
mYLabelPaint.setTextSize(Utils.convertDpToPixel(10f));
mLimitLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mLimitLinePaint.setStyle(Paint.Style.STROKE);
mDrawPaint = new Paint(Paint.DITHER_FLAG);
}
// public void initWithDummyData() {
// ColorTemplate template = new ColorTemplate();
// template.addColorsForDataSets(ColorTemplate.COLORFUL_COLORS,
// getContext());
//
// setColorTemplate(template);
// setDrawYValues(false);
//
// ArrayList<String> xVals = new ArrayList<String>();
// Calendar calendar = Calendar.getInstance();
// for (int i = 0; i < 12; i++) {
// xVals.add(calendar.getDisplayName(Calendar.MONTH, Calendar.SHORT,
// Locale.getDefault()));
// }
//
// ArrayList<DataSet> dataSets = new ArrayList<DataSet>();
// for (int i = 0; i < 3; i++) {
//
// ArrayList<Entry> yVals = new ArrayList<Entry>();
//
// for (int j = 0; j < 12; j++) {
// float val = (float) (Math.random() * 100);
// yVals.add(new Entry(val, j));
// }
//
// DataSet set = new DataSet(yVals, "DataSet " + i);
// dataSets.add(set); // add the datasets
// }
// // create a data object with the datasets
// ChartData data = new ChartData(xVals, dataSets);
// setData(data);
// invalidate();
// }
/**
* Sets a new data object for the chart. The data object contains all values
* and information needed for displaying.
*
* @param data
*/
public void setData(T data) {
// if (data == null || !data.isValid()) {
// Log.e(LOG_TAG,
// "Cannot set data for chart. Provided chart values are null or contain less than 1 entry.");
// mDataNotSet = true;
// return;
// }
if (data == null) {
Log.e(LOG_TAG,
"Cannot set data for chart. Provided data object is null.");
return;
}
// Log.i(LOG_TAG, "xvalcount: " + data.getXValCount());
// Log.i(LOG_TAG, "entrycount: " + data.getYValCount());
// LET THE CHART KNOW THERE IS DATA
mDataNotSet = false;
mOffsetsCalculated = false;
mCurrentData = data;
mOriginalData = data;
prepare();
// calculate how many digits are needed
calcFormats();
Log.i(LOG_TAG, "Data is set.");
}
/**
* Clears the chart from all data and refreshes it (by calling
* invalidate()).
*/
public void clear() {
mCurrentData = null;
mOriginalData = null;
mDataNotSet = true;
invalidate();
}
/**
* Returns true if the chart is empty (meaning it's data object is either
* null or contains no entries).
*
* @return
*/
public boolean isEmpty() {
if (mOriginalData == null)
return true;
else {
if (mOriginalData.getYValCount() <= 0)
return true;
else
return false;
}
}
/**
* does needed preparations for drawing
*/
public abstract void prepare();
/**
* Lets the chart know its underlying data has changed and performs all
* necessary recalculations.
*/
public abstract void notifyDataSetChanged();
/**
* calculates the offsets of the chart to the border depending on the
* position of an eventual legend or depending on the length of the y-axis
* and x-axis labels and their position
*/
protected abstract void calculateOffsets();
/**
* calcualtes the y-min and y-max value and the y-delta and x-delta value
*/
protected void calcMinMax(boolean fixedValues) {
// only calculate values if not fixed values
if (!fixedValues) {
mYChartMin = mCurrentData.getYMin();
mYChartMax = mCurrentData.getYMax();
}
// calc delta
mDeltaY = Math.abs(mYChartMax - mYChartMin);
mDeltaX = mCurrentData.getXVals().size() - 1;
}
/**
* calculates the required number of digits for the values that might be
* drawn in the chart (if enabled)
*/
protected void calcFormats() {
// check if a custom formatter is set or not
if (mUseDefaultFormatter) {
float reference = 0f;
if (mOriginalData == null || mOriginalData.getXValCount() < 2) {
reference = Math.max(Math.abs(mYChartMin), Math.abs(mYChartMax));
} else {
reference = mDeltaY;
}
int digits = Utils.getDecimals(reference);
StringBuffer b = new StringBuffer();
for (int i = 0; i < digits; i++) {
if (i == 0)
b.append(".");
b.append("0");
}
DecimalFormat formatter = new DecimalFormat("###,###,###,##0" + b.toString());
mValueFormatter = new DefaultValueFormatter(formatter);
}
}
/** flag that indicates if offsets calculation has already been done or not */
private boolean mOffsetsCalculated = false;
/**
* Bitmap object used for drawing. This is necessary because hardware
* acceleration uses OpenGL which only allows a specific texture size to be
* drawn on the canvas directly.
**/
protected Bitmap mDrawBitmap;
/** paint object used for drawing the bitmap */
protected Paint mDrawPaint;
@Override
protected void onDraw(Canvas canvas) {
// super.onDraw(canvas);
if (mDataNotSet) { // check if there is data
// if no data, inform the user
canvas.drawText(mNoDataText, getWidth() / 2, getHeight() / 2, mInfoPaint);
if (!TextUtils.isEmpty(mNoDataTextDescription)) {
float textOffset = -mInfoPaint.ascent() + mInfoPaint.descent();
canvas.drawText(mNoDataTextDescription, getWidth() / 2, (getHeight() / 2)
+ textOffset, mInfoPaint);
}
return;
}
if (!mOffsetsCalculated) {
calculateOffsets();
mOffsetsCalculated = true;
}
if (mDrawBitmap == null || mDrawCanvas == null) {
mDrawBitmap = Bitmap.createBitmap(getWidth(), getHeight(),
Bitmap.Config.ARGB_4444);
mDrawCanvas = new Canvas(mDrawBitmap);
}
// clear everything
mDrawBitmap.eraseColor(Color.TRANSPARENT);
// mDrawCanvas.drawColor(Color.WHITE);
// canvas.drawColor(Color.TRANSPARENT,
// android.graphics.PorterDuff.Mode.XOR); // clear all
}
/**
* sets up the content rect that restricts the chart surface
*/
protected void prepareContentRect() {
mContentRect.set(mOffsetLeft,
mOffsetTop,
getWidth() - mOffsetRight,
getHeight() - mOffsetBottom);
}
/**
* Generates an automatically prepared legend depending on the DataSets in
* the chart and their colors.
*/
public void prepareLegend() {
ArrayList<String> labels = new ArrayList<String>();
ArrayList<Integer> colors = new ArrayList<Integer>();
// loop for building up the colors and labels used in the legend
for (int i = 0; i < mOriginalData.getDataSetCount(); i++) {
DataSet<? extends Entry> dataSet = mOriginalData.getDataSetByIndex(i);
ArrayList<Integer> clrs = dataSet.getColors();
int entryCount = dataSet.getEntryCount();
// if we have a barchart with stacked bars
if (dataSet instanceof BarDataSet && ((BarDataSet) dataSet).getStackSize() > 1) {
BarDataSet bds = (BarDataSet) dataSet;
String[] sLabels = bds.getStackLabels();
for (int j = 0; j < clrs.size() && j < entryCount && j < bds.getStackSize(); j++) {
labels.add(sLabels[j % sLabels.length]);
colors.add(clrs.get(j));
}
// add the legend description label
colors.add(-2);
labels.add(bds.getLabel());
} else if (dataSet instanceof PieDataSet) {
ArrayList<String> xVals = mOriginalData.getXVals();
PieDataSet pds = (PieDataSet) dataSet;
for (int j = 0; j < clrs.size() && j < entryCount && j < xVals.size(); j++) {
labels.add(xVals.get(j));
colors.add(clrs.get(j));
}
// add the legend description label
colors.add(-2);
labels.add(pds.getLabel());
} else { // all others
for (int j = 0; j < clrs.size() && j < entryCount; j++) {
// if multiple colors are set for a DataSet, group them
if (j < clrs.size() - 1 && j < entryCount - 1) {
labels.add(null);
} else { // add label to the last entry
String label = mOriginalData.getDataSetByIndex(i).getLabel();
labels.add(label);
}
colors.add(clrs.get(j));
}
}
}
Legend l = new Legend(colors, labels);
if (mLegend != null) {
// apply the old legend settings to a potential new legend
l.apply(mLegend);
}
mLegend = l;
}
/**
* Transforms an arraylist of Entry into a float array containing the x and
* y values transformed with all matrices for the LINECHART or SCATTERCHART.
*
* @param entries
* @return
*/
protected float[] generateTransformedValuesLineScatter(ArrayList<? extends Entry> entries) {
float[] valuePoints = new float[entries.size() * 2];
for (int j = 0; j < valuePoints.length; j += 2) {
Entry e = entries.get(j / 2);
valuePoints[j] = e.getXIndex();
valuePoints[j + 1] = e.getVal() * mPhaseY;
}
transformPointArray(valuePoints);
return valuePoints;
}
/**
* Transforms an arraylist of Entry into a float array containing the x and
* y values transformed with all matrices for the BARCHART.
*
* @param entries
* @param dataSet the dataset index
* @return
*/
protected float[] generateTransformedValuesBarChart(ArrayList<? extends Entry> entries,
int dataSet) {
float[] valuePoints = new float[entries.size() * 2];
int setCount = mOriginalData.getDataSetCount();
BarData bd = (BarData) mOriginalData;
float space = bd.getGroupSpace();
for (int j = 0; j < valuePoints.length; j += 2) {
Entry e = entries.get(j / 2);
// calculate the x-position, depending on datasetcount
float x = e.getXIndex() + (j / 2 * (setCount - 1)) + dataSet + 0.5f + space * (j / 2)
+ space / 2f;
float y = e.getVal();
valuePoints[j] = x;
valuePoints[j + 1] = y * mPhaseY;
}
transformPointArray(valuePoints);
return valuePoints;
}
/**
* transform a path with all the given matrices VERY IMPORTANT: keep order
* to value-touch-offset
*
* @param path
*/
protected void transformPath(Path path) {
path.transform(mMatrixValueToPx);
path.transform(mMatrixTouch);
path.transform(mMatrixOffset);
}
/**
* Transforms multiple paths will all matrices.
*
* @param paths
*/
protected void transformPaths(ArrayList<Path> paths) {
for (int i = 0; i < paths.size(); i++) {
transformPath(paths.get(i));
}
}
/**
* Transform an array of points with all matrices. VERY IMPORTANT: Keep
* matrix order "value-touch-offset" when transforming.
*
* @param pts
*/
protected void transformPointArray(float[] pts) {
mMatrixValueToPx.mapPoints(pts);
mMatrixTouch.mapPoints(pts);
mMatrixOffset.mapPoints(pts);
}
/**
* Transform a rectangle with all matrices.
*
* @param r
*/
protected void transformRect(RectF r) {
mMatrixValueToPx.mapRect(r);
mMatrixTouch.mapRect(r);
mMatrixOffset.mapRect(r);
}
/**
* Transform a rectangle with all matrices with potential animation phases.
*
* @param r
*/
protected void transformRectWithPhase(RectF r) {
// multiply the height of the rect with the phase
if (r.top > 0)
r.top *= mPhaseY;
else
r.bottom *= mPhaseY;
mMatrixValueToPx.mapRect(r);
mMatrixTouch.mapRect(r);
mMatrixOffset.mapRect(r);
}
/**
* transforms multiple rects with all matrices
*
* @param rects
*/
protected void transformRects(ArrayList<RectF> rects) {
for (int i = 0; i < rects.size(); i++)
transformRect(rects.get(i));
}
/**
* transforms the given rect objects with the touch matrix only
*
* @param paths
*/
protected void transformRectsTouch(ArrayList<RectF> rects) {
for (int i = 0; i < rects.size(); i++) {
mMatrixTouch.mapRect(rects.get(i));
}
}
/**
* transforms the given path objects with the touch matrix only
*
* @param paths
*/
protected void transformPathsTouch(ArrayList<Path> paths) {
for (int i = 0; i < paths.size(); i++) {
paths.get(i).transform(mMatrixTouch);
}
}
/**
* draws the legend
*/
protected void drawLegend() {
if (!mDrawLegend || mLegend == null || mLegend.getPosition() == LegendPosition.NONE)
return;
String[] labels = mLegend.getLegendLabels();
Typeface tf = mLegend.getTypeface();
if (tf != null)
mLegendLabelPaint.setTypeface(tf);
mLegendLabelPaint.setTextSize(mLegend.getTextSize());
mLegendLabelPaint.setColor(mLegend.getTextColor());
float formSize = mLegend.getFormSize();
// space between text and shape/form of entry
float formTextSpaceAndForm = mLegend.getFormToTextSpace() + formSize;
// space between the entries
float stackSpace = mLegend.getStackSpace();
float textSize = mLegend.getTextSize();
// the amount of pixels the text needs to be set down to be on the same
// height as the form
float textDrop = (Utils.calcTextHeight(mLegendLabelPaint, "AQJ") + formSize) / 2f;
float posX, posY;
// contains the stacked legend size in pixels
float stack = 0f;
boolean wasStacked = false;
switch (mLegend.getPosition()) {
case BELOW_CHART_LEFT:
posX = mLegend.getOffsetLeft();
posY = getHeight() - mLegend.getOffsetBottom() / 2f - formSize / 2f;
for (int i = 0; i < labels.length; i++) {
mLegend.drawForm(mDrawCanvas, posX, posY, mLegendFormPaint, i);
// grouped forms have null labels
if (labels[i] != null) {
// make a step to the left
if (mLegend.getColors()[i] != -2)
posX += formTextSpaceAndForm;
mLegend.drawLabel(mDrawCanvas, posX, posY + textDrop, mLegendLabelPaint, i);
posX += Utils.calcTextWidth(mLegendLabelPaint, labels[i])
+ mLegend.getXEntrySpace();
} else {
posX += formSize + stackSpace;
}
}
break;
case BELOW_CHART_RIGHT:
posX = getWidth() - getOffsetRight();
posY = getHeight() - mLegend.getOffsetBottom() / 2f - formSize / 2f;
for (int i = labels.length - 1; i >= 0; i--) {
if (labels[i] != null) {
posX -= Utils.calcTextWidth(mLegendLabelPaint, labels[i])
+ mLegend.getXEntrySpace();
mLegend.drawLabel(mDrawCanvas, posX, posY + textDrop, mLegendLabelPaint, i);
if (mLegend.getColors()[i] != -2)
posX -= formTextSpaceAndForm;
} else {
posX -= stackSpace + formSize;
}
mLegend.drawForm(mDrawCanvas, posX, posY, mLegendFormPaint, i);
}
break;
case RIGHT_OF_CHART:
posX = getWidth() - mLegend.getMaximumEntryLength(mLegendLabelPaint)
- formTextSpaceAndForm;
posY = mLegend.getOffsetTop();
for (int i = 0; i < labels.length; i++) {
mLegend.drawForm(mDrawCanvas, posX + stack, posY, mLegendFormPaint, i);
if (labels[i] != null) {
if (!wasStacked) {
float x = posX;
if (mLegend.getColors()[i] != -2)
x += formTextSpaceAndForm;
posY += textDrop;
mLegend.drawLabel(mDrawCanvas, x, posY,
mLegendLabelPaint, i);
} else {
posY += textSize * 1.2f + formSize;
mLegend.drawLabel(mDrawCanvas, posX, posY,
mLegendLabelPaint, i);
}
// make a step down
posY += mLegend.getYEntrySpace();
stack = 0f;
} else {
stack += formSize + stackSpace;
wasStacked = true;
}
}
break;
case RIGHT_OF_CHART_CENTER:
posX = getWidth() - mLegend.getMaximumEntryLength(mLegendLabelPaint)
- formTextSpaceAndForm;
posY = getHeight() / 2f - mLegend.getFullHeight(mLegendLabelPaint) / 2f;
for (int i = 0; i < labels.length; i++) {
mLegend.drawForm(mDrawCanvas, posX + stack, posY, mLegendFormPaint, i);
if (labels[i] != null) {
if (!wasStacked) {
float x = posX;
if (mLegend.getColors()[i] != -2)
x += formTextSpaceAndForm;
posY += textDrop;
mLegend.drawLabel(mDrawCanvas, x, posY,
mLegendLabelPaint, i);
} else {
posY += textSize * 1.2f + formSize;
mLegend.drawLabel(mDrawCanvas, posX, posY,
mLegendLabelPaint, i);
}
// make a step down
posY += mLegend.getYEntrySpace();
stack = 0f;
} else {
stack += formSize + stackSpace;
wasStacked = true;
}
}
break;
case BELOW_CHART_CENTER:
float fullSize = mLegend.getFullWidth(mLegendLabelPaint);
posX = getWidth() / 2f - fullSize / 2f;
posY = getHeight() - mLegend.getOffsetBottom() / 2f - formSize / 2f;
for (int i = 0; i < labels.length; i++) {
mLegend.drawForm(mDrawCanvas, posX, posY, mLegendFormPaint, i);
// grouped forms have null labels
if (labels[i] != null) {
// make a step to the left
if (mLegend.getColors()[i] != -2)
posX += formTextSpaceAndForm;
mLegend.drawLabel(mDrawCanvas, posX, posY + textDrop, mLegendLabelPaint, i);
posX += Utils.calcTextWidth(mLegendLabelPaint, labels[i])
+ mLegend.getXEntrySpace();
} else {
posX += formSize + stackSpace;
}
}
Log.i(LOG_TAG, "content bottom: " + mContentRect.bottom + ", height: "
+ getHeight() + ", posY: " + posY + ", formSize: " + formSize);
break;
case PIECHART_CENTER:
posX = getWidth()
/ 2f
- (mLegend.getMaximumEntryLength(mLegendLabelPaint) + mLegend
.getXEntrySpace())
/ 2f;
posY = getHeight() / 2f - mLegend.getFullHeight(mLegendLabelPaint) / 2f;
for (int i = 0; i < labels.length; i++) {
mLegend.drawForm(mDrawCanvas, posX + stack, posY, mLegendFormPaint, i);
if (labels[i] != null) {
if (!wasStacked) {
float x = posX;
if (mLegend.getColors()[i] != -2)
x += formTextSpaceAndForm;
posY += textDrop;
mLegend.drawLabel(mDrawCanvas, x, posY,
mLegendLabelPaint, i);
} else {
posY += textSize * 1.2f + formSize;
mLegend.drawLabel(mDrawCanvas, posX, posY,
mLegendLabelPaint, i);
}
// make a step down
posY += mLegend.getYEntrySpace();
stack = 0f;
} else {
stack += formSize + stackSpace;
wasStacked = true;
}
}
break;
case RIGHT_OF_CHART_INSIDE:
posX = getWidth() - mLegend.getMaximumEntryLength(mLegendLabelPaint)
- formTextSpaceAndForm;
posY = mLegend.getOffsetTop();
for (int i = 0; i < labels.length; i++) {
mLegend.drawForm(mDrawCanvas, posX + stack, posY, mLegendFormPaint, i);
if (labels[i] != null) {
if (!wasStacked) {
float x = posX;
if (mLegend.getColors()[i] != -2)
x += formTextSpaceAndForm;
posY += textDrop;
mLegend.drawLabel(mDrawCanvas, x, posY,
mLegendLabelPaint, i);
} else {
posY += textSize * 1.2f + formSize;
mLegend.drawLabel(mDrawCanvas, posX, posY,
mLegendLabelPaint, i);
}
// make a step down
posY += mLegend.getYEntrySpace();
stack = 0f;
} else {
stack += formSize + stackSpace;
wasStacked = true;
}
}
break;
case NONE:
break;
}
}
/**
* draws the description text in the bottom right corner of the chart
*/
protected void drawDescription() {
mDrawCanvas
.drawText(mDescription, getWidth() - mOffsetRight - 10, getHeight() - mOffsetBottom
- 10, mDescPaint);
}
/**
* draws all the text-values to the chart
*/
protected abstract void drawValues();
/**
* draws the actual data
*/
protected abstract void drawData();
/**
* draws additional stuff, whatever that might be
*/
protected abstract void drawAdditional();
/**
* draws the values of the chart that need highlightning
*/
protected abstract void drawHighlights();
/**
* ################ ################ ################ ################
*/
/** BELOW THIS CODE FOR HIGHLIGHTING */
/**
* array of Highlight objects that reference the highlighted slices in the
* chart
*/
protected Highlight[] mIndicesToHightlight = new Highlight[0];
/**
* Returns true if there are values to highlight, false if there are no
* values to highlight. Checks if the highlight array is null, has a length
* of zero or if the first object is null.
*
* @return
*/
public boolean valuesToHighlight() {
return mIndicesToHightlight == null || mIndicesToHightlight.length <= 0
|| mIndicesToHightlight[0] == null ? false
: true;
}
/**
* Highlights the values at the given indices in the given DataSets. Provide
* null or an empty array to undo all highlighting. This should be used to
* programmatically highlight values. This DOES NOT generate a callback to
* the OnChartValueSelectedListener.
*
* @param highs
*/
public void highlightValues(Highlight[] highs) {
// set the indices to highlight
mIndicesToHightlight = highs;
// redraw the chart
invalidate();
}
/**
* Highlights the value at the given x-index in the given DataSet. Provide
* -1 as the x-index to undo all highlighting.
*
* @param xIndex
* @param dataSetIndex
*/
public void highlightValue(int xIndex, int dataSetIndex) {
if (xIndex < 0 || dataSetIndex < 0 || xIndex >= mOriginalData.getXValCount()
|| dataSetIndex >= mOriginalData.getDataSetCount()) {
highlightValues(null);
} else {
highlightValues(new Highlight[] {
new Highlight(xIndex, dataSetIndex)
});
}
}
/**
* Highlights the value selected by touch gesture. Unlike
* highlightValues(...), this generates a callback to the
* OnChartValueSelectedListener.
*
* @param highs
*/
public void highlightTouch(Highlight high) {
if (high == null)
mIndicesToHightlight = null;
else {
// set the indices to highlight
mIndicesToHightlight = new Highlight[] {
high
};
}
// redraw the chart
invalidate();
if (mSelectionListener != null) {
if (!valuesToHighlight())
mSelectionListener.onNothingSelected();
else {
Entry e = getEntryByDataSetIndex(high.getXIndex(),
high.getDataSetIndex());
// notify the listener
mSelectionListener.onValueSelected(e, high.getDataSetIndex());
}
}
}
/**
* ################ ################ ################ ################
*/
/** BELOW CODE IS FOR THE MARKER VIEW */
/** if set to true, the marker view is drawn when a value is clicked */
protected boolean mDrawMarkerViews = true;
/** the view that represents the marker */
protected MarkerView mMarkerView;
/**
* draws all MarkerViews on the highlighted positions
*/
protected void drawMarkers() {
// if there is no marker view or drawing marker is disabled
if (mMarkerView == null || !mDrawMarkerViews || !valuesToHighlight())
return;
for (int i = 0; i < mIndicesToHightlight.length; i++) {
int xIndex = mIndicesToHightlight[i].getXIndex();
int dataSetIndex = mIndicesToHightlight[i].getDataSetIndex();
if (xIndex <= mDeltaX && xIndex <= mDeltaX * mPhaseX) {
Entry e = getEntryByDataSetIndex(xIndex, dataSetIndex);
// make sure entry not null
if (e == null)
continue;
float[] pos = getMarkerPosition(e, dataSetIndex);
// check bounds
if (pos[0] < mOffsetLeft || pos[0] > getWidth() - mOffsetRight
|| pos[1] < mOffsetTop || pos[1] > getHeight() - mOffsetBottom)
continue;
// callbacks to update the content
mMarkerView.refreshContent(e, dataSetIndex);
mMarkerView.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
mMarkerView.layout(0, 0, mMarkerView.getMeasuredWidth(),
mMarkerView.getMeasuredHeight());
mMarkerView.draw(mDrawCanvas, pos[0], pos[1]);
}
}
}
/**
* Returns the actual position in pixels of the MarkerView for the given
* Entry in the given DataSet.
*
* @param xIndex
* @param dataSetIndex
* @return
*/
private float[] getMarkerPosition(Entry e, int dataSetIndex) {
float xPos = (float) e.getXIndex();
// make sure the marker is in the center of the bars in BarChart and
// CandleStickChart
if (this instanceof CandleStickChart)
xPos += 0.5f;
else if (this instanceof BarChart) {
BarData bd = (BarData) mCurrentData;
float space = bd.getGroupSpace();
float j = mCurrentData.getDataSetByIndex(dataSetIndex)
.getEntryPosition(e);
float x = (j * (mCurrentData.getDataSetCount() - 1)) + dataSetIndex + space * j + space
/ 2f + 0.5f;
xPos += x;
} else if (this instanceof RadarChart) {
RadarChart rc = (RadarChart) this;
float angle = rc.getSliceAngle() * e.getXIndex() + rc.getRotationAngle();
float val = e.getVal() * rc.getFactor();
PointF c = getCenterOffsets();
PointF p = new PointF((float) (c.x + val * Math.cos(Math.toRadians(angle))),
(float) (c.y + val * Math.sin(Math.toRadians(angle))));
return new float[] {
p.x, p.y
};
}
// position of the marker depends on selected value index and value
float[] pts = new float[] {
xPos, e.getVal() * mPhaseY
};
transformPointArray(pts);
return pts;
}
/**
* ################ ################ ################ ################
* Animation support below Honeycomb thanks to Jake Wharton's awesome
* nineoldandroids library: https://github.com/JakeWharton/NineOldAndroids
*/
/** CODE BELOW THIS RELATED TO ANIMATION */
/** the phase that is animated and influences the drawn values on the y-axis */
protected float mPhaseY = 1f;
/** the phase that is animated and influences the drawn values on the x-axis */
protected float mPhaseX = 1f;
/** objectanimator used for animating values on y-axis */
private ObjectAnimator mAnimatorY;
/** objectanimator used for animating values on x-axis */
private ObjectAnimator mAnimatorX;
/**
* Animates the drawing / rendering of the chart on both x- and y-axis with
* the specified animation time. If animate(...) is called, no further
* calling of invalidate() is necessary to refresh the chart.
*
* @param durationMillisX
* @param durationMillisY
*/
public void animateXY(int durationMillisX, int durationMillisY) {
mAnimatorY = ObjectAnimator.ofFloat(this, "phaseY", 0f, 1f);
mAnimatorY.setDuration(
durationMillisY);
mAnimatorX = ObjectAnimator.ofFloat(this, "phaseX", 0f, 1f);
mAnimatorX.setDuration(
durationMillisX);
// make sure only one animator produces update-callbacks (which then
// call invalidate())
if (durationMillisX > durationMillisY) {
mAnimatorX.addUpdateListener(this);
} else {
mAnimatorY.addUpdateListener(this);
}
mAnimatorX.start();
mAnimatorY.start();
}
/**
* Animates the rendering of the chart on the x-axis with the specified
* animation time. If animate(...) is called, no further calling of
* invalidate() is necessary to refresh the chart.
*
* @param durationMillis
*/
public void animateX(int durationMillis) {
mAnimatorX = ObjectAnimator.ofFloat(this, "phaseX", 0f, 1f);
mAnimatorX.setDuration(durationMillis);
mAnimatorX.addUpdateListener(this);
mAnimatorX.start();
}
/**
* Animates the rendering of the chart on the y-axis with the specified
* animation time. If animate(...) is called, no further calling of
* invalidate() is necessary to refresh the chart.
*
* @param durationMillis
*/
public void animateY(int durationMillis) {
mAnimatorY = ObjectAnimator.ofFloat(this, "phaseY", 0f, 1f);
mAnimatorY.setDuration(durationMillis);
mAnimatorY.addUpdateListener(this);
mAnimatorY.start();
}
@Override
public void onAnimationUpdate(ValueAnimator va) {
// redraw everything after animation value change
invalidate();
// Log.i(LOG_TAG, "UPDATING, x: " + mPhaseX + ", y: " + mPhaseY);
}
/**
* This gets the y-phase that is used to animate the values.
*
* @return
*/
public float getPhaseY() {
return mPhaseY;
}
/**
* This modifys the y-phase that is used to animate the values.
*
* @param phase
*/
public void setPhaseY(float phase) {
mPhaseY = phase;
}
/**
* This gets the x-phase that is used to animate the values.
*
* @return
*/
public float getPhaseX() {
return mPhaseX;
}
/**
* This modifys the x-phase that is used to animate the values.
*
* @param phase
*/
public void setPhaseX(float phase) {
mPhaseX = phase;
}
/**
* ################ ################ ################ ################
*/
/** BELOW THIS FOR DYNAMICALLY ADDING ENTRIES AND DATASETS */
// public void addEntry(Entry e, int dataSetIndex) {
// mOriginalData.getDataSetByIndex(dataSetIndex).addEntry(e);
//
// prepare();
// calcMinMax(false);
// prepareMatrix();
// calculateOffsets();
// }
//
// public void addEntry(Entry e, String label) {
// mOriginalData.getDataSetByLabel(label, false).addEntry(e);
//
// prepare();
// calcMinMax(false);
// prepareMatrix();
// calculateOffsets();
// }
//
// public void addDataSet(DataSet d) {
// mOriginalData.addDataSet(d);
//
// prepare();
// calcMinMax(false);
// prepareMatrix();
// calculateOffsets();
// }
/**
* ################ ################ ################ ################
*/
/** BELOW THIS ONLY GETTERS AND SETTERS */
/**
* Returns the canvas object the chart uses for drawing.
*
* @return
*/
public Canvas getCanvas() {
return mDrawCanvas;
}
/**
* set a selection listener for the chart
*
* @param l
*/
public void setOnChartValueSelectedListener(OnChartValueSelectedListener l) {
this.mSelectionListener = l;
}
/**
* Sets a gesture-listener for the chart for custom callbacks when executing
* gestures on the chart surface.
*
* @param l
*/
public void setOnChartGestureListener(OnChartGestureListener l) {
this.mGestureListener = l;
}
/**
* Returns the custom gesture listener.
*
* @return
*/
public OnChartGestureListener getOnChartGestureListener() {
return mGestureListener;
}
/**
* If set to true, value highlighting is enabled which means that values can
* be highlighted programmatically or by touch gesture.
*
* @param enabled
*/
public void setHighlightEnabled(boolean enabled) {
mHighlightEnabled = enabled;
}
/**
* returns true if highlighting of values is enabled, false if not
*
* @return
*/
public boolean isHighlightEnabled() {
return mHighlightEnabled;
}
/**
* returns the total value (sum) of all y-values across all DataSets
*
* @return
*/
public float getYValueSum() {
return mCurrentData.getYValueSum();
}
/**
* returns the current y-max value across all DataSets
*
* @return
*/
public float getYMax() {
return mCurrentData.getYMax();
}
/**
* returns the lowest value the chart can display
*
* @return
*/
public float getYChartMin() {
return mYChartMin;
}
/**
* returns the highest value the chart can display
*
* @return
*/
public float getYChartMax() {
return mYChartMax;
}
/**
* returns the current y-min value across all DataSets
*
* @return
*/
public float getYMin() {
return mCurrentData.getYMin();
}
/**
* Get the total number of X-values.
*
* @return
*/
public float getDeltaX() {
return mDeltaX;
}
/**
* returns the average value of all values the chart holds
*
* @return
*/
public float getAverage() {
return getYValueSum() / mCurrentData.getYValCount();
}
/**
* returns the average value for a specific DataSet (with a specific label)
* in the chart
*
* @param dataSetLabel
* @return
*/
public float getAverage(String dataSetLabel) {
DataSet<? extends Entry> ds = mCurrentData.getDataSetByLabel(dataSetLabel, true);
return ds.getYValueSum()
/ ds.getEntryCount();
}
/**
* returns the total number of values the chart holds (across all DataSets)
*
* @return
*/
public int getValueCount() {
return mCurrentData.getYValCount();
}
/**
* Returns the center point of the chart (the whole View) in pixels.
*
* @return
*/
public PointF getCenter() {
return new PointF(getWidth() / 2f, getHeight() / 2f);
}
/**
* Returns the center of the chart taking offsets under consideration.
* (returns the center of the content rectangle)
*
* @return
*/
public PointF getCenterOffsets() {
return new PointF(mContentRect.centerX(), mContentRect.centerY());
}
/**
* sets the size of the description text in pixels, min 7f, max 14f
*
* @param size
*/
public void setDescriptionTextSize(float size) {
if (size > 14f)
size = 14f;
if (size < 7f)
size = 7f;
mInfoPaint.setTextSize(Utils.convertDpToPixel(size));
}
/**
* Set this to true to enable logcat outputs for the chart. Default:
* disabled
*
* @param enabled
*/
public void setLogEnabled(boolean enabled) {
mLogEnabled = enabled;
}
/**
* set a description text that appears in the bottom right corner of the
* chart, size = Y-legend text size
*
* @param desc
*/
public void setDescription(String desc) {
this.mDescription = desc;
}
/**
* Sets the text that informs the user that there is no data available with
* which to draw the chart.
*
* @param text
*/
public void setNoDataText(String text) {
mNoDataText = text;
}
/**
* Sets descriptive text to explain to the user why there is no chart
* available Defaults to empty if not set
*
* @param text
*/
public void setNoDataTextDescription(String text) {
mNoDataTextDescription = text;
}
/**
* Sets the offsets from the border of the view to the actual chart in every
* direction manually. Provide density pixels -> they are then rendered to
* pixels inside the chart
*
* @param left
* @param right
* @param top
* @param bottom
*/
public void setOffsets(float left, float top, float right, float bottom) {
mOffsetBottom = Utils.convertDpToPixel(bottom);
mOffsetLeft = Utils.convertDpToPixel(left);
mOffsetRight = Utils.convertDpToPixel(right);
mOffsetTop = Utils.convertDpToPixel(top);
}
public float getOffsetLeft() {
return mOffsetLeft;
}
public float getOffsetBottom() {
return mOffsetBottom;
}
public float getOffsetRight() {
return mOffsetRight;
}
public float getOffsetTop() {
return mOffsetTop;
}
/**
* Set this to false to disable all gestures and touches on the chart,
* default: true
*
* @param enabled
*/
public void setTouchEnabled(boolean enabled) {
this.mTouchEnabled = enabled;
}
/**
* set this to true to draw y-values on the chart NOTE (for bar and
* linechart): if "maxvisiblecount" is reached, no values will be drawn even
* if this is enabled
*
* @param enabled
*/
public void setDrawYValues(boolean enabled) {
this.mDrawYValues = enabled;
}
/**
* sets the view that is displayed when a value is clicked on the chart
*
* @param v
*/
public void setMarkerView(MarkerView v) {
mMarkerView = v;
}
/**
* returns the view that is set as a marker view for the chart
*
* @return
*/
public MarkerView getMarkerView() {
return mMarkerView;
}
/**
* if set to true, units are drawn next to values in the chart, default:
* false
*
* @param enabled
*/
public void setDrawUnitsInChart(boolean enabled) {
mDrawUnitInChart = enabled;
}
/**
* sets the unit that is drawn next to the values in the chart, e.g. %
*
* @param unit
*/
public void setUnit(String unit) {
mUnit = unit;
}
/**
* Returns the unit that is used for the values in the chart
*
* @return
*/
public String getUnit() {
return mUnit;
}
/**
* set this to true to draw the legend, false if not
*
* @param enabled
*/
public void setDrawLegend(boolean enabled) {
mDrawLegend = enabled;
}
/**
* returns true if drawing the legend is enabled, false if not
*
* @return
*/
public boolean isDrawLegendEnabled() {
return mDrawLegend;
}
/**
* Returns the legend object of the chart. This method can be used to
* customize the automatically generated legend. IMPORTANT: this will return
* null if no data has been set for the chart when calling this method
*
* @return
*/
public Legend getLegend() {
return mLegend;
}
/**
* Returns the rectangle that defines the borders of the chart-value surface
* (into which the actual values are drawn).
*
* @return
*/
public RectF getContentRect() {
return mContentRect;
}
/**
* disables intercept touchevents
*/
public void disableScroll() {
ViewParent parent = getParent();
parent.requestDisallowInterceptTouchEvent(true);
}
/**
* enables intercept touchevents
*/
public void enableScroll() {
ViewParent parent = getParent();
try {
parent.requestDisallowInterceptTouchEvent(false);
} catch (NullPointerException e) {
// Do nothing for the moment
}
}
/** paint for the grid lines (only line and barchart) */
public static final int PAINT_GRID = 3;
/** paint for the grid background (only line and barchart) */
public static final int PAINT_GRID_BACKGROUND = 4;
/** paint for the y-legend values (only line and barchart) */
public static final int PAINT_YLABEL = 5;
/** paint for the x-legend values (only line and barchart) */
public static final int PAINT_XLABEL = 6;
/**
* paint for the info text that is displayed when there are no values in the
* chart
*/
public static final int PAINT_INFO = 7;
/** paint for the value text */
public static final int PAINT_VALUES = 8;
/** paint for the inner circle (linechart) */
public static final int PAINT_CIRCLES_INNER = 10;
/** paint for the description text in the bottom right corner */
public static final int PAINT_DESCRIPTION = 11;
/** paint for the line surrounding the chart (only line and barchart) */
public static final int PAINT_BORDER = 12;
/** paint for the hole in the middle of the pie chart */
public static final int PAINT_HOLE = 13;
/** paint for the text in the middle of the pie chart */
public static final int PAINT_CENTER_TEXT = 14;
/** paint for highlightning the values of a linechart */
public static final int PAINT_HIGHLIGHT = 15;
/** paint object used for the limit lines */
public static final int PAINT_RADAR_WEB = 16;
/** paint used for all rendering processes */
public static final int PAINT_RENDER = 17;
/** paint used for the legend */
public static final int PAINT_LEGEND_LABEL = 18;
/** paint object used for the limit lines */
public static final int PAINT_LIMIT_LINE = 19;
/**
* set a new paint object for the specified parameter in the chart e.g.
* Chart.PAINT_VALUES
*
* @param p the new paint object
* @param which Chart.PAINT_VALUES, Chart.PAINT_GRID, Chart.PAINT_VALUES,
* ...
*/
public void setPaint(Paint p, int which) {
switch (which) {
case PAINT_INFO:
mInfoPaint = p;
break;
case PAINT_DESCRIPTION:
mDescPaint = p;
break;
case PAINT_VALUES:
mValuePaint = p;
break;
case PAINT_RENDER:
mRenderPaint = p;
break;
case PAINT_LEGEND_LABEL:
mLegendLabelPaint = p;
break;
case PAINT_XLABEL:
mXLabelPaint = p;
break;
case PAINT_YLABEL:
mYLabelPaint = p;
break;
case PAINT_HIGHLIGHT:
mHighlightPaint = p;
break;
case PAINT_LIMIT_LINE:
mLimitLinePaint = p;
break;
}
}
/**
* Returns the paint object associated with the provided constant.
*
* @param which e.g. Chart.PAINT_LEGEND_LABEL
* @return
*/
public Paint getPaint(int which) {
switch (which) {
case PAINT_INFO:
return mInfoPaint;
case PAINT_DESCRIPTION:
return mDescPaint;
case PAINT_VALUES:
return mValuePaint;
case PAINT_RENDER:
return mRenderPaint;
case PAINT_LEGEND_LABEL:
return mLegendLabelPaint;
case PAINT_XLABEL:
return mXLabelPaint;
case PAINT_YLABEL:
return mYLabelPaint;
case PAINT_HIGHLIGHT:
return mHighlightPaint;
case PAINT_LIMIT_LINE:
return mLimitLinePaint;
}
return null;
}
/**
* returns true if drawing the marker-view is enabled when tapping on values
* (use the setMarkerView(View v) method to specify a marker view)
*
* @return
*/
public boolean isDrawMarkerViewEnabled() {
return mDrawMarkerViews;
}
/**
* Set this to true to draw a user specified marker-view when tapping on
* chart values (use the setMarkerView(MarkerView mv) method to specify a
* marker view). Default: true
*
* @param enabled
*/
public void setDrawMarkerViews(boolean enabled) {
mDrawMarkerViews = enabled;
}
/**
* Sets the formatter to be used for drawing the values inside the chart. If
* no formatter is set, the chart will automatically determine a reasonable
* formatting (concerning decimals) for all the values that are drawn inside
* the chart. Set this to NULL to re-enable auto formatting.
*
* @param f
*/
public void setValueFormatter(ValueFormatter f) {
mValueFormatter = f;
if (f == null)
mUseDefaultFormatter = true;
else
mUseDefaultFormatter = false;
}
/**
* Returns the formatter used for drawing the values inside the chart.
*
* @return
*/
public ValueFormatter getValueFormatter() {
return mValueFormatter;
}
/**
* sets the draw color for the value paint object
*
* @param color
*/
public void setValueTextColor(int color) {
mValuePaint.setColor(color);
}
/**
* Sets the font size of the values that are drawn inside the chart.
*
* @param size
*/
public void setValueTextSize(float size) {
mValuePaint.setTextSize(Utils.convertDpToPixel(size));
}
/**
* returns true if y-value drawing is enabled, false if not
*
* @return
*/
public boolean isDrawYValuesEnabled() {
return mDrawYValues;
}
/**
* returns the x-value at the given index
*
* @param index
* @return
*/
public String getXValue(int index) {
if (mCurrentData == null || mCurrentData.getXValCount() <= index)
return null;
else
return mCurrentData.getXVals().get(index);
}
/**
* returns the y-value for the given index from the DataSet with the given
* label
*
* @param index
* @param dataSetLabel
* @return
*/
public float getYValue(int index, String dataSetLabel) {
DataSet<? extends Entry> set = mCurrentData.getDataSetByLabel(dataSetLabel, true);
return set.getYVals().get(index).getVal();
}
/**
* returns the y-value for the given x-index and DataSet index
*
* @param index
* @param dataSet
* @return
*/
public float getYValue(int xIndex, int dataSetIndex) {
DataSet<? extends Entry> set = mCurrentData.getDataSetByIndex(dataSetIndex);
return set.getYValForXIndex(xIndex);
}
/**
* returns the DataSet with the given index in the DataSet array held by the
* ChartData object.
*
* @param index
* @return
*/
public DataSet<? extends Entry> getDataSetByIndex(int index) {
return mCurrentData.getDataSetByIndex(index);
}
/**
* returns the DataSet with the given label that is stored in the ChartData
* object.
*
* @param type
* @return
*/
public DataSet<? extends Entry> getDataSetByLabel(String dataSetLabel) {
return mCurrentData.getDataSetByLabel(dataSetLabel, true);
}
/**
* returns the Entry object from the first DataSet stored in the ChartData
* object. If multiple DataSets are used, use getEntry(index, type) or
* getEntryByDataSetIndex(xIndex, dataSetIndex);
*
* @param index
* @return
*/
public Entry getEntry(int index) {
return mCurrentData.getDataSetByIndex(0).getYVals().get(index);
}
/**
* returns the Entry object at the given index from the DataSet with the
* given label.
*
* @param index
* @param dataSetLabel
* @return
*/
public Entry getEntry(int index, String dataSetLabel) {
return mCurrentData.getDataSetByLabel(dataSetLabel, true).getYVals().get(index);
}
/**
* Returns the corresponding Entry object at the given xIndex from the given
* DataSet. INFORMATION: This method does calculations at runtime. Do not
* over-use in performance critical situations.
*
* @param xIndex
* @param dataSetIndex
* @return
*/
public Entry getEntryByDataSetIndex(int xIndex, int dataSetIndex) {
return mCurrentData.getDataSetByIndex(dataSetIndex).getEntryForXIndex(xIndex);
}
/**
* Returns an array of SelInfo objects for the given x-index. The SelInfo
* objects give information about the value at the selected index and the
* DataSet it belongs to. INFORMATION: This method does calculations at
* runtime. Do not over-use in performance critical situations.
*
* @param xIndex
* @return
*/
public ArrayList<SelInfo> getYValsAtIndex(int xIndex) {
ArrayList<SelInfo> vals = new ArrayList<SelInfo>();
for (int i = 0; i < mCurrentData.getDataSetCount(); i++) {
// extract all y-values from all DataSets at the given x-index
float yVal = mCurrentData.getDataSetByIndex(i).getYValForXIndex(xIndex);
if (!Float.isNaN(yVal)) {
vals.add(new SelInfo(yVal, i));
}
}
return vals;
}
/**
* Get all Entry objects at the given index across all DataSets.
* INFORMATION: This method does calculations at runtime. Do not over-use in
* performance critical situations.
*
* @param xIndex
* @return
*/
public ArrayList<Entry> getEntriesAtIndex(int xIndex) {
ArrayList<Entry> vals = new ArrayList<Entry>();
for (int i = 0; i < mCurrentData.getDataSetCount(); i++) {
DataSet<? extends Entry> set = mCurrentData.getDataSetByIndex(i);
Entry e = set.getEntryForXIndex(xIndex);
if (e != null) {
vals.add(e);
}
}
return vals;
}
/**
* Returns the ChartData object the chart CURRENTLY represents (not
* dependant on zoom level). It contains all values and information the
* chart displays. If filtering algorithms have been applied, this returns
* the filtered state of data.
*
* @return
*/
public T getDataCurrent() {
return mCurrentData;
}
/**
* Returns the ChartData object that ORIGINALLY has been set for the chart.
* It contains all data in an unaltered state, before any filtering
* algorithms have been applied.
*
* @return
*/
public T getDataOriginal() {
return mOriginalData;
}
/**
* returns the percentage the given value has of the total y-value sum
*
* @param val
* @return
*/
public float getPercentOfTotal(float val) {
return val / mCurrentData.getYValueSum() * 100f;
}
/**
* sets a typeface for the value-paint
*
* @param t
*/
public void setValueTypeface(Typeface t) {
mValuePaint.setTypeface(t);
}
/**
* sets the typeface for the description paint
*
* @param t
*/
public void setDescriptionTypeface(Typeface t) {
mDescPaint.setTypeface(t);
}
/**
* Returns the bitmap that represents the chart.
*
* @return
*/
public Bitmap getChartBitmap() {
// Define a bitmap with the same size as the view
Bitmap returnedBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565);
// Bind a canvas to it
Canvas canvas = new Canvas(returnedBitmap);
// Get the view's background
Drawable bgDrawable = getBackground();
if (bgDrawable != null)
// has background drawable, then draw it on the canvas
bgDrawable.draw(canvas);
else
// does not have background drawable, then draw white background on
// the canvas
canvas.drawColor(Color.WHITE);
// draw the view on the canvas
draw(canvas);
// return the bitmap
return returnedBitmap;
}
/**
* Saves the current chart state with the given name to the given path on
* the sdcard leaving the path empty "" will put the saved file directly on
* the SD card chart is saved as a PNG image, example:
* saveToPath("myfilename", "foldername1/foldername2");
*
* @param title
* @param pathOnSD e.g. "folder1/folder2/folder3"
* @return returns true on success, false on error
*/
public boolean saveToPath(String title, String pathOnSD) {
Bitmap b = getChartBitmap();
OutputStream stream = null;
try {
stream = new FileOutputStream(Environment.getExternalStorageDirectory().getPath()
+ pathOnSD + "/" + title
+ ".png");
/*
* Write bitmap to file using JPEG or PNG and 40% quality hint for
* JPEG.
*/
b.compress(CompressFormat.PNG, 40, stream);
stream.close();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Saves the current state of the chart to the gallery as a JPEG image. The
* filename and compression can be set. 0 == maximum compression, 100 = low
* compression (high quality). NOTE: Needs permission WRITE_EXTERNAL_STORAGE
*
* @param fileName e.g. "my_image"
* @param quality e.g. 50, min = 0, max = 100
* @return returns true if saving was successfull, false if not
*/
public boolean saveToGallery(String fileName, int quality) {
// restrain quality
if (quality < 0 || quality > 100)
quality = 50;
long currentTime = System.currentTimeMillis();
File extBaseDir = Environment.getExternalStorageDirectory();
File file = new File(extBaseDir.getAbsolutePath() + "/DCIM");
if (!file.exists()) {
if (!file.mkdirs()) {
return false;
}
}
String filePath = file.getAbsolutePath() + "/" + fileName;
FileOutputStream out = null;
try {
out = new FileOutputStream(filePath);
Bitmap b = getChartBitmap();
b.compress(Bitmap.CompressFormat.JPEG, quality, out); // control
// the jpeg
// quality
out.flush();
out.close();
} catch (IOException e) {
e.printStackTrace();
return false;
}
long size = new File(filePath).length();
ContentValues values = new ContentValues(8);
// store the details
values.put(Images.Media.TITLE, fileName);
values.put(Images.Media.DISPLAY_NAME, fileName);
values.put(Images.Media.DATE_ADDED, currentTime);
values.put(Images.Media.MIME_TYPE, "image/jpeg");
values.put(Images.Media.DESCRIPTION, "MPAndroidChart-Library Save");
values.put(Images.Media.ORIENTATION, 0);
values.put(Images.Media.DATA, filePath);
values.put(Images.Media.SIZE, size);
return getContext().getContentResolver().insert(Images.Media.EXTERNAL_CONTENT_URI, values) == null
? false : true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
prepareContentRect();
//
// prepareContentRect();
// Log.i(LOG_TAG,
// "onLayout(), width: " + mContentRect.width() + ", height: " +
// mContentRect.height());
//
// calculateOffsets();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// create a new bitmap with the new dimensions
mDrawBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_4444);
mDrawCanvas = new Canvas(mDrawBitmap);
// prepare content rect and matrices
prepareContentRect();
prepare();
super.onSizeChanged(w, h, oldw, oldh);
}
/**
* Default formatter used for formatting values. Uses a DecimalFormat with
* pre-calculated number of digits (depending on max and min value).
*
* @author Philipp Jahoda
*/
private class DefaultValueFormatter implements ValueFormatter {
/** decimalformat for formatting */
private DecimalFormat mFormat;
public DefaultValueFormatter(DecimalFormat f) {
mFormat = f;
}
@Override
public String getFormattedValue(float value) {
// avoid memory allocations here (for performance)
return mFormat.format(value);
}
}
// @Override
// protected void onAttachedToWindow() {
// super.onAttachedToWindow();
// if (isInEditMode()) {
// initWithDummyData();
// }
// }
}