| |
| package com.github.mikephil.charting.data; |
| |
| import android.graphics.Typeface; |
| import android.util.Log; |
| |
| import com.github.mikephil.charting.components.YAxis.AxisDependency; |
| import com.github.mikephil.charting.utils.Highlight; |
| import com.github.mikephil.charting.utils.ValueFormatter; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| |
| /** |
| * Class that holds all relevant data that represents the chart. That involves |
| * at least one (or more) DataSets, and an array of x-values. |
| * |
| * @author Philipp Jahoda |
| */ |
| public abstract class ChartData<T extends DataSet<? extends Entry>> { |
| |
| /** maximum y-value in the y-value array across all axes */ |
| protected float mYMax = 0.0f; |
| |
| /** the minimum y-value in the y-value array across all axes */ |
| protected float mYMin = 0.0f; |
| |
| protected float mLeftAxisMax = 0.0f; |
| |
| protected float mLeftAxisMin = 0.0f; |
| |
| protected float mRightAxisMax = 0.0f; |
| |
| protected float mRightAxisMin = 0.0f; |
| |
| /** the total sum of all y-values */ |
| private float mYValueSum = 0f; |
| |
| /** total number of y-values across all DataSet objects */ |
| private int mYValCount = 0; |
| |
| /** |
| * contains the average length (in characters) an entry in the x-vals array |
| * has |
| */ |
| private float mXValAverageLength = 0; |
| |
| /** holds all x-values the chart represents */ |
| protected List<String> mXVals; |
| |
| /** array that holds all DataSets the ChartData object represents */ |
| protected List<T> mDataSets; |
| |
| public ChartData() { |
| mXVals = new ArrayList<String>(); |
| mDataSets = new ArrayList<T>(); |
| } |
| |
| /** |
| * Constructor for only x-values. This constructor can be used for setting |
| * up an empty chart without data. |
| * |
| * @param xVals |
| */ |
| public ChartData(List<String> xVals) { |
| this.mXVals = xVals; |
| this.mDataSets = new ArrayList<T>(); |
| init(mDataSets); |
| } |
| |
| /** |
| * Constructor for only x-values. This constructor can be used for setting |
| * up an empty chart without data. |
| * |
| * @param xVals |
| */ |
| public ChartData(String[] xVals) { |
| this.mXVals = arrayToList(xVals); |
| this.mDataSets = new ArrayList<T>(); |
| init(mDataSets); |
| } |
| |
| /** |
| * constructor for chart data |
| * |
| * @param xVals The values describing the x-axis. Must be at least as long |
| * as the highest xIndex in the Entry objects across all |
| * DataSets. |
| * @param sets the dataset array |
| */ |
| public ChartData(List<String> xVals, List<T> sets) { |
| this.mXVals = xVals; |
| this.mDataSets = sets; |
| |
| init(mDataSets); |
| } |
| |
| /** |
| * constructor that takes string array instead of List string |
| * |
| * @param xVals The values describing the x-axis. Must be at least as long |
| * as the highest xIndex in the Entry objects across all |
| * DataSets. |
| * @param sets the dataset array |
| */ |
| public ChartData(String[] xVals, List<T> sets) { |
| this.mXVals = arrayToList(xVals); |
| this.mDataSets = sets; |
| |
| init(mDataSets); |
| } |
| |
| /** |
| * Turns an array of strings into an List of strings. |
| * |
| * @param array |
| * @return |
| */ |
| private List<String> arrayToList(String[] array) { |
| return Arrays.asList(array); |
| } |
| |
| /** |
| * performs all kinds of initialization calculations, such as min-max and |
| * value count and sum |
| */ |
| protected void init(List<? extends DataSet<?>> dataSets) { |
| |
| isLegal(dataSets); |
| |
| calcMinMax(dataSets); |
| calcYValueSum(dataSets); |
| calcYValueCount(dataSets); |
| |
| calcXValAverageLength(); |
| } |
| |
| /** |
| * calculates the average length (in characters) across all x-value strings |
| */ |
| private void calcXValAverageLength() { |
| |
| if (mXVals.size() <= 0) { |
| mXValAverageLength = 1; |
| return; |
| } |
| |
| float sum = 1f; |
| |
| for (int i = 0; i < mXVals.size(); i++) { |
| sum += mXVals.get(i).length(); |
| } |
| |
| mXValAverageLength = sum / (float) mXVals.size(); |
| } |
| |
| /** |
| * Checks if the combination of x-values array and DataSet array is legal or |
| * not. |
| * |
| * @param dataSets |
| */ |
| private void isLegal(List<? extends DataSet<?>> dataSets) { |
| |
| if (dataSets == null) |
| return; |
| |
| for (int i = 0; i < dataSets.size(); i++) { |
| if (dataSets.get(i) |
| .getYVals() |
| .size() > mXVals.size()) { |
| throw new IllegalArgumentException( |
| "One or more of the DataSet Entry arrays are longer than the x-values array of this ChartData object."); |
| } |
| } |
| } |
| |
| /** |
| * Call this method to let the CartData know that the underlying data has |
| * changed. |
| */ |
| public void notifyDataChanged() { |
| init(mDataSets); |
| } |
| |
| /** |
| * calc minimum and maximum y value over all datasets |
| */ |
| protected void calcMinMax(List<? extends DataSet<?>> dataSets) { |
| |
| if (dataSets == null || dataSets.size() < 1) { |
| |
| mYMax = 0f; |
| mYMin = 0f; |
| } else { |
| |
| // calculate absolute min and max |
| mYMin = dataSets.get(0).getYMin(); |
| mYMax = dataSets.get(0).getYMax(); |
| |
| for (int i = 0; i < dataSets.size(); i++) { |
| if (dataSets.get(i).getYMin() < mYMin) |
| mYMin = dataSets.get(i).getYMin(); |
| |
| if (dataSets.get(i).getYMax() > mYMax) |
| mYMax = dataSets.get(i).getYMax(); |
| } |
| |
| // left axis |
| T firstLeft = getFirstLeft(); |
| |
| if (firstLeft != null) { |
| |
| mLeftAxisMax = firstLeft.getYMax(); |
| mLeftAxisMin = firstLeft.getYMin(); |
| |
| for (DataSet<?> dataSet : dataSets) { |
| if (dataSet.getAxisDependency() == AxisDependency.LEFT) { |
| if (dataSet.getYMin() < mLeftAxisMin) |
| mLeftAxisMin = dataSet.getYMin(); |
| |
| if (dataSet.getYMax() > mLeftAxisMax) |
| mLeftAxisMax = dataSet.getYMax(); |
| } |
| } |
| } |
| |
| // right axis |
| T firstRight = getFirstRight(); |
| |
| if (firstRight != null) { |
| |
| mRightAxisMax = firstRight.getYMax(); |
| mRightAxisMin = firstRight.getYMin(); |
| |
| for (DataSet<?> dataSet : dataSets) { |
| if (dataSet.getAxisDependency() == AxisDependency.RIGHT) { |
| if (dataSet.getYMin() < mRightAxisMin) |
| mRightAxisMin = dataSet.getYMin(); |
| |
| if (dataSet.getYMax() > mRightAxisMax) |
| mRightAxisMax = dataSet.getYMax(); |
| } |
| } |
| } |
| |
| // in case there is only one axis, adjust the second axis |
| handleEmptyAxis(firstLeft, firstRight); |
| } |
| } |
| |
| /** |
| * calculates the sum of all y-values in all datasets |
| */ |
| protected void calcYValueSum(List<? extends DataSet<?>> dataSets) { |
| |
| mYValueSum = 0; |
| |
| if (dataSets == null) |
| return; |
| |
| for (int i = 0; i < dataSets.size(); i++) { |
| mYValueSum += Math.abs(dataSets.get(i).getYValueSum()); |
| } |
| } |
| |
| /** |
| * Calculates the total number of y-values across all DataSets the ChartData |
| * represents. |
| * |
| * @return |
| */ |
| protected void calcYValueCount(List<? extends DataSet<?>> dataSets) { |
| |
| mYValCount = 0; |
| |
| if (dataSets == null) |
| return; |
| |
| int count = 0; |
| |
| for (int i = 0; i < dataSets.size(); i++) { |
| count += dataSets.get(i).getEntryCount(); |
| } |
| |
| mYValCount = count; |
| } |
| |
| /** ONLY GETTERS AND SETTERS BELOW THIS */ |
| |
| /** |
| * returns the number of LineDataSets this object contains |
| * |
| * @return |
| */ |
| public int getDataSetCount() { |
| if (mDataSets == null) |
| return 0; |
| return mDataSets.size(); |
| } |
| |
| /** |
| * Returns the smallest y-value the data object contains. |
| * |
| * @return |
| */ |
| public float getYMin() { |
| return mYMin; |
| } |
| |
| /** |
| * Returns the minimum y-value for the specified axis. |
| * |
| * @param axis |
| * @return |
| */ |
| public float getYMin(AxisDependency axis) { |
| if (axis == AxisDependency.LEFT) |
| return mLeftAxisMin; |
| else |
| return mRightAxisMin; |
| } |
| |
| /** |
| * Returns the greatest y-value the data object contains. |
| * |
| * @return |
| */ |
| public float getYMax() { |
| return mYMax; |
| } |
| |
| /** |
| * Returns the maximum y-value for the specified axis. |
| * |
| * @param axis |
| * @return |
| */ |
| public float getYMax(AxisDependency axis) { |
| if (axis == AxisDependency.LEFT) |
| return mLeftAxisMax; |
| else |
| return mRightAxisMax; |
| } |
| |
| /** |
| * returns the average length (in characters) across all values in the |
| * x-vals array |
| * |
| * @return |
| */ |
| public float getXValAverageLength() { |
| return mXValAverageLength; |
| } |
| |
| /** |
| * Returns the total y-value sum across all DataSet objects the this object |
| * represents. |
| * |
| * @return |
| */ |
| public float getYValueSum() { |
| return mYValueSum; |
| } |
| |
| /** |
| * Returns the total number of y-values across all DataSet objects the this |
| * object represents. |
| * |
| * @return |
| */ |
| public int getYValCount() { |
| return mYValCount; |
| } |
| |
| /** |
| * returns the x-values the chart represents |
| * |
| * @return |
| */ |
| public List<String> getXVals() { |
| return mXVals; |
| } |
| |
| /** |
| * Adds a new x-value to the chart data. |
| * |
| * @param xVal |
| */ |
| public void addXValue(String xVal) { |
| |
| mXValAverageLength = (mXValAverageLength + xVal.length()) / 2f; |
| mXVals.add(xVal); |
| } |
| |
| /** |
| * Removes the x-value at the specified index. |
| * |
| * @param index |
| */ |
| public void removeXValue(int index) { |
| mXVals.remove(index); |
| } |
| |
| /** |
| * Returns an the array of DataSets this object holds. |
| * |
| * @return |
| */ |
| public List<T> getDataSets() { |
| return mDataSets; |
| } |
| |
| /** |
| * Retrieve the index of a DataSet with a specific label from the ChartData. |
| * Search can be case sensitive or not. IMPORTANT: This method does |
| * calculations at runtime, do not over-use in performance critical |
| * situations. |
| * |
| * @param dataSets the DataSet array to search |
| * @param type |
| * @param ignorecase if true, the search is not case-sensitive |
| * @return |
| */ |
| protected int getDataSetIndexByLabel(List<T> dataSets, String label, |
| boolean ignorecase) { |
| |
| if (ignorecase) { |
| for (int i = 0; i < dataSets.size(); i++) |
| if (label.equalsIgnoreCase(dataSets.get(i).getLabel())) |
| return i; |
| } else { |
| for (int i = 0; i < dataSets.size(); i++) |
| if (label.equals(dataSets.get(i).getLabel())) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| /** |
| * returns the total number of x-values this ChartData object represents |
| * (the size of the x-values array) |
| * |
| * @return |
| */ |
| public int getXValCount() { |
| return mXVals.size(); |
| } |
| |
| /** |
| * Returns the labels of all DataSets as a string array. |
| * |
| * @return |
| */ |
| protected String[] getDataSetLabels() { |
| |
| String[] types = new String[mDataSets.size()]; |
| |
| for (int i = 0; i < mDataSets.size(); i++) { |
| types[i] = mDataSets.get(i).getLabel(); |
| } |
| |
| return types; |
| } |
| |
| /** |
| * Get the Entry for a corresponding highlight object |
| * |
| * @param highlight |
| * @return the entry that is highlighted |
| */ |
| public Entry getEntryForHighlight(Highlight highlight) { |
| return mDataSets.get(highlight.getDataSetIndex()).getEntryForXIndex( |
| highlight.getXIndex()); |
| } |
| |
| /** |
| * Returns the DataSet object with the given label. Search can be case |
| * sensitive or not. IMPORTANT: This method does calculations at runtime. |
| * Use with care in performance critical situations. |
| * |
| * @param label |
| * @param ignorecase |
| * @return |
| */ |
| public T getDataSetByLabel(String label, boolean ignorecase) { |
| |
| int index = getDataSetIndexByLabel(mDataSets, label, ignorecase); |
| |
| if (index < 0 || index >= mDataSets.size()) |
| return null; |
| else |
| return mDataSets.get(index); |
| } |
| |
| /** |
| * Returns the DataSet object at the given index. |
| * |
| * @param index |
| * @return |
| */ |
| public T getDataSetByIndex(int index) { |
| |
| if (mDataSets == null || index < 0 || index >= mDataSets.size()) |
| return null; |
| |
| return mDataSets.get(index); |
| } |
| |
| /** |
| * Adds a DataSet dynamically. |
| * |
| * @param d |
| */ |
| public void addDataSet(T d) { |
| |
| if (d == null) |
| return; |
| |
| mYValCount += d.getEntryCount(); |
| mYValueSum += d.getYValueSum(); |
| |
| if (mDataSets.size() <= 0) { |
| |
| mYMax = d.getYMax(); |
| mYMin = d.getYMin(); |
| |
| if (d.getAxisDependency() == AxisDependency.LEFT) { |
| |
| mLeftAxisMax = d.getYMax(); |
| mLeftAxisMin = d.getYMin(); |
| } else { |
| mRightAxisMax = d.getYMax(); |
| mRightAxisMin = d.getYMin(); |
| } |
| } else { |
| |
| if (mYMax < d.getYMax()) |
| mYMax = d.getYMax(); |
| if (mYMin > d.getYMin()) |
| mYMin = d.getYMin(); |
| |
| if (d.getAxisDependency() == AxisDependency.LEFT) { |
| |
| if (mLeftAxisMax < d.getYMax()) |
| mLeftAxisMax = d.getYMax(); |
| if (mLeftAxisMin > d.getYMin()) |
| mLeftAxisMin = d.getYMin(); |
| } else { |
| if (mRightAxisMax < d.getYMax()) |
| mRightAxisMax = d.getYMax(); |
| if (mRightAxisMin > d.getYMin()) |
| mRightAxisMin = d.getYMin(); |
| } |
| } |
| |
| mDataSets.add(d); |
| |
| handleEmptyAxis(getFirstLeft(), getFirstRight()); |
| } |
| |
| /** |
| * This adjusts the other axis if one axis is empty and the other is not. |
| * |
| * @param firstLeft |
| * @param firstRight |
| */ |
| private void handleEmptyAxis(T firstLeft, T firstRight) { |
| |
| // in case there is only one axis, adjust the second axis |
| if (firstLeft == null) { |
| mLeftAxisMax = mRightAxisMax; |
| mLeftAxisMin = mRightAxisMin; |
| } else if (firstRight == null) { |
| mRightAxisMax = mLeftAxisMax; |
| mRightAxisMin = mLeftAxisMin; |
| } |
| } |
| |
| /** |
| * Removes the given DataSet from this data object. Also recalculates all |
| * minimum and maximum values. Returns true if a DataSet was removed, false |
| * if no DataSet could be removed. |
| * |
| * @param d |
| */ |
| public boolean removeDataSet(T d) { |
| |
| if (d == null) |
| return false; |
| |
| boolean removed = mDataSets.remove(d); |
| |
| // if a DataSet was removed |
| if (removed) { |
| |
| mYValCount -= d.getEntryCount(); |
| mYValueSum -= d.getYValueSum(); |
| |
| calcMinMax(mDataSets); |
| } |
| |
| return removed; |
| } |
| |
| /** |
| * Removes the DataSet at the given index in the DataSet array from the data |
| * object. Also recalculates all minimum and maximum values. Returns true if |
| * a DataSet was removed, false if no DataSet could be removed. |
| * |
| * @param index |
| */ |
| public boolean removeDataSet(int index) { |
| |
| if (index >= mDataSets.size() || index < 0) |
| return false; |
| |
| T set = mDataSets.get(index); |
| return removeDataSet(set); |
| } |
| |
| /** |
| * Adds an Entry to the DataSet at the specified index. Entries are added to |
| * the end of the list. |
| * |
| * @param entry |
| * @param dataSetIndex |
| */ |
| public void addEntry(Entry e, int dataSetIndex) { |
| |
| if (mDataSets.size() > dataSetIndex && dataSetIndex >= 0) { |
| |
| float val = e.getVal(); |
| |
| mYValCount += 1; |
| mYValueSum += val; |
| |
| if (mYMax < val) |
| mYMax = val; |
| if (mYMin > val) |
| mYMin = val; |
| |
| T set = mDataSets.get(dataSetIndex); |
| |
| if (set != null) { |
| |
| if (set.getAxisDependency() == AxisDependency.LEFT) { |
| |
| if (mLeftAxisMax < e.getVal()) |
| mLeftAxisMax = e.getVal(); |
| if (mLeftAxisMin > e.getVal()) |
| mLeftAxisMin = e.getVal(); |
| } else { |
| if (mRightAxisMax < e.getVal()) |
| mRightAxisMax = e.getVal(); |
| if (mRightAxisMin > e.getVal()) |
| mRightAxisMin = e.getVal(); |
| } |
| |
| handleEmptyAxis(getFirstLeft(), getFirstRight()); |
| |
| // add the entry to the dataset |
| set.addEntry(e); |
| } |
| } else { |
| Log.e("addEntry", "Cannot add Entry because dataSetIndex too high or too low."); |
| } |
| } |
| |
| /** |
| * Removes the given Entry object from the DataSet at the specified index. |
| * |
| * @param e |
| * @param dataSetIndex |
| */ |
| public boolean removeEntry(Entry e, int dataSetIndex) { |
| |
| // entry null, outofbounds |
| if (e == null || dataSetIndex >= mDataSets.size()) |
| return false; |
| |
| // remove the entry from the dataset |
| boolean removed = mDataSets.get(dataSetIndex).removeEntry(e.getXIndex()); |
| |
| if (removed) { |
| |
| float val = e.getVal(); |
| |
| mYValCount -= 1; |
| mYValueSum -= val; |
| |
| calcMinMax(mDataSets); |
| } |
| |
| return removed; |
| } |
| |
| /** |
| * Removes the Entry object at the given xIndex from the DataSet at the |
| * specified index. Returns true if an Entry was removed, false if no Entry |
| * was found that meets the specified requirements. |
| * |
| * @param xIndex |
| * @param dataSetIndex |
| * @return |
| */ |
| public boolean removeEntry(int xIndex, int dataSetIndex) { |
| |
| if (dataSetIndex >= mDataSets.size()) |
| return false; |
| |
| T dataSet = mDataSets.get(dataSetIndex); |
| Entry e = dataSet.getEntryForXIndex(xIndex); |
| |
| return removeEntry(e, dataSetIndex); |
| } |
| |
| /** |
| * Returns the DataSet that contains the provided Entry, or null, if no |
| * DataSet contains this Entry. |
| * |
| * @param e |
| * @return |
| */ |
| public T getDataSetForEntry(Entry e) { |
| |
| if (e == null) |
| return null; |
| |
| for (int i = 0; i < mDataSets.size(); i++) { |
| |
| T set = mDataSets.get(i); |
| |
| for (int j = 0; j < set.getEntryCount(); j++) { |
| if (e.equalTo(set.getEntryForXIndex(e.getXIndex()))) |
| return set; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Returns all colors used across all DataSet objects this object |
| * represents. |
| * |
| * @return |
| */ |
| public int[] getColors() { |
| |
| if (mDataSets == null) |
| return null; |
| |
| int clrcnt = 0; |
| |
| for (int i = 0; i < mDataSets.size(); i++) { |
| clrcnt += mDataSets.get(i).getColors().size(); |
| } |
| |
| int[] colors = new int[clrcnt]; |
| int cnt = 0; |
| |
| for (int i = 0; i < mDataSets.size(); i++) { |
| |
| List<Integer> clrs = mDataSets.get(i).getColors(); |
| |
| for (Integer clr : clrs) { |
| colors[cnt] = clr; |
| cnt++; |
| } |
| } |
| |
| return colors; |
| } |
| |
| /** |
| * Returns the index of the provided DataSet inside the DataSets array of |
| * this data object. Returns -1 if the DataSet was not found. |
| * |
| * @param dataSet |
| * @return |
| */ |
| public int getIndexOfDataSet(T dataSet) { |
| |
| for (int i = 0; i < mDataSets.size(); i++) { |
| if (mDataSets.get(i) == dataSet) |
| return i; |
| } |
| |
| return -1; |
| } |
| |
| public T getFirstLeft() { |
| for (T dataSet : mDataSets) { |
| if (dataSet.getAxisDependency() == AxisDependency.LEFT) |
| return dataSet; |
| } |
| |
| return null; |
| } |
| |
| public T getFirstRight() { |
| for (T dataSet : mDataSets) { |
| if (dataSet.getAxisDependency() == AxisDependency.RIGHT) |
| return dataSet; |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Generates an x-values array filled with numbers in range specified by the |
| * parameters. Can be used for convenience. |
| * |
| * @return |
| */ |
| public static List<String> generateXVals(int from, int to) { |
| |
| List<String> xvals = new ArrayList<String>(); |
| |
| for (int i = from; i < to; i++) { |
| xvals.add("" + i); |
| } |
| |
| return xvals; |
| } |
| |
| /** |
| * Sets a custom ValueFormatter for all DataSets this data object contains. |
| * |
| * @param f |
| */ |
| public void setValueFormatter(ValueFormatter f) { |
| if (f == null) |
| return; |
| else { |
| for (DataSet<?> set : mDataSets) { |
| set.setValueFormatter(f); |
| } |
| } |
| } |
| |
| /** |
| * Sets the color of the value-text (color in which the value-labels are |
| * drawn) for all DataSets this data object contains. |
| * |
| * @param color |
| */ |
| public void setValueTextColor(int color) { |
| for (DataSet<?> set : mDataSets) { |
| set.setValueTextColor(color); |
| } |
| } |
| |
| /** |
| * Sets the Typeface for all value-labels for all DataSets this data object |
| * contains. |
| * |
| * @param color |
| */ |
| public void setValueTypeface(Typeface tf) { |
| for (DataSet<?> set : mDataSets) { |
| set.setValueTypeface(tf); |
| } |
| } |
| |
| /** |
| * Sets the size (in dp) of the value-text for all DataSets this data object |
| * contains. |
| * |
| * @param color |
| */ |
| public void setValueTextSize(float size) { |
| for (DataSet<?> set : mDataSets) { |
| set.setValueTextSize(size); |
| } |
| } |
| |
| /** |
| * Enables / disables drawing values (value-text) for all DataSets this data |
| * object contains. |
| * |
| * @param enabled |
| */ |
| public void setDrawValues(boolean enabled) { |
| for (DataSet<?> set : mDataSets) { |
| set.setDrawValues(enabled); |
| } |
| } |
| |
| /** |
| * Clears this data object from all DataSets and removes all Entries. |
| */ |
| public void clearValues() { |
| mDataSets.clear(); |
| notifyDataChanged(); |
| } |
| |
| /** |
| * Checks if this data object contains the specified Entry. Returns true if |
| * so, false if not. NOTE: Performance is pretty bad on this one, do not |
| * over-use in performance critical situations. |
| * |
| * @param e |
| * @return |
| */ |
| public boolean contains(Entry e) { |
| |
| for (T set : mDataSets) { |
| if (set.contains(e)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Checks if this data object contains the specified DataSet. Returns true |
| * if so, false if not. |
| * |
| * @param dataSet |
| * @return |
| */ |
| public boolean contains(T dataSet) { |
| |
| for (T set : mDataSets) { |
| if (set.equals(dataSet)) |
| return true; |
| } |
| |
| return false; |
| } |
| } |