package com.github.mikephil.charting.data;
import java.util.ArrayList;
import java.util.List;
/**
* The DataSet class represents one group or type of entries (Entry) in the
* Chart that belong together. It is designed to logically separate different
* groups of values inside the Chart (e.g. the values for a specific line in the
* LineChart, or the values of a specific group of bars in the BarChart).
*
* @author Philipp Jahoda
*/
public abstract class DataSet<T extends Entry> extends BaseDataSet<T> {
/**
* the entries that this DataSet represents / holds together
*/
protected List<T> mValues = null;
/**
* maximum y-value in the value array
*/
protected float mYMax = -Float.MAX_VALUE;
/**
* minimum y-value in the value array
*/
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;
/**
* Creates a new DataSet object with the given values (entries) it represents. Also, a
* label that describes the DataSet can be specified. The label can also be
* used to retrieve the DataSet from a ChartData object.
*
* @param values
* @param label
*/
public DataSet(List<T> values, String label) {
super(label);
this.mValues = values;
if (mValues == null)
mValues = new ArrayList<T>();
calcMinMax();
}
@Override
public void calcMinMax() {
if (mValues == null || mValues.isEmpty())
return;
mYMax = -Float.MAX_VALUE;
mYMin = Float.MAX_VALUE;
mXMax = -Float.MAX_VALUE;
mXMin = Float.MAX_VALUE;
for (T e : mValues) {
calcMinMax(e);
}
}
@Override
public void calcMinMaxY(float fromX, float toX) {
if (mValues == null || mValues.isEmpty())
return;
mYMax = -Float.MAX_VALUE;
mYMin = Float.MAX_VALUE;
int indexFrom = getEntryIndex(fromX, Float.NaN, Rounding.DOWN);
int indexTo = getEntryIndex(toX, Float.NaN, Rounding.UP);
for (int i = indexFrom; i <= indexTo; i++) {
// only recalculate y
calcMinMaxY(mValues.get(i));
}
}
/**
* Updates the min and max x and y value of this DataSet based on the given Entry.
*
* @param e
*/
protected void calcMinMax(T e) {
if (e == null)
return;
calcMinMaxX(e);
calcMinMaxY(e);
}
protected void calcMinMaxX(T e) {
if (e.getX() < mXMin)
mXMin = e.getX();
if (e.getX() > mXMax)
mXMax = e.getX();
}
protected void calcMinMaxY(T e) {
if (e.getY() < mYMin)
mYMin = e.getY();
if (e.getY() > mYMax)
mYMax = e.getY();
}
@Override
public int getEntryCount() {
return mValues.size();
}
/**
* Returns the array of entries that this DataSet represents.
*
* @return
*/
public List<T> getValues() {
return mValues;
}
/**
* Sets the array of entries that this DataSet represents, and calls notifyDataSetChanged()
*
* @return
*/
public void setValues(List<T> values) {
mValues = values;
notifyDataSetChanged();
}
/**
* Provides an exact copy of the DataSet this method is used on.
*
* @return
*/
public abstract DataSet<T> copy();
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(toSimpleString());
for (int i = 0; i < mValues.size(); i++) {
buffer.append(mValues.get(i).toString() + " ");
}
return buffer.toString();
}
/**
* Returns a simple string representation of the DataSet with the type and
* the number of Entries.
*
* @return
*/
public String toSimpleString() {
StringBuffer buffer = new StringBuffer();
buffer.append("DataSet, label: " + (getLabel() == null ? "" : getLabel()) + ", entries: " + mValues.size() +
"\n");
return buffer.toString();
}
@Override
public float getYMin() {
return mYMin;
}
@Override
public float getYMax() {
return mYMax;
}
@Override
public float getXMin() {
return mXMin;
}
@Override
public float getXMax() {
return mXMax;
}
@Override
public void addEntryOrdered(T e) {
if (e == null)
return;
if (mValues == null) {
mValues = new ArrayList<T>();
}
calcMinMax(e);
if (mValues.size() > 0 && mValues.get(mValues.size() - 1).getX() > e.getX()) {
int closestIndex = getEntryIndex(e.getX(), e.getY(), Rounding.UP);
mValues.add(closestIndex, e);
} else {
mValues.add(e);
}
}
@Override
public void clear() {
mValues.clear();
notifyDataSetChanged();
}
@Override
public boolean addEntry(T e) {
if (e == null)
return false;
List<T> values = getValues();
if (values == null) {
values = new ArrayList<T>();
}
calcMinMax(e);
// add the entry
return values.add(e);
}
@Override
public boolean removeEntry(T e) {
if (e == null)
return false;
if (mValues == null)
return false;
// remove the entry
boolean removed = mValues.remove(e);
if (removed) {
calcMinMax();
}
return removed;
}
@Override
public int getEntryIndex(Entry e) {
return mValues.indexOf(e);
}
@Override
public T getEntryForXValue(float xValue, float closestToY, Rounding rounding) {
int index = getEntryIndex(xValue, closestToY, rounding);
if (index > -1)
return mValues.get(index);
return null;
}
@Override
public T getEntryForXValue(float xValue, float closestToY) {
return getEntryForXValue(xValue, closestToY, Rounding.CLOSEST);
}
@Override
public T getEntryForIndex(int index) {
return mValues.get(index);
}
@Override
public int getEntryIndex(float xValue, float closestToY, Rounding rounding) {
if (mValues == null || mValues.isEmpty())
return -1;
int low = 0;
int high = mValues.size() - 1;
int closest = high;
while (low < high) {
int m = (low + high) / 2;
final float d1 = mValues.get(m).getX() - xValue,
d2 = mValues.get(m + 1).getX() - xValue,
ad1 = Math.abs(d1), ad2 = Math.abs(d2);
if (ad2 < ad1) {
// [m + 1] is closer to xValue
// Search in an higher place
low = m + 1;
} else if (ad1 < ad2) {
// [m] is closer to xValue
// Search in a lower place
high = m;
} else {
// We have multiple sequential x-value with same distance
if (d1 >= 0.0) {
// Search in a lower place
high = m;
} else if (d1 < 0.0) {
// Search in an higher place
low = m + 1;
}
}
closest = high;
}
if (closest != -1) {
float closestXValue = mValues.get(closest).getX();
if (rounding == Rounding.UP) {
// If rounding up, and found x-value is lower than specified x, and we can go upper...
if (closestXValue < xValue && closest < mValues.size() - 1) {
++closest;
}
} else if (rounding == Rounding.DOWN) {
// If rounding down, and found x-value is upper than specified x, and we can go lower...
if (closestXValue > xValue && closest > 0) {
--closest;
}
}
// Search by closest to y-value
if (!Float.isNaN(closestToY)) {
while (closest > 0 && mValues.get(closest - 1).getX() == closestXValue)
closest -= 1;
float closestYValue = mValues.get(closest).getY();
int closestYIndex = closest;
while (true) {
closest += 1;
if (closest >= mValues.size())
break;
final Entry value = mValues.get(closest);
if (value.getX() != closestXValue)
break;
if (Math.abs(value.getY() - closestToY) < Math.abs(closestYValue - closestToY)) {
closestYValue = closestToY;
closestYIndex = closest;
}
}
closest = closestYIndex;
}
}
return closest;
}
@Override
public List<T> getEntriesForXValue(float xValue) {
List<T> entries = new ArrayList<T>();
int low = 0;
int high = mValues.size() - 1;
while (low <= high) {
int m = (high + low) / 2;
T entry = mValues.get(m);
// if we have a match
if (xValue == entry.getX()) {
while (m > 0 && mValues.get(m - 1).getX() == xValue)
m--;
high = mValues.size();
// loop over all "equal" entries
for (; m < high; m++) {
entry = mValues.get(m);
if (entry.getX() == xValue) {
entries.add(entry);
} else {
break;
}
}
break;
} else {
if (xValue > entry.getX())
low = m + 1;
else
high = m - 1;
}
}
return entries;
}
/**
* Determines how to round DataSet index values for
* {@link DataSet#getEntryIndex(float, float, Rounding)} DataSet.getEntryIndex()}
* when an exact x-index is not found.
*/
public enum Rounding {
UP,
DOWN,
CLOSEST,
}
}