From ce7b52c604a2a3e4962e9c3f0ff339a797fc69bd Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 21 Dec 2025 17:17:42 +0100 Subject: [PATCH 1/2] Kotlin Chartdata --- .../github/mikephil/charting/data/BarData.kt | 33 +- .../data/BarLineScatterCandleBubbleData.kt | 12 +- .../mikephil/charting/data/BubbleData.kt | 6 +- .../mikephil/charting/data/ChartData.java | 776 ------------------ .../mikephil/charting/data/ChartData.kt | 720 ++++++++++++++++ .../mikephil/charting/data/CombinedData.java | 34 +- .../github/mikephil/charting/data/PieData.kt | 44 +- .../mikephil/charting/data/ScatterData.kt | 8 +- .../charting/highlight/BarHighlighter.kt | 18 +- .../charting/highlight/ChartHighlighter.kt | 14 +- .../charting/highlight/CombinedHighlighter.kt | 29 +- .../highlight/HorizontalBarHighlighter.kt | 2 +- .../charting/highlight/RadarHighlighter.kt | 7 +- .../dataprovider/base/IBaseProvider.java | 2 +- .../charting/renderer/BarChartRenderer.kt | 14 +- .../charting/renderer/BubbleChartRenderer.kt | 114 +-- .../renderer/CandleStickChartRenderer.kt | 108 +-- .../charting/renderer/DataRenderer.kt | 4 +- .../renderer/HorizontalBarChartRenderer.kt | 16 +- .../charting/renderer/LegendRenderer.kt | 2 +- .../charting/renderer/LineChartRenderer.kt | 182 ++-- .../charting/renderer/PieChartRenderer.kt | 284 +++---- .../charting/renderer/RadarChartRenderer.kt | 110 +-- .../charting/renderer/ScatterChartRenderer.kt | 4 +- .../renderer/XAxisRendererRadarChart.kt | 23 +- .../renderer/YAxisRendererRadarChart.kt | 15 +- 26 files changed, 1281 insertions(+), 1300 deletions(-) delete mode 100644 MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java create mode 100644 MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.kt diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.kt index 15967fe10..af0efad06 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarData.kt @@ -33,13 +33,12 @@ class BarData : BarLineScatterCandleBubbleData { */ fun groupBars(fromX: Float, groupSpace: Float, barSpace: Float) { var fromX = fromX - val setCount = mDataSets.size + val setCount = dataSets?.size ?: 0 if (setCount <= 1) { throw RuntimeException("BarData needs to hold at least 2 BarDataSets to allow grouping.") } - val max = getMaxEntryCountSet() - val maxEntryCount = max.entryCount + val maxEntryCount = maxEntryCountSet?.entryCount ?: 0 val groupSpaceWidthHalf = groupSpace / 2f val barSpaceHalf = barSpace / 2f @@ -51,22 +50,23 @@ class BarData : BarLineScatterCandleBubbleData { val start = fromX fromX += groupSpaceWidthHalf - for (set in mDataSets) { - fromX += barSpaceHalf - fromX += barWidthHalf + dataSets?.let { + for (set in dataSets) { + fromX += barSpaceHalf + fromX += barWidthHalf - if (i < set.entryCount) { - val entry = set.getEntryForIndex(i) + if (i < set.entryCount) { + val entry = set.getEntryForIndex(i) - if (entry != null) { - entry.x = fromX + if (entry != null) { + entry.x = fromX + } } - } - fromX += barWidthHalf - fromX += barSpaceHalf + fromX += barWidthHalf + fromX += barSpaceHalf + } } - fromX += groupSpaceWidthHalf val end = fromX val innerInterval = end - start @@ -85,6 +85,9 @@ class BarData : BarLineScatterCandleBubbleData { * In case of grouped bars, this method returns the space an individual group of bar needs on the x-axis. */ fun getGroupWidth(groupSpace: Float, barSpace: Float): Float { - return mDataSets.size * (this.barWidth + barSpace) + groupSpace + return if (dataSets == null) + 0f + else + dataSets!!.size * (this.barWidth + barSpace) + groupSpace } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.kt index e3de277d7..20c359923 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BarLineScatterCandleBubbleData.kt @@ -1,14 +1,20 @@ package com.github.mikephil.charting.data import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet +import com.github.mikephil.charting.interfaces.datasets.IDataSet /** * Baseclass for all Line, Bar, Scatter, Candle and Bubble data. */ -abstract class BarLineScatterCandleBubbleData> : ChartData { +abstract class BarLineScatterCandleBubbleData : ChartData<@UnsafeVariance T> + where T : IDataSet, + T : IBarLineScatterCandleBubbleDataSet { + constructor() : super() - constructor(vararg sets: T) : super(*sets) + @Suppress("UNCHECKED_CAST") + constructor(vararg sets: @UnsafeVariance T) : super(*sets) - constructor(sets: MutableList) : super(sets) + @Suppress("UNCHECKED_CAST") + constructor(sets: MutableList<@UnsafeVariance T>) : super(sets) } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.kt index 4b403c307..e43b6197b 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/BubbleData.kt @@ -15,8 +15,10 @@ class BubbleData : BarLineScatterCandleBubbleData { * for all DataSet objects this data object contains, in dp. */ fun setHighlightCircleWidth(width: Float) { - for (set in mDataSets) { - set.highlightCircleWidth = width + if (dataSets != null) { + for (set in dataSets) { + set.highlightCircleWidth = width + } } } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java deleted file mode 100644 index af8f66e92..000000000 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.java +++ /dev/null @@ -1,776 +0,0 @@ - -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.io.Serializable; -import java.util.ArrayList; -import java.util.Collections; -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 - */ -@SuppressWarnings("unused") -public abstract class ChartData> implements Serializable { - - /** - * 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 mDataSets; - - /** - * Default constructor. - */ - public ChartData() { - mDataSets = new ArrayList<>(); - } - - /** - * Constructor taking single or multiple DataSet objects. - */ - public ChartData(T... dataSets) { - mDataSets = arrayToList(dataSets); - notifyDataChanged(); - } - - /** - * Created because Arrays.asList(...) does not support modification. - */ - private List arrayToList(T[] array) { - - List list = new ArrayList<>(); - - Collections.addAll(list, array); - - return list; - } - - /** - * constructor for chart data - * - * @param sets the dataset array - */ - public ChartData(List 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(); - } - } - } - } - } - - /** - * returns the number of LineDataSets this object contains - */ - public int getDataSetCount() { - if (mDataSets == null) { - return 0; - } - return mDataSets.size(); - } - - /** - * Returns the smallest y-value the data object contains. - */ - public float getYMin() { - return mYMin; - } - - /** - * Returns the minimum y-value for the specified axis. - */ - 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. - */ - public float getYMax() { - return mYMax; - } - - /** - * Returns the maximum y-value for the specified axis. - */ - 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. - */ - public float getXMin() { - return mXMin; - } - - /** - * Returns the maximum x-value this data object contains. - */ - public float getXMax() { - return mXMax; - } - - /** - * Returns all DataSet objects this ChartData object holds. - */ - public List 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 ignorecase if true, the search is not case-sensitive - */ - protected int getDataSetIndexByLabel(List 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. - */ - 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 - * @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. - */ - 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. - */ - 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. - */ - public boolean removeDataSet(T d) { - - if (d == null) { - return false; - } - - boolean removed = mDataSets.remove(d); - - // if a DataSet was removed - if (removed) { - notifyDataChanged(); - } - - 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. - */ - 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. - */ - 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. - */ - protected void calcMinMax(Entry entry, AxisDependency axis) { - - if (mYMax < entry.getY()) { - mYMax = entry.getY(); - } - if (mYMin > entry.getY()) { - mYMin = entry.getY(); - } - - if (mXMax < entry.getX()) { - mXMax = entry.getX(); - } - if (mXMin > entry.getX()) { - mXMin = entry.getX(); - } - - if (axis == AxisDependency.LEFT) { - - if (mLeftAxisMax < entry.getY()) { - mLeftAxisMax = entry.getY(); - } - if (mLeftAxisMin > entry.getY()) { - mLeftAxisMin = entry.getY(); - } - } else { - if (mRightAxisMax < entry.getY()) { - mRightAxisMax = entry.getY(); - } - if (mRightAxisMin > entry.getY()) { - mRightAxisMin = entry.getY(); - } - } - } - - /** - * Adjusts the minimum and maximum values based on the given DataSet. - */ - 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. - */ - public boolean removeEntry(Entry e, int dataSetIndex) { - - // entry null, out of bounds - 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) { - notifyDataChanged(); - } - - 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. - */ - 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. - */ - 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. - */ - 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 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. - */ - 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. - */ - protected T getFirstLeft(List 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. - */ - public T getFirstRight(List 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. - */ - public void setValueFormatter(IValueFormatter valueFormatter) { - for (IDataSet set : mDataSets) { - set.setValueFormatter(valueFormatter); - } - } - - /** - * Sets the color of the value-text (color in which the value-labels are - * drawn) for all DataSets this data object contains. - */ - public void setValueTextColor(int color) { - for (IDataSet set : mDataSets) { - set.setSingleValueTextColor(color); - } - } - - /** - * Sets the same list of value-colors for all DataSets this - * data object contains. - */ - public void setValueTextColors(List colors) { - for (IDataSet set : mDataSets) { - set.setValueTextColors(colors); - } - } - - /** - * Sets the Typeface for all value-labels for all DataSets this data object - * contains. - */ - 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. - */ - 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. - */ - 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. - */ - 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. - */ - 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. - */ - 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. - */ - 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; - } -} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.kt new file mode 100644 index 000000000..b46cabf66 --- /dev/null +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ChartData.kt @@ -0,0 +1,720 @@ +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.io.Serializable +import java.util.Collections + +/** + * Class that holds all relevant data that represents the chart. That involves at least one (or more) DataSets, and an array of x-values. + */ +@Suppress("unused") +abstract class ChartData> : Serializable { + /** + * maximum y-value in the value array across all axes + */ + var yMax: Float = -Float.MAX_VALUE + protected set + + /** + * the minimum y-value in the value array across all axes + */ + var yMin: Float = Float.MAX_VALUE + protected set + + /** + * maximum x-value in the value array + */ + var xMax: Float = -Float.MAX_VALUE + protected set + + /** + * minimum x-value in the value array + */ + var xMin: Float = Float.MAX_VALUE + protected set + + + @JvmField + protected var mLeftAxisMax: Float = -Float.MAX_VALUE + + @JvmField + protected var mLeftAxisMin: Float = Float.MAX_VALUE + + @JvmField + protected var mRightAxisMax: Float = -Float.MAX_VALUE + + @JvmField + protected var mRightAxisMin: Float = Float.MAX_VALUE + + /** + * Returns all DataSet objects this ChartData object holds. + */ + /** + * array that holds all DataSets the ChartData object represents + */ + open var dataSets: MutableList? = null + protected set + + /** + * Default constructor. + */ + constructor() { + this.dataSets = ArrayList() + } + + /** + * Constructor taking single or multiple DataSet objects. + */ + constructor(vararg dataSets: T) { + this.dataSets = dataSets.toMutableList() + notifyDataChanged() + } + + /** + * Created because Arrays.asList(...) does not support modification. + */ + private fun arrayToList(array: Array): MutableList { + val list: MutableList = ArrayList() + + Collections.addAll(list, *array) + + return list + } + + /** + * constructor for chart data + * + * @param sets the dataset array + */ + constructor(sets: MutableList) { + this.dataSets = 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. + */ + open fun 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 + */ + fun calcMinMaxY(fromX: Float, toX: Float) { + for (set in this.dataSets!!) { + set.calcMinMaxY(fromX, toX) + } + + // apply the new data + calcMinMax() + } + + /** + * Calc minimum and maximum values (both x and y) over all DataSets. + */ + open fun calcMinMax() { + if (this.dataSets == null) { + return + } + + this.yMax = -Float.MAX_VALUE + this.yMin = Float.MAX_VALUE + this.xMax = -Float.MAX_VALUE + this.xMin = Float.MAX_VALUE + + for (set in this.dataSets) { + calcMinMax(set) + } + + mLeftAxisMax = -Float.MAX_VALUE + mLeftAxisMin = Float.MAX_VALUE + mRightAxisMax = -Float.MAX_VALUE + mRightAxisMin = Float.MAX_VALUE + + // left axis + val firstLeft = getFirstLeft(this.dataSets!!) + + if (firstLeft != null) { + mLeftAxisMax = firstLeft.yMax + mLeftAxisMin = firstLeft.yMin + + for (dataSet in this.dataSets) { + if (dataSet.axisDependency == AxisDependency.LEFT) { + if (dataSet.yMin < mLeftAxisMin) { + mLeftAxisMin = dataSet.yMin + } + + if (dataSet.yMax > mLeftAxisMax) { + mLeftAxisMax = dataSet.yMax + } + } + } + } + + // right axis + val firstRight = getFirstRight(this.dataSets!!) + + if (firstRight != null) { + mRightAxisMax = firstRight.yMax + mRightAxisMin = firstRight.yMin + + for (dataSet in this.dataSets) { + if (dataSet.axisDependency == AxisDependency.RIGHT) { + if (dataSet.yMin < mRightAxisMin) { + mRightAxisMin = dataSet.yMin + } + + if (dataSet.yMax > mRightAxisMax) { + mRightAxisMax = dataSet.yMax + } + } + } + } + } + + val dataSetCount: Int + /** + * returns the number of LineDataSets this object contains + */ + get() { + if (this.dataSets == null) { + return 0 + } + return dataSets!!.size + } + + /** + * Returns the minimum y-value for the specified axis. + */ + fun getYMin(axis: AxisDependency?): Float { + return if (axis == AxisDependency.LEFT) { + if (mLeftAxisMin == Float.MAX_VALUE) { + mRightAxisMin + } else { + mLeftAxisMin + } + } else { + if (mRightAxisMin == Float.MAX_VALUE) { + mLeftAxisMin + } else { + mRightAxisMin + } + } + } + + /** + * Returns the maximum y-value for the specified axis. + */ + fun getYMax(axis: AxisDependency?): Float { + return if (axis == AxisDependency.LEFT) { + if (mLeftAxisMax == -Float.MAX_VALUE) { + mRightAxisMax + } else { + mLeftAxisMax + } + } else { + if (mRightAxisMax == -Float.MAX_VALUE) { + mLeftAxisMax + } else { + mRightAxisMax + } + } + } + + /** + * 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 ignoreCase if true, the search is not case-sensitive + */ + protected fun getDataSetIndexByLabel( + dataSets: MutableList<@UnsafeVariance T>, label: String, + ignoreCase: Boolean + ): Int { + if (ignoreCase) { + for (i in dataSets.indices) { + if (label.equals(dataSets[i].label, ignoreCase = true)) { + return i + } + } + } else { + for (i in dataSets.indices) { + if (label == dataSets[i].label) { + return i + } + } + } + + return -1 + } + + val dataSetLabels: Array + /** + * Returns the labels of all DataSets as a string array. + */ + get() { + val types = arrayOfNulls(dataSets!!.size) + + for (i in dataSets!!.indices) { + types[i] = dataSets!![i].label + } + + return types + } + + /** + * Get the Entry for a corresponding highlight object + * @return the entry that is highlighted + */ + open fun getEntryForHighlight(highlight: Highlight): Entry? { + return if (highlight.dataSetIndex >= dataSets!!.size) { + null + } else { + dataSets!![highlight.dataSetIndex].getEntryForXValue(highlight.x, highlight.y) + } + } + + /** + * 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. + */ + open fun getDataSetByLabel(label: String, ignoreCase: Boolean): T? { + val index = getDataSetIndexByLabel(this.dataSets!!, label, ignoreCase) + + return if (index < 0 || index >= dataSets!!.size) { + null + } else { + dataSets!![index] + } + } + + open fun getDataSetByIndex(index: Int): T? { + if (this.dataSets == null || index < 0 || index >= dataSets!!.size) { + return null + } + + return dataSets!![index] + } + + /** + * Adds a DataSet dynamically. + */ + fun addDataSet(d: @UnsafeVariance T?) { + if (d == null) { + return + } + + calcMinMax(d) + + dataSets!!.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. + */ + open fun removeDataSet(d: @UnsafeVariance T?): Boolean { + if (d == null) { + return false + } + + val removed = dataSets!!.remove(d) + + // if a DataSet was removed + if (removed) { + notifyDataChanged() + } + + 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. + */ + open fun removeDataSet(index: Int): Boolean { + if (index >= dataSets!!.size || index < 0) { + return false + } + + val set = dataSets!![index] + return removeDataSet(set) + } + + /** + * Adds an Entry to the DataSet at the specified index. + * Entries are added to the end of the list. + */ + @Suppress("UNCHECKED_CAST") + fun addEntry(entry: Entry, dataSetIndex: Int) { + if (dataSets!!.size > dataSetIndex && dataSetIndex >= 0) { + val set: T = dataSets!![dataSetIndex] + // add the entry to the dataset + // We need to cast here because T is covariant (out) but addEntry needs to consume T + val dataSet = set as IDataSet + if (!dataSet.addEntry(entry)) { + return + } + + calcMinMax(entry, set.axisDependency) + } 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. + */ + protected fun calcMinMax(e: Entry, axis: AxisDependency?) { + if (this.yMax < e.y) { + this.yMax = e.y + } + if (this.yMin > e.y) { + this.yMin = e.y + } + + if (this.xMax < e.x) { + this.xMax = e.x + } + if (this.xMin > e.x) { + this.xMin = e.x + } + + if (axis == AxisDependency.LEFT) { + if (mLeftAxisMax < e.y) { + mLeftAxisMax = e.y + } + if (mLeftAxisMin > e.y) { + mLeftAxisMin = e.y + } + } else { + if (mRightAxisMax < e.y) { + mRightAxisMax = e.y + } + if (mRightAxisMin > e.y) { + mRightAxisMin = e.y + } + } + } + + /** + * Adjusts the minimum and maximum values based on the given DataSet. + */ + protected fun calcMinMax(d: @UnsafeVariance T?) { + if (this.yMax < d!!.yMax) { + this.yMax = d.yMax + } + if (this.yMin > d.yMin) { + this.yMin = d.yMin + } + + if (this.xMax < d.xMax) { + this.xMax = d.xMax + } + if (this.xMin > d.xMin) { + this.xMin = d.xMin + } + + if (d.axisDependency == AxisDependency.LEFT) { + if (mLeftAxisMax < d.yMax) { + mLeftAxisMax = d.yMax + } + if (mLeftAxisMin > d.yMin) { + mLeftAxisMin = d.yMin + } + } else { + if (mRightAxisMax < d.yMax) { + mRightAxisMax = d.yMax + } + if (mRightAxisMin > d.yMin) { + mRightAxisMin = d.yMin + } + } + } + + /** + * Removes the given Entry object from the DataSet at the specified index. + */ + @Suppress("UNCHECKED_CAST") + open fun removeEntry(entry: Entry?, dataSetIndex: Int): Boolean { + // entry null, out of bounds + + if (entry == null || dataSetIndex >= dataSets!!.size) { + return false + } + + val set: T = dataSets!![dataSetIndex] + + // remove the entry from the dataset + // Cast needed because T is covariant (out) but removeEntry needs to consume T + val dataSet = set as IDataSet + val removed: Boolean = dataSet.removeEntry(entry) + + if (removed) { + notifyDataChanged() + } + + return removed + } + + /** + * 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. + */ + open fun removeEntry(xValue: Float, dataSetIndex: Int): Boolean { + if (dataSetIndex >= dataSets!!.size) { + return false + } + + val dataSet: IDataSet<*> = dataSets!![dataSetIndex] + val entry: Entry = dataSet.getEntryForXValue(xValue, Float.NaN) ?: return false + + return removeEntry(entry, dataSetIndex) + } + + /** + * Returns the DataSet that contains the provided Entry, or null, if no + * DataSet contains this Entry. + */ + fun getDataSetForEntry(e: Entry?): T? { + if (e == null) { + return null + } + + for (i in dataSets!!.indices) { + val set = dataSets!![i] + + for (j in 0..): T? { + for (dataSet in sets) { + if (dataSet.axisDependency == 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. + */ + fun getFirstRight(sets: MutableList<@UnsafeVariance T>): T? { + for (dataSet in sets) { + if (dataSet.axisDependency == AxisDependency.RIGHT) { + return dataSet + } + } + return null + } + + /** + * Sets a custom IValueFormatter for all DataSets this data object contains. + */ + fun setValueFormatter(f: IValueFormatter) { + for (set in this.dataSets!!) { + set.valueFormatter = f + } + } + + /** + * Sets the color of the value-text (color in which the value-labels are + * drawn) for all DataSets this data object contains. + */ + fun setValueTextColor(color: Int) { + for (set in this.dataSets!!) { + set.setSingleValueTextColor(color) + } + } + + /** + * Sets the same list of value-colors for all DataSets this + * data object contains. + */ + fun setValueTextColors(colors: MutableList) { + for (set in this.dataSets!!) { + set.valueTextColors = colors + } + } + + /** + * Sets the Typeface for all value-labels for all DataSets this data object + * contains. + */ + fun setValueTypeface(tf: Typeface?) { + for (set in this.dataSets!!) { + set.valueTypeface = tf + } + } + + /** + * Sets the size (in dp) of the value-text for all DataSets this data object + * contains. + */ + fun setValueTextSize(size: Float) { + for (set in this.dataSets!!) { + set.valueTextSize = size + } + } + + /** + * Enables / disables drawing values (value-text) for all DataSets this data + * object contains. + */ + fun setDrawValues(enabled: Boolean) { + for (set in this.dataSets!!) { + set.isDrawValues = enabled + } + } + + var isHighlightEnabled: Boolean + /** + * Returns true if highlighting of all underlying values is enabled, false if not. + */ + get() { + for (set in this.dataSets!!) { + if (!set.isHighlightEnabled) { + return false + } + } + return true + } + /** + * 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. + */ + set(enabled) { + for (set in this.dataSets!!) { + set.isHighlightEnabled = enabled + } + } + + /** + * Clears this data object from all DataSets and removes all Entries. Don't + * forget to invalidate the chart after this. + */ + fun clearValues() { + if (this.dataSets != null) { + dataSets!!.clear() + } + notifyDataChanged() + } + + /** + * Checks if this data object contains the specified DataSet. Returns true + * if so, false if not. + */ + fun contains(dataSet: @UnsafeVariance T?): Boolean { + for (set in this.dataSets!!) { + if (set == dataSet) { + return true + } + } + + return false + } + + val entryCount: Int + /** + * Returns the total entry count across all DataSet objects this data object contains. + */ + get() { + var count = 0 + + for (set in this.dataSets!!) { + count += set.entryCount + } + + return count + } + + val maxEntryCountSet: T? + /** + * Returns the DataSet object with the maximum number of entries or null if there are no DataSets. + */ + get() { + if (this.dataSets == null || dataSets!!.isEmpty()) { + return null + } + + var max = dataSets!![0] + + for (set in this.dataSets) { + if (set.entryCount > max.entryCount) { + max = set + } + } + + return max + } +} diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java index de31839d3..b410bdb99 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java @@ -56,15 +56,15 @@ public void setData(BubbleData data) { @Override public void calcMinMax() { - if(mDataSets == null){ - mDataSets = new ArrayList<>(); + if(dataSets == null){ + dataSets = new ArrayList<>(); } - mDataSets.clear(); + dataSets.clear(); - mYMax = -Float.MAX_VALUE; - mYMin = Float.MAX_VALUE; - mXMax = -Float.MAX_VALUE; - mXMin = Float.MAX_VALUE; + yMax = -Float.MAX_VALUE; + yMin = Float.MAX_VALUE; + xMax = -Float.MAX_VALUE; + xMin = Float.MAX_VALUE; mLeftAxisMax = -Float.MAX_VALUE; mLeftAxisMin = Float.MAX_VALUE; @@ -78,19 +78,19 @@ public void calcMinMax() { data.calcMinMax(); List> sets = data.getDataSets(); - mDataSets.addAll(sets); + dataSets.addAll(sets); - if (data.getYMax() > mYMax) - mYMax = data.getYMax(); + if (data.getYMax() > yMax) + yMax = data.getYMax(); - if (data.getYMin() < mYMin) - mYMin = data.getYMin(); + if (data.getYMin() < yMin) + yMin = data.getYMin(); - if (data.getXMax() > mXMax) - mXMax = data.getXMax(); + if (data.getXMax() > xMax) + xMax = data.getXMax(); - if (data.getXMin() < mXMin) - mXMin = data.getXMin(); + if (data.getXMin() < xMin) + xMin = data.getXMin(); for (IBarLineScatterCandleBubbleDataSet dataset : sets) { if (dataset.getAxisDependency() == YAxis.AxisDependency.LEFT) { @@ -254,7 +254,7 @@ public boolean removeDataSet(int index) { @Deprecated @Override - public boolean removeEntry(Entry e, int dataSetIndex) { + public boolean removeEntry(Entry entry, int dataSetIndex) { Log.e("AndroidChart", "removeEntry(...) not supported for CombinedData"); return false; } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.kt index 03bbd1f4a..afadfa9c3 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/PieData.kt @@ -20,41 +20,47 @@ class PieData : ChartData { * Returns the DataSet this PieData object represents. A PieData object can * only contain one DataSet. */ - get() = mDataSets[0] + get() = dataSets!![0] /** * Sets the PieDataSet this data object should represent. */ set(dataSet) { - mDataSets.clear() - mDataSets.add(dataSet) + dataSets?.clear() + dataSets?.add(dataSet) notifyDataChanged() } - override fun getDataSets(): MutableList { - val dataSets = super.getDataSets() - - if (dataSets.isEmpty()) { - Log.e( - "AndroidChart", - "Found multiple data sets while pie chart only allows one" - ) + override var dataSets: MutableList? + get() { + super.dataSets?.let { + if (it.isEmpty()) { + Log.e("AndroidChart", "Found multiple data sets while pie chart only allows one") + } + } + return super.dataSets + } + set(value) { + super.dataSets = value } - - return dataSets - } /** * The PieData object can only have one DataSet. Use getDataSet() method instead. */ override fun getDataSetByIndex(index: Int): IPieDataSet? { - return if (index == 0) this.dataSet else null + return if (index == 0) + this.dataSet + else + null } - override fun getDataSetByLabel(label: String, ignorecase: Boolean): IPieDataSet? { - return if (ignorecase) if (label.equals(mDataSets[0]!!.label, ignoreCase = true)) - mDataSets[0] + override fun getDataSetByLabel(label: String, ignoreCase: Boolean): IPieDataSet? { + return if (ignoreCase) if (label.equals(dataSets!![0].label, ignoreCase = true)) + dataSets!![0] + else + null else if (label == dataSets!![0].label) + dataSets!![0] else - null else if (label == mDataSets[0]!!.label) mDataSets[0] else null + null } override fun getEntryForHighlight(highlight: Highlight): Entry? { diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.kt index 69c13d39f..00f80971a 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/ScatterData.kt @@ -16,10 +16,12 @@ class ScatterData : BarLineScatterCandleBubbleData { get() { var max = 0f - for (set in mDataSets) { - val size = set.scatterShapeSize + if (dataSets != null) { + for (set in dataSets) { + val size = set.scatterShapeSize - if (size > max) max = size + if (size > max) max = size + } } return max diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.kt index 88a39a83c..da33fd8b8 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/BarHighlighter.kt @@ -14,16 +14,16 @@ open class BarHighlighter(barDataProvider: BarDataProvider) : ChartHighlighter + if (set.isStacked()) { + return getStackedHighlight( + high, + set, + pos.x.toFloat(), + pos.y.toFloat() + ) + } } - MPPointD.recycleInstance(pos) return high diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.kt index 945218175..0a7e8354e 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/ChartHighlighter.kt @@ -92,17 +92,19 @@ open class ChartHighlighter(protecte val data = this.data var i = 0 - val dataSetCount = data.getDataSetCount() + val dataSetCount = data.dataSetCount while (i < dataSetCount) { - val dataSet: IDataSet<*> = data.getDataSetByIndex(i) + val dataSet= data.getDataSetByIndex(i) // don't include DataSets that cannot be highlighted - if (!dataSet.isHighlightEnabled) { - i++ - continue + dataSet?.let { + if (!it.isHighlightEnabled) { + i++ + continue + } + highlightBuffer.addAll(buildHighlights(it, i, xVal, DataSet.Rounding.CLOSEST)) } - highlightBuffer.addAll(buildHighlights(dataSet, i, xVal, DataSet.Rounding.CLOSEST)) i++ } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.kt index 291b5b484..d8b425356 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/CombinedHighlighter.kt @@ -5,7 +5,6 @@ import com.github.mikephil.charting.data.ChartData import com.github.mikephil.charting.data.DataSet import com.github.mikephil.charting.interfaces.dataprovider.BarDataProvider import com.github.mikephil.charting.interfaces.dataprovider.CombinedDataProvider -import com.github.mikephil.charting.interfaces.datasets.IDataSet open class CombinedHighlighter(dataProvider: CombinedDataProvider, barChart: BarDataProvider) : ChartHighlighter(dataProvider), IHighlighter { /** @@ -37,20 +36,22 @@ open class CombinedHighlighter(dataProvider: CombinedDataProvider, barChart: Bar } } else { var j = 0 - val dataSetCount = dataObject.getDataSetCount() + val dataSetCount = dataObject.dataSetCount while (j < dataSetCount) { - val dataSet: IDataSet<*> = dataObjects[i].getDataSetByIndex(j) - - // don't include datasets that cannot be highlighted - if (!dataSet.isHighlightEnabled) { - j++ - continue - } - - val highs = buildHighlights(dataSet, j, xVal, DataSet.Rounding.CLOSEST) - for (high in highs) { - high.dataIndex = i - highlightBuffer.add(high) + val dataSet = dataObjects[i].getDataSetByIndex(j) + + dataSet?.let { + // don't include datasets that cannot be highlighted + if (!it.isHighlightEnabled) { + j++ + continue + } + + val highs = buildHighlights(it, j, xVal, DataSet.Rounding.CLOSEST) + for (high in highs) { + high.dataIndex = i + highlightBuffer.add(high) + } } j++ } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.kt index e28d61c54..1bc4fb7a1 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/HorizontalBarHighlighter.kt @@ -16,7 +16,7 @@ class HorizontalBarHighlighter(dataProvider: BarDataProvider) : BarHighlighter(d val high = getHighlightForX(pos.y.toFloat(), y, x) ?: return null val set = barData.getDataSetByIndex(high.dataSetIndex) - if (set.isStacked()) { + if (set != null && set.isStacked()) { return getStackedHighlight( high, set, diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.kt index 43d643fa2..69a8da737 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/RadarHighlighter.kt @@ -2,7 +2,6 @@ package com.github.mikephil.charting.highlight import com.github.mikephil.charting.charts.RadarChart import com.github.mikephil.charting.data.Entry -import com.github.mikephil.charting.interfaces.datasets.IDataSet import com.github.mikephil.charting.utils.MPPointF import com.github.mikephil.charting.utils.Utils import kotlin.math.abs @@ -44,10 +43,10 @@ open class RadarHighlighter(chart: RadarChart) : PieRadarHighlighter val factor = chartPieRadar.getFactor() val pOut = MPPointF.getInstance(0f, 0f) - for (i in 0.. = chartPieRadar.data!!.getDataSetByIndex(i) + for (i in 0.. getData(); + ChartData getData(); int getMaxVisibleCount(); } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarChartRenderer.kt index 96f4c245c..20d6480fb 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/BarChartRenderer.kt @@ -49,9 +49,9 @@ open class BarChartRenderer( private var roundedBarRadius = 0f constructor( - chart: BarDataProvider, animator: ChartAnimator, + dataProvider: BarDataProvider, animator: ChartAnimator, viewPortHandler: ViewPortHandler, mDrawRoundedBars: Boolean, mRoundedBarRadius: Float - ) : this(chart, animator, viewPortHandler) { + ) : this(dataProvider, animator, viewPortHandler) { this.drawRoundedBars = mDrawRoundedBars this.roundedBarRadius = mRoundedBarRadius } @@ -60,7 +60,7 @@ open class BarChartRenderer( val barData = dataProvider.barData barBuffers = mutableListOf() - barData.dataSets.forEach { + barData.dataSets?.forEach { barBuffers.add( BarBuffer( it.entryCount * 4 * (if (it.isStacked) it.stackSize else 1), @@ -80,8 +80,10 @@ open class BarChartRenderer( for (i in 0.. - val positions = transformer.generateTransformedValuesBubble(dataSet, phaseY, xBounds.min, xBounds.max) + xBounds.set(dataProvider, dataSet) - val alpha = if (phaseX == 1f) - phaseY - else - phaseX + dataProvider.getTransformer(dataSet.axisDependency)?.let { transformer -> + val positions = transformer.generateTransformedValuesBubble(dataSet, phaseY, xBounds.min, xBounds.max) - val iconsOffset = MPPointF.getInstance(dataSet.iconsOffset) - iconsOffset.x = iconsOffset.x.convertDpToPixel() - iconsOffset.y = iconsOffset.y.convertDpToPixel() + val alpha = if (phaseX == 1f) + phaseY + else + phaseX - var j = 0 - while (j < positions.size) { - var valueTextColor = dataSet.getValueTextColor(j / 2 + xBounds.min) - valueTextColor = Color.argb( - (255f * alpha).roundToInt(), Color.red(valueTextColor), - Color.green(valueTextColor), Color.blue(valueTextColor) - ) + val iconsOffset = MPPointF.getInstance(dataSet.iconsOffset) + iconsOffset.x = iconsOffset.x.convertDpToPixel() + iconsOffset.y = iconsOffset.y.convertDpToPixel() - val x = positions[j] - val y = positions[j + 1] + var j = 0 + while (j < positions.size) { + var valueTextColor = dataSet.getValueTextColor(j / 2 + xBounds.min) + valueTextColor = Color.argb( + (255f * alpha).roundToInt(), Color.red(valueTextColor), + Color.green(valueTextColor), Color.blue(valueTextColor) + ) - if (!viewPortHandler.isInBoundsRight(x)) break + val x = positions[j] + val y = positions[j + 1] - if ((!viewPortHandler.isInBoundsLeft(x) || !viewPortHandler.isInBoundsY(y))) { - j += 2 - continue - } + if (!viewPortHandler.isInBoundsRight(x)) break - val bubbleEntry = dataSet.getEntryForIndex(j / 2 + xBounds.min) - bubbleEntry?.let { - if (dataSet.isDrawValues) { - drawValue( - canvas, dataSet.valueFormatter, bubbleEntry.size, bubbleEntry, i, x, - y + (0.5f * lineHeight), valueTextColor - ) + if ((!viewPortHandler.isInBoundsLeft(x) || !viewPortHandler.isInBoundsY(y))) { + j += 2 + continue } - if (bubbleEntry.icon != null && dataSet.isDrawIcons) { - val icon = bubbleEntry.icon - - icon?.let { - Utils.drawImage( - canvas, - it, - (x + iconsOffset.x).toInt(), - (y + iconsOffset.y).toInt() + val bubbleEntry = dataSet.getEntryForIndex(j / 2 + xBounds.min) + bubbleEntry?.let { + if (dataSet.isDrawValues) { + drawValue( + canvas, dataSet.valueFormatter, bubbleEntry.size, bubbleEntry, i, x, + y + (0.5f * lineHeight), valueTextColor ) } + + if (bubbleEntry.icon != null && dataSet.isDrawIcons) { + val icon = bubbleEntry.icon + + icon?.let { ico -> + Utils.drawImage( + canvas, + ico, + (x + iconsOffset.x).toInt(), + (y + iconsOffset.y).toInt() + ) + } + } } + j += 2 } - j += 2 - } - MPPointF.recycleInstance(iconsOffset) + MPPointF.recycleInstance(iconsOffset) + } } } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CandleStickChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CandleStickChartRenderer.kt index f47f5a1d0..fe906fcb1 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CandleStickChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/CandleStickChartRenderer.kt @@ -211,75 +211,77 @@ open class CandleStickChartRenderer( if (isDrawingValuesAllowed(dataProvider)) { dataProvider.candleData?.let { - for (i in it.dataSets.indices) { - val dataSet = it.dataSets[i] - if (dataSet.entryCount == 0) { - continue - } - if (!shouldDrawValues(dataSet) || dataSet.entryCount < 1) { - continue - } - - // apply the text-styling defined by the DataSet - applyValueTextStyle(dataSet) + it.dataSets?.let { it1 -> + for (i in it1.indices) { + val dataSet = it.dataSets!![i] + if (dataSet.entryCount == 0) { + continue + } + if (!shouldDrawValues(dataSet) || dataSet.entryCount < 1) { + continue + } - val trans = dataProvider.getTransformer(dataSet.axisDependency) + // apply the text-styling defined by the DataSet + applyValueTextStyle(dataSet) - xBounds.set(dataProvider, dataSet) + val trans = dataProvider.getTransformer(dataSet.axisDependency) - val positions = trans!!.generateTransformedValuesCandle( - dataSet, animator.phaseX, animator.phaseY, xBounds.min, xBounds.max - ) + xBounds.set(dataProvider, dataSet) - val yOffset = 5f.convertDpToPixel() + val positions = trans!!.generateTransformedValuesCandle( + dataSet, animator.phaseX, animator.phaseY, xBounds.min, xBounds.max + ) - val iconsOffset = MPPointF.getInstance(dataSet.iconsOffset) - iconsOffset.x = iconsOffset.x.convertDpToPixel() - iconsOffset.y = iconsOffset.y.convertDpToPixel() + val yOffset = 5f.convertDpToPixel() - var j = 0 - while (j < positions.size) { - val x = positions[j] - val y = positions[j + 1] + val iconsOffset = MPPointF.getInstance(dataSet.iconsOffset) + iconsOffset.x = iconsOffset.x.convertDpToPixel() + iconsOffset.y = iconsOffset.y.convertDpToPixel() - if (!viewPortHandler.isInBoundsRight(x)) break + var j = 0 + while (j < positions.size) { + val x = positions[j] + val y = positions[j + 1] - if (!viewPortHandler.isInBoundsLeft(x) || !viewPortHandler.isInBoundsY(y)) { - j += 2 - continue - } + if (!viewPortHandler.isInBoundsRight(x)) break - val entry = dataSet.getEntryForIndex(j / 2 + xBounds.min)!! - - if (dataSet.isDrawValues) { - drawValue( - canvas, - dataSet.valueFormatter, - entry.high, - entry, - i, - x, - y - yOffset, - dataSet.getValueTextColor(j / 2) - ) - } + if (!viewPortHandler.isInBoundsLeft(x) || !viewPortHandler.isInBoundsY(y)) { + j += 2 + continue + } - if (entry.icon != null && dataSet.isDrawIcons) { - val icon = entry.icon + val entry = dataSet.getEntryForIndex(j / 2 + xBounds.min)!! - icon?.let { ico -> - Utils.drawImage( + if (dataSet.isDrawValues) { + drawValue( canvas, - ico, - (x + iconsOffset.x).toInt(), - (y + iconsOffset.y).toInt() + dataSet.valueFormatter, + entry.high, + entry, + i, + x, + y - yOffset, + dataSet.getValueTextColor(j / 2) ) } + + if (entry.icon != null && dataSet.isDrawIcons) { + val icon = entry.icon + + icon?.let { ico -> + Utils.drawImage( + canvas, + ico, + (x + iconsOffset.x).toInt(), + (y + iconsOffset.y).toInt() + ) + } + } + j += 2 } - j += 2 - } - MPPointF.recycleInstance(iconsOffset) + MPPointF.recycleInstance(iconsOffset) + } } } } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/DataRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/DataRenderer.kt index f032c643d..d71a0ff08 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/DataRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/DataRenderer.kt @@ -58,8 +58,8 @@ abstract class DataRenderer( paintHighlight.color = Color.rgb(255, 187, 115) } - protected open fun isDrawingValuesAllowed(chart: IBaseProvider): Boolean { - return chart.data!!.entryCount < chart.maxVisibleCount * viewPortHandler.scaleX + protected open fun isDrawingValuesAllowed(baseProvider: IBaseProvider): Boolean { + return baseProvider.data!!.entryCount < baseProvider.maxVisibleCount * viewPortHandler.scaleX } /** diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.kt index 0948c4b12..3d511ff96 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/HorizontalBarChartRenderer.kt @@ -32,10 +32,12 @@ open class HorizontalBarChartRenderer( for (i in barBuffers.indices) { val set = barData.getDataSetByIndex(i) - barBuffers[i] = HorizontalBarBuffer( - set.entryCount * 4 * (if (set.isStacked) set.stackSize else 1), - barData.dataSetCount, set.isStacked - ) + set?.let { + barBuffers[i] = HorizontalBarBuffer( + it.entryCount * 4 * (if (set.isStacked) set.stackSize else 1), + barData.dataSetCount, set.isStacked + ) + } } } @@ -175,7 +177,7 @@ open class HorizontalBarChartRenderer( val drawValueAboveBar = dataProvider.isDrawValueAboveBarEnabled for (i in 0.. - set?.let { - if (it.isVisible) - drawDataSet(canvas, set) - } + lineData.dataSets?.forEach { set -> + if (set.isVisible) + drawDataSet(canvas, set) } canvas.drawBitmap(drawBitmapLocal, 0f, 0f, null) } @@ -277,113 +275,113 @@ open class LineChartRenderer( } // more than 1 color - if (dataSet.colors.size > 1) { - val numberOfFloats = pointsPerEntryPair * 2 + if (dataSet.colors.size > 1) { + val numberOfFloats = pointsPerEntryPair * 2 - if (lineBuffer.size <= numberOfFloats) - lineBuffer = FloatArray(numberOfFloats * 2) + if (lineBuffer.size <= numberOfFloats) + lineBuffer = FloatArray(numberOfFloats * 2) - val max = xBounds.min + xBounds.range + val max = xBounds.min + xBounds.range - for (j in xBounds.min.. 0) { - trans!!.pointValuesToPixel(lineBuffer) + lineBuffer[j++] = e2.x + lineBuffer[j++] = e2.y * phaseY + } + + if (j > 0) { + trans!!.pointValuesToPixel(lineBuffer) - val size = (max(((xBounds.range + 1) * pointsPerEntryPair).toDouble(), pointsPerEntryPair.toDouble()) * 2).toInt() + val size = (max(((xBounds.range + 1) * pointsPerEntryPair).toDouble(), pointsPerEntryPair.toDouble()) * 2).toInt() - paintRender.color = dataSet.color + paintRender.color = dataSet.color - canvas!!.drawLines(lineBuffer, 0, size, paintRender) - } + canvas!!.drawLines(lineBuffer, 0, size, paintRender) } + } } paintRender.pathEffect = null @@ -487,9 +485,9 @@ open class LineChartRenderer( if (isDrawingValuesAllowed(dataProvider)) { val dataSets = dataProvider.lineData.dataSets - for (i in dataSets.indices) { - val dataSet = dataSets[i] - dataSet?.let { + dataSets?.let { + for (i in it.indices) { + val dataSet = dataSets[i] if (dataSet.entryCount == 0) { continue } @@ -591,9 +589,9 @@ open class LineChartRenderer( val dataSets = dataProvider.lineData.dataSets - for (i in dataSets.indices) { - val dataSet = dataSets[i] - dataSet?.let { + dataSets?.let { + for (i in it.indices) { + val dataSet = dataSets[i] if (!dataSet.isVisible || !dataSet.isDrawCirclesEnabled || dataSet.entryCount == 0) continue circlePaintInner.color = dataSet.circleHoleColor diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.kt index b3b0fc5e2..94c4d90a6 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/PieChartRenderer.kt @@ -103,7 +103,7 @@ open class PieChartRenderer( val pieData = chart.data - for (set in pieData!!.dataSets) { + for (set in pieData!!.dataSets!!) { if (set!!.isVisible && set.entryCount > 0) drawDataSet(set) } } @@ -387,196 +387,198 @@ open class PieChartRenderer( canvas.withSave { val offset = 5f.convertDpToPixel() - for (i in dataSets.indices) { - val dataSet = dataSets[i] - if (dataSet!!.entryCount == 0) { - continue - } - val drawValues = dataSet.isDrawValues - if (!drawValues && !drawEntryLabels) { - continue - } - - val xValuePosition = dataSet.xValuePosition - val yValuePosition = dataSet.yValuePosition - - // apply the text-styling defined by the DataSet - applyValueTextStyle(dataSet) + dataSets?.let { + for (i in it.indices) { + val dataSet = dataSets[i] + if (dataSet.entryCount == 0) { + continue + } + val drawValues = dataSet.isDrawValues + if (!drawValues && !drawEntryLabels) { + continue + } - val lineHeight = (Utils.calcTextHeight(paintValues, "Q") - + 4f.convertDpToPixel()) + val xValuePosition = dataSet.xValuePosition + val yValuePosition = dataSet.yValuePosition - val formatter = dataSet.valueFormatter + // apply the text-styling defined by the DataSet + applyValueTextStyle(dataSet) - val entryCount = dataSet.entryCount + val lineHeight = (Utils.calcTextHeight(paintValues, "Q") + + 4f.convertDpToPixel()) - val isUseValueColorForLineEnabled = dataSet.isUseValueColorForLineEnabled - val valueLineColor = dataSet.valueLineColor + val formatter = dataSet.valueFormatter - valueLinePaint.strokeWidth = dataSet.valueLineWidth.convertDpToPixel() + val entryCount = dataSet.entryCount - val sliceSpace = getSliceSpace(dataSet) + val isUseValueColorForLineEnabled = dataSet.isUseValueColorForLineEnabled + val valueLineColor = dataSet.valueLineColor - val iconsOffset = MPPointF.getInstance(dataSet.iconsOffset) - iconsOffset.x = iconsOffset.x.convertDpToPixel() - iconsOffset.y = iconsOffset.y.convertDpToPixel() + valueLinePaint.strokeWidth = dataSet.valueLineWidth.convertDpToPixel() - for (j in 0.. + val sliceSpace = getSliceSpace(dataSet) - angle = if (xIndex == 0) 0f - else absoluteAngles[xIndex - 1] * phaseX + val iconsOffset = MPPointF.getInstance(dataSet.iconsOffset) + iconsOffset.x = iconsOffset.x.convertDpToPixel() + iconsOffset.y = iconsOffset.y.convertDpToPixel() - val sliceAngle = drawAngles[xIndex] - val sliceSpaceMiddleAngle = sliceSpace / (Utils.FDEG2RAD * labelRadius) + for (j in 0.. - // offset needed to center the drawn text in the slice - val angleOffset = (sliceAngle - sliceSpaceMiddleAngle / 2f) / 2f + angle = if (xIndex == 0) 0f + else absoluteAngles[xIndex - 1] * phaseX - angle += angleOffset + val sliceAngle = drawAngles[xIndex] + val sliceSpaceMiddleAngle = sliceSpace / (Utils.FDEG2RAD * labelRadius) - val transformedAngle = rotationAngle + angle * phaseY + // offset needed to center the drawn text in the slice + val angleOffset = (sliceAngle - sliceSpaceMiddleAngle / 2f) / 2f - val value = if (chart.isUsePercentValuesEnabled) (entry.y / yValueSum * 100f) else entry.y - val entryLabel = entry.label + angle += angleOffset - val sliceXBase = cos((transformedAngle * Utils.FDEG2RAD).toDouble()).toFloat() - val sliceYBase = sin((transformedAngle * Utils.FDEG2RAD).toDouble()).toFloat() + val transformedAngle = rotationAngle + angle * phaseY - val drawXOutside = drawEntryLabels && xValuePosition == ValuePosition.OUTSIDE_SLICE - val drawYOutside = drawValues && yValuePosition == ValuePosition.OUTSIDE_SLICE - val drawXInside = drawEntryLabels && xValuePosition == ValuePosition.INSIDE_SLICE - val drawYInside = drawValues && yValuePosition == ValuePosition.INSIDE_SLICE + val value = if (chart.isUsePercentValuesEnabled) (entry.y / yValueSum * 100f) else entry.y + val entryLabel = entry.label - if (drawXOutside || drawYOutside) { - val valueLineLength1 = dataSet.valueLinePart1Length - val valueLineLength2 = dataSet.valueLinePart2Length - val valueLinePart1OffsetPercentage = dataSet.valueLinePart1OffsetPercentage / 100f + val sliceXBase = cos((transformedAngle * Utils.FDEG2RAD).toDouble()).toFloat() + val sliceYBase = sin((transformedAngle * Utils.FDEG2RAD).toDouble()).toFloat() - val pt2x: Float - val pt2y: Float - val labelPtx: Float - val labelPty: Float + val drawXOutside = drawEntryLabels && xValuePosition == ValuePosition.OUTSIDE_SLICE + val drawYOutside = drawValues && yValuePosition == ValuePosition.OUTSIDE_SLICE + val drawXInside = drawEntryLabels && xValuePosition == ValuePosition.INSIDE_SLICE + val drawYInside = drawValues && yValuePosition == ValuePosition.INSIDE_SLICE - val line1Radius = - if (chart.isDrawHoleEnabled) ((radius - (radius * holeRadiusPercent)) * valueLinePart1OffsetPercentage + (radius * holeRadiusPercent)) - else radius * valueLinePart1OffsetPercentage + if (drawXOutside || drawYOutside) { + val valueLineLength1 = dataSet.valueLinePart1Length + val valueLineLength2 = dataSet.valueLinePart2Length + val valueLinePart1OffsetPercentage = dataSet.valueLinePart1OffsetPercentage / 100f - val polyline2Width = if (dataSet.isValueLineVariableLength) labelRadius * valueLineLength2 * abs( - sin( - (transformedAngle * Utils.FDEG2RAD).toDouble() - ) - ).toFloat() - else labelRadius * valueLineLength2 + val pt2x: Float + val pt2y: Float + val labelPtx: Float + val labelPty: Float - val pt0x = line1Radius * sliceXBase + center.x - val pt0y = line1Radius * sliceYBase + center.y + val line1Radius = + if (chart.isDrawHoleEnabled) ((radius - (radius * holeRadiusPercent)) * valueLinePart1OffsetPercentage + (radius * holeRadiusPercent)) + else radius * valueLinePart1OffsetPercentage - val pt1x = labelRadius * (1 + valueLineLength1) * sliceXBase + center.x - val pt1y = labelRadius * (1 + valueLineLength1) * sliceYBase + center.y + val polyline2Width = if (dataSet.isValueLineVariableLength) labelRadius * valueLineLength2 * abs( + sin( + (transformedAngle * Utils.FDEG2RAD).toDouble() + ) + ).toFloat() + else labelRadius * valueLineLength2 - if (transformedAngle % 360.0 in 90.0..270.0) { - pt2x = pt1x - polyline2Width - pt2y = pt1y + val pt0x = line1Radius * sliceXBase + center.x + val pt0y = line1Radius * sliceYBase + center.y - paintValues.textAlign = Align.RIGHT + val pt1x = labelRadius * (1 + valueLineLength1) * sliceXBase + center.x + val pt1y = labelRadius * (1 + valueLineLength1) * sliceYBase + center.y - if (drawXOutside) paintEntryLabels.textAlign = Align.RIGHT + if (transformedAngle % 360.0 in 90.0..270.0) { + pt2x = pt1x - polyline2Width + pt2y = pt1y - labelPtx = pt2x - offset - labelPty = pt2y - } else { - pt2x = pt1x + polyline2Width - pt2y = pt1y - paintValues.textAlign = Align.LEFT + paintValues.textAlign = Align.RIGHT - if (drawXOutside) paintEntryLabels.textAlign = Align.LEFT + if (drawXOutside) paintEntryLabels.textAlign = Align.RIGHT - labelPtx = pt2x + offset - labelPty = pt2y - } + labelPtx = pt2x - offset + labelPty = pt2y + } else { + pt2x = pt1x + polyline2Width + pt2y = pt1y + paintValues.textAlign = Align.LEFT - var lineColor = ColorTemplate.COLOR_NONE + if (drawXOutside) paintEntryLabels.textAlign = Align.LEFT - if (isUseValueColorForLineEnabled) lineColor = dataSet.getColorByIndex(j) - else if (valueLineColor != ColorTemplate.COLOR_NONE) lineColor = valueLineColor + labelPtx = pt2x + offset + labelPty = pt2y + } - if (lineColor != ColorTemplate.COLOR_NONE) { - valueLinePaint.color = lineColor - drawLine(pt0x, pt0y, pt1x, pt1y, valueLinePaint) - drawLine(pt1x, pt1y, pt2x, pt2y, valueLinePaint) - } + var lineColor = ColorTemplate.COLOR_NONE - // draw everything, depending on settings - if (drawXOutside && drawYOutside) { - drawValue( - this, formatter, value, entry, 0, labelPtx, labelPty, dataSet.getValueTextColor(j) - ) + if (isUseValueColorForLineEnabled) lineColor = dataSet.getColorByIndex(j) + else if (valueLineColor != ColorTemplate.COLOR_NONE) lineColor = valueLineColor - if (j < data.entryCount && entryLabel != null) { - drawEntryLabel(this, entryLabel, labelPtx, labelPty + lineHeight) + if (lineColor != ColorTemplate.COLOR_NONE) { + valueLinePaint.color = lineColor + drawLine(pt0x, pt0y, pt1x, pt1y, valueLinePaint) + drawLine(pt1x, pt1y, pt2x, pt2y, valueLinePaint) } - } else if (drawXOutside) { - if (j < data.entryCount && entryLabel != null) { - drawEntryLabel(this, entryLabel, labelPtx, labelPty + lineHeight / 2f) + + // draw everything, depending on settings + if (drawXOutside && drawYOutside) { + drawValue( + this, formatter, value, entry, 0, labelPtx, labelPty, dataSet.getValueTextColor(j) + ) + + if (j < data.entryCount && entryLabel != null) { + drawEntryLabel(this, entryLabel, labelPtx, labelPty + lineHeight) + } + } else if (drawXOutside) { + if (j < data.entryCount && entryLabel != null) { + drawEntryLabel(this, entryLabel, labelPtx, labelPty + lineHeight / 2f) + } + } else if (drawYOutside) { + drawValue( + this, + formatter, + value, + entry, + 0, + labelPtx, + labelPty + lineHeight / 2f, + dataSet.getValueTextColor(j) + ) } - } else if (drawYOutside) { - drawValue( - this, - formatter, - value, - entry, - 0, - labelPtx, - labelPty + lineHeight / 2f, - dataSet.getValueTextColor(j) - ) } - } - if (drawXInside || drawYInside) { - // calculate the text position - val x = labelRadius * sliceXBase + center.x - val y = labelRadius * sliceYBase + center.y + if (drawXInside || drawYInside) { + // calculate the text position + val x = labelRadius * sliceXBase + center.x + val y = labelRadius * sliceYBase + center.y - paintValues.textAlign = Align.CENTER + paintValues.textAlign = Align.CENTER - // draw everything, depending on settings - if (drawXInside && drawYInside) { - drawValue(this, formatter, value, entry, 0, x, y, dataSet.getValueTextColor(j) - ) + // draw everything, depending on settings + if (drawXInside && drawYInside) { + drawValue(this, formatter, value, entry, 0, x, y, dataSet.getValueTextColor(j) + ) - if (j < data.entryCount && entryLabel != null) { - drawEntryLabel(this, entryLabel, x, y + lineHeight) - } - } else if (drawXInside) { - if (j < data.entryCount && entryLabel != null) { - drawEntryLabel(this, entryLabel, x, y + lineHeight / 2f) + if (j < data.entryCount && entryLabel != null) { + drawEntryLabel(this, entryLabel, x, y + lineHeight) + } + } else if (drawXInside) { + if (j < data.entryCount && entryLabel != null) { + drawEntryLabel(this, entryLabel, x, y + lineHeight / 2f) + } + } else if (drawYInside) { + drawValue(this, formatter, value, entry, 0, x, y + lineHeight / 2f, dataSet.getValueTextColor(j)) } - } else if (drawYInside) { - drawValue(this, formatter, value, entry, 0, x, y + lineHeight / 2f, dataSet.getValueTextColor(j)) } - } - if (entry.icon != null && dataSet.isDrawIcons) { - val icon = entry.icon + if (entry.icon != null && dataSet.isDrawIcons) { + val icon = entry.icon - val x = (labelRadius + iconsOffset.y) * sliceXBase + center.x - var y = (labelRadius + iconsOffset.y) * sliceYBase + center.y - y += iconsOffset.x + val x = (labelRadius + iconsOffset.y) * sliceXBase + center.x + var y = (labelRadius + iconsOffset.y) * sliceYBase + center.y + y += iconsOffset.x - icon?.let { - Utils.drawImage( - this, it, x.toInt(), y.toInt() - ) + icon?.let { + Utils.drawImage( + this, it, x.toInt(), y.toInt() + ) + } } } + xIndex++ } - xIndex++ - } - MPPointF.recycleInstance(iconsOffset) + MPPointF.recycleInstance(iconsOffset) + } } MPPointF.recycleInstance(center) } diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RadarChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RadarChartRenderer.kt index 7a2b290f4..c5f730b3b 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RadarChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/RadarChartRenderer.kt @@ -32,13 +32,14 @@ open class RadarChartRenderer( override fun initBuffers() = Unit override fun drawData(canvas: Canvas) { - val radarData = chart.data + chart.data?.let { radarData -> - val mostEntries = radarData!!.maxEntryCountSet.entryCount + val mostEntries = radarData.maxEntryCountSet?.entryCount ?: 0 - for (set in radarData.dataSets) { - if (set.isVisible) { - drawDataSet(canvas, set, mostEntries) + for (set in radarData.dataSets!!) { + if (set.isVisible) { + drawDataSet(canvas, set, mostEntries) + } } } } @@ -128,69 +129,72 @@ open class RadarChartRenderer( val yOffset = 5f.convertDpToPixel() for (i in 0.. - // apply the text-styling defined by the DataSet - applyValueTextStyle(dataSet) - - val iconsOffset = MPPointF.getInstance(dataSet.iconsOffset) - iconsOffset.x = iconsOffset.x.convertDpToPixel() - iconsOffset.y = iconsOffset.y.convertDpToPixel() - - for (j in 0.. + chart.data!!.getDataSetByIndex(i) + if (dataSet.entryCount == 0) { + continue + } + if (!shouldDrawValues(dataSet)) { + continue + } - Utils.getPosition( - center, - (entry.y - chart.yChartMin) * factor * phaseY, - sliceAngle * j * phaseX + chart.rotationAngle, - pOut - ) + // apply the text-styling defined by the DataSet + applyValueTextStyle(dataSet) - if (dataSet.isDrawValues) { - drawValue( - canvas, - dataSet.valueFormatter, - entry.y, - entry, - i, - pOut.x, - pOut.y - yOffset, - dataSet.getValueTextColor(j) - ) - } + val iconsOffset = MPPointF.getInstance(dataSet.iconsOffset) + iconsOffset.x = iconsOffset.x.convertDpToPixel() + iconsOffset.y = iconsOffset.y.convertDpToPixel() - if (entry.icon != null && dataSet.isDrawIcons) { - val icon = entry.icon + for (j in 0.. Utils.getPosition( center, - (entry.y) * factor * phaseY + iconsOffset.y, + (entry.y - chart.yChartMin) * factor * phaseY, sliceAngle * j * phaseX + chart.rotationAngle, - pIcon + pOut ) - pIcon.y += iconsOffset.x - - icon?.let { - Utils.drawImage( + if (dataSet.isDrawValues) { + drawValue( canvas, - it, - pIcon.x.toInt(), - pIcon.y.toInt() + dataSet.valueFormatter, + entry.y, + entry, + i, + pOut.x, + pOut.y - yOffset, + dataSet.getValueTextColor(j) ) } + + if (entry.icon != null && dataSet.isDrawIcons) { + val icon = entry.icon + + Utils.getPosition( + center, + (entry.y) * factor * phaseY + iconsOffset.y, + sliceAngle * j * phaseX + chart.rotationAngle, + pIcon + ) + + pIcon.y += iconsOffset.x + + icon?.let { + Utils.drawImage( + canvas, + it, + pIcon.x.toInt(), + pIcon.y.toInt() + ) + } + } } } - } - MPPointF.recycleInstance(iconsOffset) + MPPointF.recycleInstance(iconsOffset) + } } MPPointF.recycleInstance(center) @@ -218,7 +222,7 @@ open class RadarChartRenderer( webPaint.alpha = chart.webAlpha val xIncrements = 1 + chart.skipWebLineCount - val maxEntryCount = chart.data!!.maxEntryCountSet.entryCount + val maxEntryCount = chart.data!!.maxEntryCountSet?.entryCount ?: 0 val p = MPPointF.getInstance(0f, 0f) var i = 0 diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/ScatterChartRenderer.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/ScatterChartRenderer.kt index 8e05a2579..671ddbfe7 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/ScatterChartRenderer.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/renderer/ScatterChartRenderer.kt @@ -22,7 +22,7 @@ open class ScatterChartRenderer(@JvmField var dataProvider: ScatterDataProvider, val scatterData = dataProvider.scatterData scatterData?.let { - for (set in it.dataSets) { + for (set in it.dataSets!!) { if (set.isVisible) drawDataSet(canvas, set) } @@ -80,7 +80,7 @@ open class ScatterChartRenderer(@JvmField var dataProvider: ScatterDataProvider, if (isDrawingValuesAllowed(dataProvider)) { dataProvider.scatterData?.let { scatterData -> for (i in 0.. + for (i in 0.. + for (j in 0.. Date: Mon, 22 Dec 2025 06:24:16 +0100 Subject: [PATCH 2/2] Cosmetic in activities --- .../mikephil/charting/data/CombinedData.java | 32 +++++++++---------- .../charting/highlight/PieHighlighter.kt | 4 +-- .../charting/highlight/PieRadarHighlighter.kt | 10 +++--- .../charting/renderer/PieChartRenderer.kt | 2 +- .../mikephil/charting/test/ChartDataTest.kt | 4 +-- .../appdev/chartexample/PieChartActivity.kt | 4 +-- .../chartexample/PieChartRoundedActivity.kt | 4 +-- .../chartexample/PiePolylineChartActivity.kt | 2 +- 8 files changed, 30 insertions(+), 32 deletions(-) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java index b410bdb99..c059c47c0 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/data/CombinedData.java @@ -56,15 +56,15 @@ public void setData(BubbleData data) { @Override public void calcMinMax() { - if(dataSets == null){ - dataSets = new ArrayList<>(); + if(getDataSets() == null){ + setDataSets(new ArrayList<>()); } - dataSets.clear(); + getDataSets().clear(); - yMax = -Float.MAX_VALUE; - yMin = Float.MAX_VALUE; - xMax = -Float.MAX_VALUE; - xMin = Float.MAX_VALUE; + setYMax(-Float.MAX_VALUE); + setYMin(Float.MAX_VALUE); + setXMax(-Float.MAX_VALUE); + setXMin(Float.MAX_VALUE); mLeftAxisMax = -Float.MAX_VALUE; mLeftAxisMin = Float.MAX_VALUE; @@ -78,19 +78,19 @@ public void calcMinMax() { data.calcMinMax(); List> sets = data.getDataSets(); - dataSets.addAll(sets); + getDataSets().addAll(sets); - if (data.getYMax() > yMax) - yMax = data.getYMax(); + if (data.getYMax() > getYMax()) + setYMax(data.getYMax()); - if (data.getYMin() < yMin) - yMin = data.getYMin(); + if (data.getYMin() < getYMin()) + setYMin(data.getYMin()); - if (data.getXMax() > xMax) - xMax = data.getXMax(); + if (data.getXMax() > getXMax()) + setXMax(data.getXMax()); - if (data.getXMin() < xMin) - xMin = data.getXMin(); + if (data.getXMin() < getXMin()) + setXMin(data.getXMin()); for (IBarLineScatterCandleBubbleDataSet dataset : sets) { if (dataset.getAxisDependency() == YAxis.AxisDependency.LEFT) { diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieHighlighter.kt index 338df19c9..68e841920 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieHighlighter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieHighlighter.kt @@ -3,9 +3,9 @@ package com.github.mikephil.charting.highlight import com.github.mikephil.charting.charts.PieChart import com.github.mikephil.charting.data.Entry -class PieHighlighter(chart: PieChart) : PieRadarHighlighter(chart) { +open class PieHighlighter(chart: PieChart) : PieRadarHighlighter(chart) { override fun getClosestHighlight(index: Int, x: Float, y: Float): Highlight? { - val pieDataSet = chartPieRadar.data!!.dataSet + val pieDataSet = chartPieRadar.data!!.dataSets!![0] val entry: Entry? = pieDataSet.getEntryForIndex(index) diff --git a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.kt b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.kt index fc366d298..76f053243 100644 --- a/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.kt +++ b/MPChartLib/src/main/java/com/github/mikephil/charting/highlight/PieRadarHighlighter.kt @@ -1,6 +1,5 @@ package com.github.mikephil.charting.highlight -import com.github.mikephil.charting.charts.PieChart import com.github.mikephil.charting.charts.PieRadarChartBase abstract class PieRadarHighlighter>(protected var chartPieRadar: T) : IHighlighter { @@ -20,14 +19,13 @@ abstract class PieRadarHighlighter>(protected var chart } else { var angle = chartPieRadar.getAngleForPoint(x, y) - if (chartPieRadar is PieChart) { - angle /= chartPieRadar.animator.phaseY - } + angle /= chartPieRadar.animator.phaseY val index = chartPieRadar.getIndexForAngle(angle) - // check if the index could be found - return if (index < 0 || index >= chartPieRadar.getData()!!.getMaxEntryCountSet().entryCount) { + val localData = chartPieRadar.data + val maxCount = localData?.maxEntryCountSet?.entryCount ?: 0 + return if (index !in 0.. 0) drawDataSet(set) + if (set.isVisible && set.entryCount > 0) drawDataSet(set) } } diff --git a/MPChartLib/src/test/kotlin/com/github/mikephil/charting/test/ChartDataTest.kt b/MPChartLib/src/test/kotlin/com/github/mikephil/charting/test/ChartDataTest.kt index 2658ee6a1..2686be28c 100644 --- a/MPChartLib/src/test/kotlin/com/github/mikephil/charting/test/ChartDataTest.kt +++ b/MPChartLib/src/test/kotlin/com/github/mikephil/charting/test/ChartDataTest.kt @@ -39,13 +39,13 @@ class ChartDataTest { Assert.assertEquals(-2f, data.yMin, 0.01f) Assert.assertEquals(50f, data.yMax, 0.01f) - Assert.assertEquals(3, data.maxEntryCountSet.entryCount) + Assert.assertEquals(3, data.maxEntryCountSet?.entryCount) // now add and remove values data.addEntry(Entry(-10f, -10f), 0) Assert.assertEquals(set1, data.maxEntryCountSet) - Assert.assertEquals(4, data.maxEntryCountSet.entryCount) + Assert.assertEquals(4, data.maxEntryCountSet?.entryCount) Assert.assertEquals(-10f, data.getYMin(YAxis.AxisDependency.LEFT), 0.01f) Assert.assertEquals(50f, data.getYMax(YAxis.AxisDependency.LEFT), 0.01f) diff --git a/app/src/main/kotlin/info/appdev/chartexample/PieChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/PieChartActivity.kt index 08396be2e..683772ac9 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/PieChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/PieChartActivity.kt @@ -168,14 +168,14 @@ class PieChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelect R.id.actionToggleValues -> { binding.chart1.data?.dataSets?.forEach { - it?.isDrawValues = !it.isDrawValues + it.isDrawValues = !it.isDrawValues } binding.chart1.invalidate() } R.id.actionToggleIcons -> { binding.chart1.data?.dataSets?.forEach { set -> - set?.isDrawIcons = !set.isDrawIcons + set.isDrawIcons = !set.isDrawIcons } binding.chart1.invalidate() } diff --git a/app/src/main/kotlin/info/appdev/chartexample/PieChartRoundedActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/PieChartRoundedActivity.kt index 687205f85..8a1b856c7 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/PieChartRoundedActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/PieChartRoundedActivity.kt @@ -172,14 +172,14 @@ class PieChartRoundedActivity : DemoBase(), OnSeekBarChangeListener, OnChartValu R.id.actionToggleValues -> { binding.chart1.data?.dataSets?.forEach { - it?.isDrawValues = !it.isDrawValues + it.isDrawValues = !it.isDrawValues } binding.chart1.invalidate() } R.id.actionToggleIcons -> { binding.chart1.data?.dataSets?.forEach { set -> - set?.isDrawIcons = !set.isDrawIcons + set.isDrawIcons = !set.isDrawIcons } binding.chart1.invalidate() } diff --git a/app/src/main/kotlin/info/appdev/chartexample/PiePolylineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/PiePolylineChartActivity.kt index 300619e47..9094ff3d5 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/PiePolylineChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/PiePolylineChartActivity.kt @@ -161,7 +161,7 @@ class PiePolylineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartVal R.id.actionToggleValues -> { binding.chart1.data?.dataSets?.forEach { - it?.isDrawValues = !it.isDrawValues + it.isDrawValues = !it.isDrawValues } binding.chart1.invalidate() }