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.os.Environment; import android.provider.MediaStore.Images; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.View; 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.OnChartValueSelectedListener; import com.github.mikephil.charting.utils.Highlight; import com.github.mikephil.charting.utils.Legend; import com.github.mikephil.charting.utils.MarkerView; import com.github.mikephil.charting.utils.SelInfo; import com.github.mikephil.charting.utils.Utils; 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.util.ArrayList; /** * Baseclass of all Chart-Views. * * @author Philipp Jahoda */ public abstract class Chart extends View implements AnimatorUpdateListener { public static final String LOG_TAG = "MPChart"; protected int mColorDarkBlue = Color.rgb(41, 128, 186); protected int mColorDarkRed = Color.rgb(232, 76, 59); /** * string that is drawn next to the values in the chart, indicating their * unit */ protected String mUnit = ""; /** * flag that holds the background color of the view and the color the canvas * is cleared with */ private int mBackgroundColor = Color.WHITE; /** * defines the number of digits to use for all printed values, -1 means * automatically determine */ protected int mValueDigitsToUse = -1; /** * defines the number of digits all printed values */ protected int mValueFormatDigits = -1; /** 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 ChartData 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 ChartData mOriginalData = null; /** final bitmap that contains all information and is drawn to the screen */ protected Bitmap mDrawBitmap; /** 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 object used for darwing the bitmap to the screen */ protected Paint mDrawPaint; /** 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; /** 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; /** if true, thousands values are separated by a dot */ protected boolean mSeparateTousands = 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."; /** * 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() { // 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); mDrawPaint = new Paint(); 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)); } // 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(); // } protected boolean mOffsetsCalculated = false; /** * Sets a new ChartData object for the chart. * * @param data */ protected void setData(ChartData data) { if (data == null || !data.isValid()) { Log.e(LOG_TAG, "Cannot set data for chart. Provided chart values are null or contain less than 2 entries."); mDataNotSet = true; return; } // LET THE CHART KNOW THERE IS DATA mDataNotSet = false; mOffsetsCalculated = false; mCurrentData = data; mOriginalData = data; prepare(); Log.i(LOG_TAG, "Data is set."); } // /** // * Sets primitive data for the chart. Internally, this is converted into a // * ChartData object with one DataSet (type 0). If you have more specific // * requirements for your data, use the setData(ChartData data) method and // * create your own ChartData object with as many DataSets as you like. // * // * @param xVals // * @param yVals // */ // public void setData(ArrayList<String> xVals, ArrayList<Float> yVals) { // // ArrayList<Entry> series = new ArrayList<Entry>(); // // for (int i = 0; i < yVals.size(); i++) { // series.add(new Entry(yVals.get(i), i)); // } // // DataSet set = new DataSet(series, "DataSet"); // ArrayList<DataSet> dataSets = new ArrayList<DataSet>(); // dataSets.add(set); // // ChartData data = new ChartData(xVals, dataSets); // // setData(data); // } // /** // * Sets primitive data for the chart. Internally, this is converted into a // * ChartData object with one DataSet (type 0). If you have more specific // * requirements for your data, use the setData(ChartData data) method and // * create your own ChartData object with as many DataSets as you like. // * // * @param xVals // * @param yVals // */ // public void setData(ArrayList<String> xVals, ArrayList<Float> yVals) { // // ArrayList<Entry> series = new ArrayList<Entry>(); // // for (int i = 0; i < yVals.size(); i++) { // series.add(new Entry(yVals.get(i), i)); // } // // DataSet set = new DataSet(series, "DataSet"); // ArrayList<DataSet> dataSets = new ArrayList<DataSet>(); // dataSets.add(set); // // ChartData data = new ChartData(xVals, dataSets); // // setData(data); // } /** * does needed preparations for drawing */ public abstract void prepare(); /** lets the chart know its unterlying data has changed */ 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; } @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) { // use RGB_565 for best performance mDrawBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.RGB_565); mDrawCanvas = new Canvas(mDrawBitmap); } mDrawCanvas.drawColor(mBackgroundColor); // clear all } /** * setup all the matrices that will be used for scaling the coordinates to * the display */ protected void prepareMatrix() { float scaleX = (float) ((getWidth() - mOffsetLeft - mOffsetRight) / mDeltaX); float scaleY = (float) ((getHeight() - mOffsetBottom - mOffsetTop) / mDeltaY); // setup all matrices mMatrixValueToPx.reset(); mMatrixValueToPx.postTranslate(0, -mYChartMin); mMatrixValueToPx.postScale(scaleX, -scaleY); mMatrixOffset.reset(); mMatrixOffset.postTranslate(mOffsetLeft, getHeight() - mOffsetBottom); // mMatrixOffset.setTranslate(mOffsetLeft, 0); // mMatrixOffset.postScale(1.0f, -1.0f); Log.i(LOG_TAG, "Matrices prepared."); } /** * sets up the content rect that restricts the chart surface */ protected void prepareContentRect() { mContentRect.set((int) mOffsetLeft, (int) mOffsetTop, getMeasuredWidth() - (int) mOffsetRight, getMeasuredHeight() - (int) mOffsetBottom + 1); } /** * 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 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) { valuePoints[j] = entries.get(j / 2).getXIndex(); valuePoints[j + 1] = entries.get(j / 2).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) 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; // Log.i(LOG_TAG, "OffsetBottom: " + mLegend.getOffsetBottom() + // ", Formsize: " + formSize + ", Textsize: " + textSize + // ", TextDrop: " + textDrop); float posX, posY; 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(); float stack = 0f; boolean wasStacked = false; 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; } } /** * 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]; /** * checks if the given index in the given DataSet is set for highlighting or * not * * @param xIndex * @param dataSetIndex * @return */ public boolean needsHighlight(int xIndex, int dataSetIndex) { // no highlight if (!valuesToHighlight()) return false; for (int i = 0; i < mIndicesToHightlight.length; i++) // check if the xvalue for the given dataset needs highlight if (mIndicesToHightlight[i].getXIndex() == xIndex && mIndicesToHightlight[i].getDataSetIndex() == dataSetIndex && xIndex <= mDeltaX) return true; return false; } /** * 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 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); // callbacks to update the content mMarkerView.refreshContent(e, dataSetIndex); 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 */ /** * set a selection listener for the chart * * @param l */ public void setOnChartValueSelectedListener(OnChartValueSelectedListener l) { this.mSelectionListener = l; } /** * 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 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 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; } /** 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; } } /** * 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; } 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 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)); } /** * set this to true to separate thousands values by a dot. Default: true * * @param enabled */ public void setSeparateThousands(boolean enabled) { mSeparateTousands = enabled; } /** * 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 first DataSet. If * multiple DataSets are used, please use getYValue(int index, int type); * * @param index * @return */ public float getYValue(int index) { return mCurrentData.getDataSetByIndex(0).getYVals().get(index).getVal(); } /** * 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 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 getYValueByDataSetIndex(int xIndex, int dataSet) { DataSet set = mCurrentData.getDataSetByIndex(dataSet); 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 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 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 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 ChartData 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 ChartData 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); } /** * sets the number of digits that should be used for all printed values (if * this is set to -1, digits will be calculated automatically), default -1 * * @param digits */ public void setValueDigits(int digits) { mValueDigitsToUse = digits; } /** * returns the number of digits used to format the printed values of the * chart (-1 means digits are calculated automatically) * * @return */ public int getValueDigits() { return mValueDigitsToUse; } /** * sets the background color for the chart --> this also sets the color the * canvas is cleared with */ @Override public void setBackgroundColor(int color) { super.setBackgroundColor(color); mBackgroundColor = color; } /** * Saves the chart 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) { 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. */ mDrawBitmap.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); mDrawBitmap.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); 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); } /** flag indicating if the matrix has alerady been prepared */ private boolean mMatrixOnLayoutPrepared = false; @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); prepareContentRect(); Log.i(LOG_TAG, "onLayout(), width: " + mContentRect.width() + ", height: " + mContentRect.height()); if (this instanceof BarLineChartBase) { BarLineChartBase b = (BarLineChartBase) this; // if y-values are not fixed if (!b.hasFixedYValues() && !mMatrixOnLayoutPrepared) { prepareMatrix(); mMatrixOnLayoutPrepared = true; } } else { prepareMatrix(); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); } // @Override // protected void onAttachedToWindow() { // super.onAttachedToWindow(); // if (isInEditMode()) { // initWithDummyData(); // } // } }