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.formatter.IValueFormatter;
import com.github.mikephil.charting.highlight.Highlight;
import com.github.mikephil.charting.interfaces.datasets.IDataSet;
import java.util.ArrayList;
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 IDataSet<? extends Entry>> {
/**
* maximum y-value in the value array across all axes
*/
protected float mYMax = -Float.MAX_VALUE;
/**
* the minimum y-value in the value array across all axes
*/
protected float mYMin = Float.MAX_VALUE;
/**
* maximum x-value in the value array
*/
protected float mXMax = -Float.MAX_VALUE;
/**
* minimum x-value in the value array
*/
protected float mXMin = Float.MAX_VALUE;
protected float mLeftAxisMax = -Float.MAX_VALUE;
protected float mLeftAxisMin = Float.MAX_VALUE;
protected float mRightAxisMax = -Float.MAX_VALUE;
protected float mRightAxisMin = Float.MAX_VALUE;
/**
* array that holds all DataSets the ChartData object represents
*/
protected List<T> mDataSets;
/**
* Default constructor.
*/
public ChartData() {
mDataSets = new ArrayList<T>();
}
/**
* Constructor taking single or multiple DataSet objects.
*
* @param dataSets
*/
public ChartData(T... dataSets) {
mDataSets = arrayToList(dataSets);
notifyDataChanged();
}
/**
* Created because Arrays.asList(...) does not support modification.
*
* @param array
* @return
*/
private List<T> arrayToList(T[] array) {
List<T> list = new ArrayList<>();
for (T set : array) {
list.add(set);
}
return list;
}
/**
* constructor for chart data
*
* @param sets the dataset array
*/
public ChartData(List<T> sets) {
this.mDataSets = sets;
notifyDataChanged();
}
/**
* Call this method to let the ChartData know that the underlying data has
* changed. Calling this performs all necessary recalculations needed when
* the contained data has changed.
*/
public void notifyDataChanged() {
calcMinMax();
}
/**
* Calc minimum and maximum y-values over all DataSets.
* Tell DataSets to recalculate their min and max y-values, this is only needed for autoScaleMinMax.
*
* @param fromX the x-value to start the calculation from
* @param toX the x-value to which the calculation should be performed
*/
public void calcMinMaxY(float fromX, float toX) {
for (T set : mDataSets) {
set.calcMinMaxY(fromX, toX);
}
// apply the new data
calcMinMax();
}
/**
* Calc minimum and maximum values (both x and y) over all DataSets.
*/
protected void calcMinMax() {
if (mDataSets == null)
return;
mYMax = -Float.MAX_VALUE;
mYMin = Float.MAX_VALUE;
mXMax = -Float.MAX_VALUE;
mXMin = Float.MAX_VALUE;
for (T set : mDataSets) {
calcMinMax(set);
}
mLeftAxisMax = -Float.MAX_VALUE;
mLeftAxisMin = Float.MAX_VALUE;
mRightAxisMax = -Float.MAX_VALUE;
mRightAxisMin = Float.MAX_VALUE;
// left axis
T firstLeft = getFirstLeft(mDataSets);
if (firstLeft != null) {
mLeftAxisMax = firstLeft.getYMax();
mLeftAxisMin = firstLeft.getYMin();
for (T dataSet : mDataSets) {
if (dataSet.getAxisDependency() == AxisDependency.LEFT) {
if (dataSet.getYMin() < mLeftAxisMin)
mLeftAxisMin = dataSet.getYMin();
if (dataSet.getYMax() > mLeftAxisMax)
mLeftAxisMax = dataSet.getYMax();
}
}
}
// right axis
T firstRight = getFirstRight(mDataSets);
if (firstRight != null) {
mRightAxisMax = firstRight.getYMax();
mRightAxisMin = firstRight.getYMin();
for (T dataSet : mDataSets) {
if (dataSet.getAxisDependency() == AxisDependency.RIGHT) {
if (dataSet.getYMin() < mRightAxisMin)
mRightAxisMin = dataSet.getYMin();
if (dataSet.getYMax() > mRightAxisMax)
mRightAxisMax = dataSet.getYMax();
}
}
}
}
/** 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) {
if (mLeftAxisMin == Float.MAX_VALUE) {
return mRightAxisMin;
} else
return mLeftAxisMin;
} else {
if (mRightAxisMin == Float.MAX_VALUE) {
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) {
if (mLeftAxisMax == -Float.MAX_VALUE) {
return mRightAxisMax;
} else
return mLeftAxisMax;
} else {
if (mRightAxisMax == -Float.MAX_VALUE) {
return mLeftAxisMax;
} else
return mRightAxisMax;
}
}
/**
* Returns the minimum x-value this data object contains.
*
* @return
*/
public float getXMin() {
return mXMin;
}
/**
* Returns the maximum x-value this data object contains.
*
* @return
*/
public float getXMax() {
return mXMax;
}
/**
* Returns all DataSet objects this ChartData 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 label
* @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 labels of all DataSets as a string array.
*
* @return
*/
public 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) {
if (highlight.getDataSetIndex() >= mDataSets.size())
return null;
else {
return mDataSets.get(highlight.getDataSetIndex()).getEntryForXValue(highlight.getX(), highlight.getY());
}
}
/**
* 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);
}
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;
calcMinMax(d);
mDataSets.add(d);
}
/**
* 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) {
calcMinMax();
}
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 e
* @param dataSetIndex
*/
public void addEntry(Entry e, int dataSetIndex) {
if (mDataSets.size() > dataSetIndex && dataSetIndex >= 0) {
IDataSet set = mDataSets.get(dataSetIndex);
// add the entry to the dataset
if (!set.addEntry(e))
return;
calcMinMax(e, set.getAxisDependency());
} else {
Log.e("addEntry", "Cannot add Entry because dataSetIndex too high or too low.");
}
}
/**
* Adjusts the current minimum and maximum values based on the provided Entry object.
*
* @param e
* @param axis
*/
protected void calcMinMax(Entry e, AxisDependency axis) {
if (mYMax < e.getY())
mYMax = e.getY();
if (mYMin > e.getY())
mYMin = e.getY();
if (mXMax < e.getX())
mXMax = e.getX();
if (mXMin > e.getX())
mXMin = e.getX();
if (axis == AxisDependency.LEFT) {
if (mLeftAxisMax < e.getY())
mLeftAxisMax = e.getY();
if (mLeftAxisMin > e.getY())
mLeftAxisMin = e.getY();
} else {
if (mRightAxisMax < e.getY())
mRightAxisMax = e.getY();
if (mRightAxisMin > e.getY())
mRightAxisMin = e.getY();
}
}
/**
* Adjusts the minimum and maximum values based on the given DataSet.
*
* @param d
*/
protected void calcMinMax(T d) {
if (mYMax < d.getYMax())
mYMax = d.getYMax();
if (mYMin > d.getYMin())
mYMin = d.getYMin();
if (mXMax < d.getXMax())
mXMax = d.getXMax();
if (mXMin > d.getXMin())
mXMin = d.getXMin();
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();
}
}
/**
* 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;
IDataSet set = mDataSets.get(dataSetIndex);
if (set != null) {
// remove the entry from the dataset
boolean removed = set.removeEntry(e);
if (removed) {
calcMinMax();
}
return removed;
} else
return false;
}
/**
* Removes the Entry object closest to the given DataSet at the
* specified index. Returns true if an Entry was removed, false if no Entry
* was found that meets the specified requirements.
*
* @param xValue
* @param dataSetIndex
* @return
*/
public boolean removeEntry(float xValue, int dataSetIndex) {
if (dataSetIndex >= mDataSets.size())
return false;
IDataSet dataSet = mDataSets.get(dataSetIndex);
Entry e = dataSet.getEntryForXValue(xValue, Float.NaN);
if (e == null)
return false;
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.getEntryForXValue(e.getX(), e.getY())))
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 in the DataSet array of this data object, or -1 if it does not exist.
*
* @param dataSet
* @return
*/
public int getIndexOfDataSet(T dataSet) {
return mDataSets.indexOf(dataSet);
}
/**
* Returns the first DataSet from the datasets-array that has it's dependency on the left axis.
* Returns null if no DataSet with left dependency could be found.
*
* @return
*/
protected T getFirstLeft(List<T> sets) {
for (T dataSet : sets) {
if (dataSet.getAxisDependency() == AxisDependency.LEFT)
return dataSet;
}
return null;
}
/**
* Returns the first DataSet from the datasets-array that has it's dependency on the right axis.
* Returns null if no DataSet with right dependency could be found.
*
* @return
*/
public T getFirstRight(List<T> sets) {
for (T dataSet : sets) {
if (dataSet.getAxisDependency() == AxisDependency.RIGHT)
return dataSet;
}
return null;
}
/**
* Sets a custom IValueFormatter for all DataSets this data object contains.
*
* @param f
*/
public void setValueFormatter(IValueFormatter f) {
if (f == null)
return;
else {
for (IDataSet 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 (IDataSet set : mDataSets) {
set.setValueTextColor(color);
}
}
/**
* Sets the same list of value-colors for all DataSets this
* data object contains.
*
* @param colors
*/
public void setValueTextColors(List<Integer> colors) {
for (IDataSet set : mDataSets) {
set.setValueTextColors(colors);
}
}
/**
* Sets the Typeface for all value-labels for all DataSets this data object
* contains.
*
* @param tf
*/
public void setValueTypeface(Typeface tf) {
for (IDataSet set : mDataSets) {
set.setValueTypeface(tf);
}
}
/**
* Sets the size (in dp) of the value-text for all DataSets this data object
* contains.
*
* @param size
*/
public void setValueTextSize(float size) {
for (IDataSet 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 (IDataSet set : mDataSets) {
set.setDrawValues(enabled);
}
}
/**
* Enables / disables highlighting values for all DataSets this data object
* contains. If set to true, this means that values can
* be highlighted programmatically or by touch gesture.
*/
public void setHighlightEnabled(boolean enabled) {
for (IDataSet set : mDataSets) {
set.setHighlightEnabled(enabled);
}
}
/**
* Returns true if highlighting of all underlying values is enabled, false
* if not.
*
* @return
*/
public boolean isHighlightEnabled() {
for (IDataSet set : mDataSets) {
if (!set.isHighlightEnabled())
return false;
}
return true;
}
/**
* Clears this data object from all DataSets and removes all Entries. Don't
* forget to invalidate the chart after this.
*/
public void clearValues() {
if (mDataSets != null) {
mDataSets.clear();
}
notifyDataChanged();
}
/**
* 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;
}
/**
* Returns the total entry count across all DataSet objects this data object contains.
*
* @return
*/
public int getEntryCount() {
int count = 0;
for (T set : mDataSets) {
count += set.getEntryCount();
}
return count;
}
/**
* Returns the DataSet object with the maximum number of entries or null if there are no DataSets.
*
* @return
*/
public T getMaxEntryCountSet() {
if (mDataSets == null || mDataSets.isEmpty())
return null;
T max = mDataSets.get(0);
for (T set : mDataSets) {
if (set.getEntryCount() > max.getEntryCount())
max = set;
}
return max;
}
}