diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index c5864c808..959168d23 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -46,6 +46,9 @@ android {
testOptions {
animationsDisabled = true
}
+ lint {
+ lintConfig = file("lint.xml")
+ }
packaging {
jniLibs {
// androidx.graphics:graphics-path ships a .so that llvm-strip cannot process;
@@ -56,6 +59,8 @@ android {
}
dependencies {
+ lintChecks(project(":lint"))
+
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.13.0")
implementation(project(":chartLib"))
diff --git a/app/lint.xml b/app/lint.xml
new file mode 100644
index 000000000..bbd42cf46
--- /dev/null
+++ b/app/lint.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/kotlin/info/appdev/chartexample/BubbleChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/BubbleChartActivity.kt
index 7207e7744..8b8e147d0 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/BubbleChartActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/BubbleChartActivity.kt
@@ -19,6 +19,7 @@ import info.appdev.charting.components.Legend
import info.appdev.charting.components.XAxis
import info.appdev.charting.data.BubbleData
import info.appdev.charting.data.BubbleDataSet
+import info.appdev.charting.data.BubbleEntry
import info.appdev.charting.data.BubbleEntryFloat
import info.appdev.charting.data.EntryFloat
import info.appdev.charting.highlight.Highlight
diff --git a/build.gradle.kts b/build.gradle.kts
index 4fba12077..1ecd19fad 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,7 +4,10 @@ buildscript {
mavenCentral()
}
dependencies {
- classpath("com.android.tools.build:gradle:9.1.0")
+ // Lint API version tracks AGP: AGP 9.1.1 → Lint 32.1.1
+ // in module lint:
+ // val lintVersion = "32.1.1"
+ classpath("com.android.tools.build:gradle:9.1.1")
classpath("com.github.dcendents:android-maven-gradle-plugin:2.1")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.20")
}
diff --git a/chartLib/build.gradle.kts b/chartLib/build.gradle.kts
index d8464bb59..fea1cd0a3 100644
--- a/chartLib/build.gradle.kts
+++ b/chartLib/build.gradle.kts
@@ -50,6 +50,9 @@ android {
}
dependencies {
+ lintChecks(project(":lint")) // applies locally
+ lintPublish(project(path = ":lint", configuration = "lintJar")) // embeds in published AAR
+
implementation("androidx.annotation:annotation:1.10.0")
implementation("androidx.core:core:1.18.0")
implementation("androidx.activity:activity-ktx:1.13.0")
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt
new file mode 100644
index 000000000..eedc9b38d
--- /dev/null
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt
@@ -0,0 +1,45 @@
+package info.appdev.charting.data
+
+import android.graphics.drawable.Drawable
+
+
+@Deprecated(
+ message = "The replacement is BarEntryFloat, or use BarEntryDouble for higher precision. BarEntry is retained for backward compatibility but will be removed in a future version.",
+ replaceWith = ReplaceWith("BarEntryFloat", "info.appdev.charting.data.BarEntryFloat")
+)
+open class BarEntry : BarEntryFloat {
+
+ /**
+ * Constructor for normal bars (not stacked).
+ */
+ constructor(x: Float, y: Float) : super(x, y)
+
+ /**
+ * Constructor for normal bars (not stacked).
+ *
+ * @param x
+ * @param y
+ * @param data - Spot for additional data this Entry represents.
+ */
+ constructor(x: Float, y: Float, data: Any?) : super(x, y, data)
+
+ /**
+ * Constructor for normal bars (not stacked).
+ *
+ * @param x
+ * @param y
+ * @param icon - icon image
+ */
+ constructor(x: Float, y: Float, icon: Drawable?) : super(x, y, icon)
+
+ /**
+ * Constructor for normal bars (not stacked).
+ *
+ * @param x
+ * @param y
+ * @param icon - icon image
+ * @param data - Spot for additional data this Entry represents.
+ */
+ constructor(x: Float, y: Float, icon: Drawable?, data: Any?) : super(x, y, icon, data)
+
+}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntry.kt
new file mode 100644
index 000000000..af0986e7e
--- /dev/null
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntry.kt
@@ -0,0 +1,46 @@
+package info.appdev.charting.data
+
+import android.annotation.SuppressLint
+import android.graphics.drawable.Drawable
+
+@Deprecated(
+ message = "The replacement is BubbleEntryFloat, or use BubbleEntryDouble for higher precision. BubbleEntry is retained for backward compatibility but will be removed in a future version.",
+ replaceWith = ReplaceWith("BubbleEntryFloat", "info.appdev.charting.data.BubbleEntryFloat")
+)
+class BubbleEntry : BubbleEntryFloat {
+
+ /**
+ * Constructor.
+ *
+ * @param x The value on the x-axis.
+ * @param y The value on the y-axis.
+ * @param size The size of the bubble.
+ */
+ constructor(x: Float, y: Float, size: Float) : super(x, y, size) {
+ this.size = size
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param x The value on the x-axis.
+ * @param y The value on the y-axis.
+ * @param size The size of the bubble.
+ * @param data Spot for additional data this Entry represents.
+ */
+ constructor(x: Float, y: Float, size: Float, data: Any?) : super(x, y, size, data) {
+ this.size = size
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param x The value on the x-axis.
+ * @param y The value on the y-axis.
+ * @param size The size of the bubble.
+ * @param icon Icon image
+ */
+ constructor(x: Float, y: Float, size: Float, icon: Drawable?) : super(x, y, size, icon) {
+ this.size = size
+ }
+}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntry.kt
new file mode 100644
index 000000000..e7d9811bf
--- /dev/null
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntry.kt
@@ -0,0 +1,82 @@
+package info.appdev.charting.data
+
+import android.graphics.drawable.Drawable
+
+@Deprecated(
+ message = "The replacement is CandleEntryFloat, or use CandleEntryDouble for higher precision. CandleEntry is retained for backward compatibility but will be removed in a future version.",
+ replaceWith = ReplaceWith("CandleEntryFloat", "info.appdev.charting.data.CandleEntryFloat")
+)
+class CandleEntry : CandleEntryFloat {
+
+ /**
+ * Constructor.
+ *
+ * @param x The value on the x-axis
+ * @param shadowH The (shadow) high value
+ * @param shadowL The (shadow) low value
+ * @param open The open value
+ * @param close The close value
+ */
+ constructor(x: Float, shadowH: Float, shadowL: Float, open: Float, close: Float) : super(x, shadowH, shadowL, open, close) {
+ this.high = shadowH
+ this.low = shadowL
+ this.open = open
+ this.close = close
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param x The value on the x-axis
+ * @param shadowH The (shadow) high value
+ * @param shadowL The (shadow) low value
+ * @param data Spot for additional data this Entry represents
+ */
+ constructor(
+ x: Float, shadowH: Float, shadowL: Float, open: Float, close: Float,
+ data: Any?
+ ) : super(x, shadowH, shadowL, open, close) {
+ this.high = shadowH
+ this.low = shadowL
+ this.open = open
+ this.close = close
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param x The value on the x-axis
+ * @param shadowH The (shadow) high value
+ * @param shadowL The (shadow) low value
+ * @param icon Icon image
+ */
+ constructor(
+ x: Float, shadowH: Float, shadowL: Float, open: Float, close: Float,
+ icon: Drawable?
+ ) : super(x, shadowH, shadowL, open, close) {
+ this.high = shadowH
+ this.low = shadowL
+ this.open = open
+ this.close = close
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param x The value on the x-axis
+ * @param shadowH The (shadow) high value
+ * @param shadowL The (shadow) low value
+ * @param icon Icon image
+ * @param data Spot for additional data this Entry represents
+ */
+ constructor(
+ x: Float, shadowH: Float, shadowL: Float, open: Float, close: Float,
+ icon: Drawable?, data: Any?
+ ) : super(x, shadowH, shadowL, open, close) {
+ this.high = shadowH
+ this.low = shadowL
+ this.open = open
+ this.close = close
+ }
+
+}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt
index 84ee4ea1c..dc247286d 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.data
+import android.annotation.SuppressLint
import android.graphics.Typeface
import info.appdev.charting.components.YAxis.AxisDependency
import info.appdev.charting.formatter.IValueFormatter
@@ -440,6 +441,7 @@ abstract class ChartData> : Serializable {
* specified index. Returns true if an Entry was removed, false if no Entry
* was found that meets the specified requirements.
*/
+ @SuppressLint("RawTypeDataSet")
open fun removeEntry(xValue: Float, dataSetIndex: Int): Boolean {
if (dataSetIndex >= dataSets.size) {
return false
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/DataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/DataSet.kt
index 33b87d315..4073809f3 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/data/DataSet.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/DataSet.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.data
+import android.annotation.SuppressLint
import timber.log.Timber
import java.io.Serializable
import kotlin.math.abs
@@ -130,7 +131,7 @@ abstract class DataSet>(
*/
abstract fun copy(): DataSet?
- protected fun copy(dataSet: DataSet<*>) {
+ protected fun copy(@SuppressLint("RawTypeDataSet") dataSet: DataSet<*>) {
super.copy(dataSet)
}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt
new file mode 100644
index 000000000..9bc6dd11b
--- /dev/null
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt
@@ -0,0 +1,63 @@
+package info.appdev.charting.data
+
+import android.R.attr.data
+import android.R.attr.x
+import android.R.attr.y
+import android.graphics.drawable.Drawable
+import android.os.Build
+import android.os.Parcel
+import android.os.ParcelFormatException
+import android.os.Parcelable
+import info.appdev.charting.utils.Utils
+import java.io.Serializable
+import kotlin.math.abs
+
+/**
+ * Class representing one entry in the chart. Might contain multiple values.
+ * Might only contain a single value depending on the used constructor.
+ */
+@Deprecated(
+ message = "The replacement is EntryFloat, or use EntryDouble for higher precision. Entry is retained for backward compatibility but will be removed in a future version.",
+ replaceWith = ReplaceWith("EntryFloat", "info.appdev.charting.data.EntryFloat")
+)
+class Entry : EntryFloat {
+
+ constructor()
+
+ /**
+ * An EntryFloat represents one single entry in the chart.
+ *
+ * @param x the x value
+ * @param y the y value (the actual value of the entry)
+ */
+ constructor(x: Float, y: Float) : super(x = x, y = y)
+
+ /**
+ * An EntryFloat represents one single entry in the chart.
+ *
+ * @param x the x value
+ * @param y the y value (the actual value of the entry)
+ * @param data Spot for additional data this Entry represents.
+ */
+ constructor(x: Float, y: Float, data: Any?) : super(x = x, y = y, data = data)
+
+ /**
+ * An EntryFloat represents one single entry in the chart.
+ *
+ * @param x the x value
+ * @param y the y value (the actual value of the entry)
+ * @param icon icon image
+ */
+ constructor(x: Float, y: Float, icon: Drawable?) : super(x = x, y = y, icon = icon)
+
+ /**
+ * An EntryFloat represents one single entry in the chart.
+ *
+ * @param x the x value
+ * @param y the y value (the actual value of the entry)
+ * @param icon icon image
+ * @param data Spot for additional data this EntryFloat represents.
+ */
+ constructor(x: Float, y: Float, icon: Drawable?, data: Any?) : super(x = x, y = y, icon = icon, data = data)
+
+}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt
index 635c017d5..3c369112d 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.data
+import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.graphics.DashPathEffect
@@ -84,7 +85,7 @@ open class LineDataSet>(yVals: MutableList = mutableList
return copied as DataSet
}
- protected fun copy(lineDataSet: LineDataSet<*>) {
+ protected fun copy(@SuppressLint("RawTypeDataSet") lineDataSet: LineDataSet<*>) {
super.copy((lineDataSet as BaseDataSet<*>?)!!)
lineDataSet.circleColors = this.circleColors
lineDataSet.mCircleHoleColor = mCircleHoleColor
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntry.kt
new file mode 100644
index 000000000..dafa6e378
--- /dev/null
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntry.kt
@@ -0,0 +1,35 @@
+package info.appdev.charting.data
+
+import android.graphics.drawable.Drawable
+
+@Deprecated(
+ message = "The replacement is PieEntryFloat, or use PieEntryDouble for higher precision. PieEntry is retained for backward compatibility but will be removed in a future version.",
+ replaceWith = ReplaceWith("PieEntryFloat", "info.appdev.charting.data.PieEntryFloat")
+)
+class PieEntry : PieEntryFloat {
+
+ constructor(value: Float) : super(0f)
+
+ constructor(value: Float, data: Any?) : super(value, data)
+
+ constructor(value: Float, icon: Drawable?) : super(value, icon)
+
+ constructor(value: Float, icon: Drawable?, data: Any?) : super(value, icon, data)
+
+ constructor(value: Float, label: String?) : super(value) {
+ this.label = label
+ }
+
+ constructor(value: Float, label: String?, data: Any?) : super(value, data) {
+ this.label = label
+ }
+
+ constructor(value: Float, label: String?, icon: Drawable?) : super(value, icon) {
+ this.label = label
+ }
+
+ constructor(value: Float, label: String?, icon: Drawable?, data: Any?) : super(value, icon, data) {
+ this.label = label
+ }
+
+}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntryDouble.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntryDouble.kt
index a27a56a22..e69de29bb 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntryDouble.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntryDouble.kt
@@ -1,59 +0,0 @@
-package info.appdev.charting.data
-
-import android.annotation.SuppressLint
-import android.graphics.drawable.Drawable
-import timber.log.Timber
-
-/**
- * High-precision pie entry that stores the value as Double, extending [PieEntryFloat]
- * so it works seamlessly in the existing pie chart rendering pipeline.
- * Use [valueDouble] for full-precision access.
- * Pie entries have no meaningful x-axis value.
- */
-@SuppressLint("ParcelCreator")
-open class PieEntryDouble : PieEntryFloat {
-
- var valueDouble: Double = 0.0
-
- override var y: Float
- get() = valueDouble.toFloat()
- set(value) { valueDouble = value.toDouble() }
-
- @get:Deprecated("")
- @set:Deprecated("")
- @Suppress("DEPRECATION")
- override var x: Float
- get() {
- Timber.i("Pie entries do not have x values")
- return super.x
- }
- set(x) {
- super.x = x
- Timber.i("Pie entries do not have x values")
- }
-
- constructor(value: Double) : super(value.toFloat()) { valueDouble = value }
-
- constructor(value: Double, data: Any?) : super(value.toFloat(), data) { valueDouble = value }
-
- constructor(value: Double, icon: Drawable?) : super(value.toFloat(), icon) { valueDouble = value }
-
- constructor(value: Double, icon: Drawable?, data: Any?) : super(value.toFloat(), icon, data) { valueDouble = value }
-
- constructor(value: Double, label: String?) : super(value.toFloat(), label) { valueDouble = value }
-
- constructor(value: Double, label: String?, data: Any?) : super(value.toFloat(), label, data) { valueDouble = value }
-
- constructor(value: Double, label: String?, icon: Drawable?) : super(value.toFloat(), label, icon) { valueDouble = value }
-
- constructor(value: Double, label: String?, icon: Drawable?, data: Any?) : super(value.toFloat(), label, icon, data) { valueDouble = value }
-
- /** Full-precision value (same as [valueDouble]). */
- val valuePrecise: Double get() = valueDouble
-
- override fun copy(): PieEntryDouble = PieEntryDouble(valueDouble, label, data)
-
- override fun toString(): String = "PieEntryDouble valueDouble=$valueDouble label=$label"
-}
-
-
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntry.kt
new file mode 100644
index 000000000..49c5c3dd6
--- /dev/null
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntry.kt
@@ -0,0 +1,12 @@
+package info.appdev.charting.data
+
+@Deprecated(
+ message = "The replacement is RadarEntryFloat, or use RadarEntryDouble for higher precision. RadarEntry is retained for backward compatibility but will be removed in a future version.",
+ replaceWith = ReplaceWith("RadarEntryFloat", "info.appdev.charting.data.RadarEntryFloat")
+)
+class RadarEntry : RadarEntryFloat {
+ constructor(value: Float) : super(value)
+
+ constructor(value: Float, data: Any?) : super(value, data)
+
+}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntryDouble.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntryDouble.kt
index 4fad54682..03080dfee 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntryDouble.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntryDouble.kt
@@ -8,6 +8,7 @@ import android.annotation.SuppressLint
* Use [valueDouble] for full-precision access.
* Radar entries have no meaningful x-axis value.
*/
+@Suppress("DEPRECATION")
@SuppressLint("ParcelCreator")
open class RadarEntryDouble : RadarEntryFloat {
@@ -19,7 +20,6 @@ open class RadarEntryDouble : RadarEntryFloat {
@get:Deprecated("")
@set:Deprecated("")
- @Suppress("DEPRECATION")
override var x: Float
get() = super.x
set(x) { super.x = x }
@@ -36,4 +36,3 @@ open class RadarEntryDouble : RadarEntryFloat {
override fun toString(): String = "RadarEntryDouble valueDouble=$valueDouble"
}
-
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt b/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt
index ddd760648..32be88ca6 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.highlight
+import android.annotation.SuppressLint
import info.appdev.charting.components.YAxis.AxisDependency
import info.appdev.charting.data.ChartData
import info.appdev.charting.data.DataSet
@@ -115,7 +116,7 @@ open class ChartHighlighter>(prote
*/
@Suppress("SameParameterValue")
protected open fun buildHighlights(
- set: IDataSet<*>,
+ @SuppressLint("RawTypeDataSet") set: IDataSet<*>,
dataSetIndex: Int,
xVal: Float,
rounding: DataSet.Rounding?
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/highlight/HorizontalBarHighlighter.kt b/chartLib/src/main/kotlin/info/appdev/charting/highlight/HorizontalBarHighlighter.kt
index 8e61df89d..48cf47467 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/HorizontalBarHighlighter.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/HorizontalBarHighlighter.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.highlight
+import android.annotation.SuppressLint
import info.appdev.charting.data.DataSet
import info.appdev.charting.data.EntryFloat
import info.appdev.charting.interfaces.dataprovider.BarDataProvider
@@ -32,7 +33,7 @@ class HorizontalBarHighlighter(dataProvider: BarDataProvider) : BarHighlighter(d
return null
}
- override fun buildHighlights(set: IDataSet<*>, dataSetIndex: Int, xVal: Float, rounding: DataSet.Rounding?): MutableList {
+ override fun buildHighlights(@SuppressLint("RawTypeDataSet") set: IDataSet<*>, dataSetIndex: Int, xVal: Float, rounding: DataSet.Rounding?): MutableList {
val highlights = ArrayList()
var entries = set.getEntriesForXValue(xVal)?.map { it as EntryFloat }?.toMutableList()
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/listener/OnDrawListener.kt b/chartLib/src/main/kotlin/info/appdev/charting/listener/OnDrawListener.kt
index 45b9af8ac..04690245a 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/listener/OnDrawListener.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/listener/OnDrawListener.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.listener
+import android.annotation.SuppressLint
import info.appdev.charting.data.DataSet
import info.appdev.charting.data.EntryFloat
@@ -25,5 +26,5 @@ interface OnDrawListener {
*
* @param dataSet the last drawn DataSet
*/
- fun onDrawFinished(dataSet: DataSet<*>)
+ fun onDrawFinished(@SuppressLint("RawTypeDataSet") dataSet: DataSet<*>)
}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/BarLineScatterCandleBubbleRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/BarLineScatterCandleBubbleRenderer.kt
index 8b256869f..6775ee280 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/BarLineScatterCandleBubbleRenderer.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/BarLineScatterCandleBubbleRenderer.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.renderer
+import android.annotation.SuppressLint
import info.appdev.charting.animation.ChartAnimator
import info.appdev.charting.data.DataSet
import info.appdev.charting.data.BaseEntry
@@ -22,7 +23,7 @@ abstract class BarLineScatterCandleBubbleRenderer(
/**
* Returns true if the DataSet values should be drawn, false if not.
*/
- protected fun shouldDrawValues(set: IDataSet<*>): Boolean {
+ protected fun shouldDrawValues(@SuppressLint("RawTypeDataSet") set: IDataSet<*>): Boolean {
return set.isVisible && (set.isDrawValues || set.isDrawIcons)
}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/DataRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/DataRenderer.kt
index ba5e970ad..f4ed9d404 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/DataRenderer.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/DataRenderer.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.renderer
+import android.annotation.SuppressLint
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
@@ -70,7 +71,7 @@ abstract class DataRenderer(
* Applies the required styling (provided by the DataSet) to the value-paint
* object.
*/
- protected fun applyValueTextStyle(set: IDataSet<*>) {
+ protected fun applyValueTextStyle(@SuppressLint("RawTypeDataSet") set: IDataSet<*>) {
paintValues.typeface = set.valueTypeface
paintValues.textSize = set.valueTextSize
}
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineChartRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineChartRenderer.kt
index 14a528102..0b0db2432 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineChartRenderer.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineChartRenderer.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.renderer
+import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.graphics.Bitmap.createBitmap
import android.graphics.Canvas
@@ -8,7 +9,6 @@ import android.graphics.Paint
import android.graphics.Path
import info.appdev.charting.animation.ChartAnimator
import info.appdev.charting.data.BaseEntry
-import info.appdev.charting.data.EntryFloat
import info.appdev.charting.data.LineDataSet
import info.appdev.charting.highlight.Highlight
import info.appdev.charting.interfaces.dataprovider.LineDataProvider
@@ -25,6 +25,7 @@ import java.lang.ref.WeakReference
import kotlin.math.max
import kotlin.math.min
+@SuppressLint("RawTypeDataSet")
open class LineChartRenderer(
var dataProvider: LineDataProvider,
animator: ChartAnimator,
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineScatterCandleRadarRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineScatterCandleRadarRenderer.kt
index 9cfe7bc7b..13f8ad48e 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineScatterCandleRadarRenderer.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineScatterCandleRadarRenderer.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.renderer
+import android.annotation.SuppressLint
import android.graphics.Canvas
import android.graphics.Path
import info.appdev.charting.animation.ChartAnimator
@@ -23,7 +24,7 @@ abstract class LineScatterCandleRadarRenderer(
* @param y y-position of the highlight line intersection
* @param set the currently drawn dataset
*/
- protected fun drawHighlightLines(canvas: Canvas, x: Float, y: Float, set: ILineScatterCandleRadarDataSet<*>) {
+ protected fun drawHighlightLines(canvas: Canvas, x: Float, y: Float, @SuppressLint("RawTypeDataSet") set: ILineScatterCandleRadarDataSet<*>) {
// set color and stroke-width
paintHighlight.color = set.highLightColor
diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/Transformer.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/Transformer.kt
index e26be48d7..9d5831ed2 100644
--- a/chartLib/src/main/kotlin/info/appdev/charting/utils/Transformer.kt
+++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/Transformer.kt
@@ -1,5 +1,6 @@
package info.appdev.charting.utils
+import android.annotation.SuppressLint
import android.graphics.Matrix
import android.graphics.Path
import android.graphics.RectF
@@ -142,7 +143,7 @@ open class Transformer(protected var viewPortHandler: ViewPortHandler) {
* y values transformed with all matrices for the LINECHART.
*/
fun generateTransformedValuesLine(
- data: ILineDataSet<*>,
+ @SuppressLint("RawTypeDataSet") data: ILineDataSet<*>,
phaseX: Float, phaseY: Float,
min: Int, max: Int
): FloatArray {
diff --git a/lint/build.gradle.kts b/lint/build.gradle.kts
new file mode 100644
index 000000000..9d1f47a35
--- /dev/null
+++ b/lint/build.gradle.kts
@@ -0,0 +1,46 @@
+import org.jetbrains.kotlin.gradle.dsl.JvmTarget
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ kotlin("jvm")
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+}
+
+tasks.withType().configureEach {
+ compilerOptions {
+ jvmTarget.set(JvmTarget.JVM_17)
+ }
+}
+
+// Lint API version tracks AGP: AGP 9.1.1 → Lint 32.1.1
+val lintVersion = "32.1.1"
+
+dependencies {
+ compileOnly("com.android.tools.lint:lint-api:$lintVersion")
+ compileOnly("com.android.tools.lint:lint-checks:$lintVersion")
+
+ testImplementation("com.android.tools.lint:lint:$lintVersion")
+ testImplementation("com.android.tools.lint:lint-tests:$lintVersion")
+ testImplementation("junit:junit:4.13.2")
+}
+
+// The manifest attribute tells the Android Lint tooling which IssueRegistry to load.
+tasks.jar {
+ manifest {
+ attributes["Lint-Registry-v2"] = "info.appdev.charting.lint.LintRegistry"
+ }
+}
+
+// Expose a single-artifact configuration so library modules can use lintPublish.
+// lintPublish requires exactly one JAR — the default project() dependency
+// resolves to multiple artifacts and causes "Found more than one jar" errors.
+configurations {
+ create("lintJar")
+}
+artifacts {
+ add("lintJar", tasks.named("jar"))
+}
diff --git a/lint/src/main/kotlin/info/appdev/charting/lint/EntryUsageDetector.kt b/lint/src/main/kotlin/info/appdev/charting/lint/EntryUsageDetector.kt
new file mode 100644
index 000000000..15c63fac1
--- /dev/null
+++ b/lint/src/main/kotlin/info/appdev/charting/lint/EntryUsageDetector.kt
@@ -0,0 +1,129 @@
+package info.appdev.charting.lint
+
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.LintFix
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiClass
+import com.intellij.psi.PsiElement
+import org.jetbrains.uast.UReferenceExpression
+import org.jetbrains.uast.UImportStatement
+import org.jetbrains.uast.getParentOfType
+
+/**
+ * Lint detector that flags all usages of deprecated legacy entry classes and offers
+ * auto-fixes to replace them with either the `*Float` or `*Double` equivalent.
+ *
+ * Deprecated → Float replacement → Double replacement:
+ * Entry → EntryFloat → EntryDouble
+ * BarEntry → BarEntryFloat → BarEntryDouble
+ * BubbleEntry → BubbleEntryFloat → BubbleEntryDouble
+ * CandleEntry → CandleEntryFloat → CandleEntryDouble
+ * PieEntry → PieEntryFloat → PieEntryDouble
+ * RadarEntry → RadarEntryFloat → RadarEntryDouble
+ */
+class EntryUsageDetector : Detector(), SourceCodeScanner {
+
+ companion object {
+ private const val PKG = "info.appdev.charting.data"
+
+ /** Deprecated FQN → Float replacement FQN */
+ private val FLOAT_REPLACEMENTS: Map = mapOf(
+ "$PKG.Entry" to "$PKG.EntryFloat",
+ "$PKG.BarEntry" to "$PKG.BarEntryFloat",
+ "$PKG.BubbleEntry" to "$PKG.BubbleEntryFloat",
+ "$PKG.CandleEntry" to "$PKG.CandleEntryFloat",
+ "$PKG.PieEntry" to "$PKG.PieEntryFloat",
+ "$PKG.RadarEntry" to "$PKG.RadarEntryFloat",
+ )
+
+ /** Deprecated FQN → Double replacement FQN */
+ private val DOUBLE_REPLACEMENTS: Map = mapOf(
+ "$PKG.Entry" to "$PKG.EntryDouble",
+ "$PKG.BarEntry" to "$PKG.BarEntryDouble",
+ "$PKG.BubbleEntry" to "$PKG.BubbleEntryDouble",
+ "$PKG.CandleEntry" to "$PKG.CandleEntryDouble",
+ "$PKG.PieEntry" to "$PKG.PieEntryDouble",
+ "$PKG.RadarEntry" to "$PKG.RadarEntryDouble",
+ )
+
+ @JvmField
+ val ISSUE: Issue = Issue.create(
+ id = "LegacyEntryUsage",
+ briefDescription = "Replace deprecated legacy entry class with its `*Float` or `*Double` equivalent",
+ explanation = """
+ Several entry classes (`Entry`, `BarEntry`, `BubbleEntry`, `CandleEntry`, \
+ `PieEntry`, `RadarEntry`) are deprecated and will be removed in a future version. \
+ Replace them with their `*Float` equivalents for identical precision, or \
+ `*Double` equivalents for higher precision.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 6,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ EntryUsageDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+
+ private val DEPRECATED_SIMPLE_NAMES: Set =
+ FLOAT_REPLACEMENTS.keys.map { it.substringAfterLast('.') }.toSet()
+ }
+
+ override fun getApplicableReferenceNames(): List =
+ DEPRECATED_SIMPLE_NAMES.toList()
+
+ override fun visitReference(
+ context: JavaContext,
+ reference: UReferenceExpression,
+ referenced: PsiElement
+ ) {
+ if (referenced !is PsiClass) return
+ val deprecatedFqn = referenced.qualifiedName ?: return
+ val floatFqn = FLOAT_REPLACEMENTS[deprecatedFqn] ?: return
+ val doubleFqn = DOUBLE_REPLACEMENTS[deprecatedFqn] ?: return
+
+ val deprecatedSimple = deprecatedFqn.substringAfterLast('.')
+ val floatSimple = floatFqn.substringAfterLast('.')
+ val doubleSimple = doubleFqn.substringAfterLast('.')
+
+ val importNode = reference.getParentOfType()
+ val combinedFix: LintFix = if (importNode != null) {
+ // For import statements replace the full FQN
+ fix().alternatives(
+ fix().replace().name("Replace with $floatSimple")
+ .text(deprecatedFqn).with(floatFqn).autoFix().build(),
+ fix().replace().name("Replace with $doubleSimple (high precision)")
+ .text(deprecatedFqn).with(doubleFqn).autoFix().build()
+ )
+ } else {
+ // For type refs / constructor calls replace only the simple name
+ fix().alternatives(
+ fix().replace().name("Replace with $floatSimple")
+ .text(deprecatedSimple).with(floatSimple).autoFix().build(),
+ fix().replace().name("Replace with $doubleSimple (high precision)")
+ .text(deprecatedSimple).with(doubleSimple).autoFix().build()
+ )
+ }
+
+ val location = if (importNode != null)
+ context.getLocation(importNode)
+ else
+ context.getLocation(reference)
+
+ val element = importNode ?: reference
+
+ context.report(
+ ISSUE,
+ element,
+ location,
+ "Replace deprecated `$deprecatedSimple` — use `$floatSimple` (same precision) or `$doubleSimple` (higher precision)",
+ combinedFix
+ )
+ }
+}
diff --git a/lint/src/main/kotlin/info/appdev/charting/lint/LintRegistry.kt b/lint/src/main/kotlin/info/appdev/charting/lint/LintRegistry.kt
new file mode 100644
index 000000000..7597608d7
--- /dev/null
+++ b/lint/src/main/kotlin/info/appdev/charting/lint/LintRegistry.kt
@@ -0,0 +1,26 @@
+package info.appdev.charting.lint
+
+import com.android.tools.lint.client.api.IssueRegistry
+import com.android.tools.lint.client.api.Vendor
+import com.android.tools.lint.detector.api.CURRENT_API
+import com.android.tools.lint.detector.api.Issue
+
+class LintRegistry : IssueRegistry() {
+
+ override val issues: List = listOf(
+ EntryUsageDetector.ISSUE,
+ RawTypeDataSetDetector.ISSUE
+ )
+
+ /** Must match the Lint API version used at compile time. */
+ override val api: Int = CURRENT_API
+
+ /** Minimum Lint API version this registry works with. */
+ override val minApi: Int = 14
+
+ override val vendor: Vendor = Vendor(
+ vendorName = "AndroidChart",
+ feedbackUrl = "https://github.com/AppDevNext/AndroidChart/issues"
+ )
+}
+
diff --git a/lint/src/main/kotlin/info/appdev/charting/lint/RawTypeDataSetDetector.kt b/lint/src/main/kotlin/info/appdev/charting/lint/RawTypeDataSetDetector.kt
new file mode 100644
index 000000000..36b684385
--- /dev/null
+++ b/lint/src/main/kotlin/info/appdev/charting/lint/RawTypeDataSetDetector.kt
@@ -0,0 +1,154 @@
+package info.appdev.charting.lint
+
+import com.android.tools.lint.client.api.UElementHandler
+import com.android.tools.lint.detector.api.Category
+import com.android.tools.lint.detector.api.Detector
+import com.android.tools.lint.detector.api.Implementation
+import com.android.tools.lint.detector.api.Issue
+import com.android.tools.lint.detector.api.JavaContext
+import com.android.tools.lint.detector.api.Scope
+import com.android.tools.lint.detector.api.Severity
+import com.android.tools.lint.detector.api.SourceCodeScanner
+import com.intellij.psi.PsiClassType
+import org.jetbrains.uast.UElement
+import org.jetbrains.uast.UField
+import org.jetbrains.uast.UImportStatement
+import org.jetbrains.uast.ULocalVariable
+import org.jetbrains.uast.UMethod
+import org.jetbrains.uast.UParameter
+import org.jetbrains.uast.UTypeReferenceExpression
+import org.jetbrains.uast.getParentOfType
+
+class RawTypeDataSetDetector : Detector(), SourceCodeScanner {
+
+ companion object {
+ private const val DATA_PKG = "info.appdev.charting.data"
+ private const val IFACE_PKG = "info.appdev.charting.interfaces.datasets"
+ private const val FLOAT_DEFAULT = "EntryFloat"
+ private const val DOUBLE_DEFAULT = "EntryDouble"
+
+ private val GENERIC_TYPES: Map> = mapOf(
+ "$DATA_PKG.DataSet" to (FLOAT_DEFAULT to DOUBLE_DEFAULT),
+ "$DATA_PKG.LineDataSet" to (FLOAT_DEFAULT to DOUBLE_DEFAULT),
+ "$IFACE_PKG.IDataSet" to (FLOAT_DEFAULT to DOUBLE_DEFAULT),
+ "$IFACE_PKG.ILineDataSet" to (FLOAT_DEFAULT to DOUBLE_DEFAULT),
+ "$IFACE_PKG.ILineRadarDataSet" to (FLOAT_DEFAULT to DOUBLE_DEFAULT),
+ "$IFACE_PKG.ILineScatterCandleRadarDataSet" to (FLOAT_DEFAULT to DOUBLE_DEFAULT),
+ "$IFACE_PKG.IBarLineScatterCandleBubbleDataSet" to (FLOAT_DEFAULT to DOUBLE_DEFAULT),
+ )
+
+ @JvmField
+ val ISSUE: Issue = Issue.create(
+ id = "RawTypeDataSet",
+ briefDescription = "Specify an explicit entry type parameter instead of `<*>` or no argument",
+ explanation = """
+ Using a star projection `Type<*>` or omitting the type argument entirely loses \
+ compile-time type-safety. Replace with `` (same precision as the \
+ legacy API) or `` (higher precision). \
+ Example: `LineDataSet` or `LineDataSet<*>` → `LineDataSet`.
+ """,
+ category = Category.CORRECTNESS,
+ priority = 5,
+ severity = Severity.ERROR,
+ implementation = Implementation(
+ RawTypeDataSetDetector::class.java,
+ Scope.JAVA_FILE_SCOPE
+ )
+ )
+ }
+
+ // -----------------------------------------------------------------------
+ // Why multiple node types?
+ //
+ // UTypeReferenceExpression in getApplicableUastTypes() is only traversed
+ // for INLINE expressions (e.g. `is LineDataSet` checks).
+ //
+ // For type ANNOTATIONS on declarations the UTypeReferenceExpression is
+ // NOT a traversed tree node — it is only accessible as a property:
+ // ULocalVariable.typeReference → val x: LineDataSet<*>
+ // UField.typeReference → class Foo { val x: LineDataSet<*> }
+ // UParameter.typeReference → fun f(x: LineDataSet<*>)
+ // UMethod.returnTypeReference → fun f(): LineDataSet<*>
+ //
+ // We therefore visit all container node types and pull the typeReference
+ // from each one explicitly.
+ // -----------------------------------------------------------------------
+ override fun getApplicableUastTypes(): List> = listOf(
+ ULocalVariable::class.java, // val x: LineDataSet<*>
+ UField::class.java, // class Foo { val x: LineDataSet<*> }
+ UParameter::class.java, // fun f(x: LineDataSet<*>)
+ UMethod::class.java, // fun f(): LineDataSet<*>
+ UTypeReferenceExpression::class.java, // is LineDataSet, cast as LineDataSet<*>, …
+ )
+
+ override fun createUastHandler(context: JavaContext): UElementHandler =
+ object : UElementHandler() {
+
+ override fun visitLocalVariable(node: ULocalVariable) =
+ checkTypeRef(context, node.typeReference)
+
+ override fun visitField(node: UField) =
+ checkTypeRef(context, node.typeReference)
+
+ override fun visitParameter(node: UParameter) =
+ checkTypeRef(context, node.typeReference)
+
+ override fun visitMethod(node: UMethod) =
+ checkTypeRef(context, node.returnTypeReference)
+
+ // Catches inline type positions: `is LineDataSet`, `as LineDataSet<*>`, …
+ override fun visitTypeReferenceExpression(node: UTypeReferenceExpression) {
+ if (node.getParentOfType() != null) return
+ checkTypeRefNode(context, node)
+ }
+
+ // ---- shared implementation ----------------------------------------
+
+ private fun checkTypeRef(context: JavaContext, typeRef: UTypeReferenceExpression?) {
+ if (typeRef == null) return
+ if (typeRef.getParentOfType() != null) return
+ checkTypeRefNode(context, typeRef)
+ }
+
+ private fun checkTypeRefNode(context: JavaContext, node: UTypeReferenceExpression) {
+ val sourcePsi = node.sourcePsi ?: return
+ val nodeText = sourcePsi.text.trim()
+
+ for ((fqn, typeArgs) in GENERIC_TYPES) {
+ val simpleName = fqn.substringAfterLast('.')
+ val isStarProjection = nodeText == "$simpleName<*>"
+ val isMissingTypeArg = nodeText == simpleName
+ if (!isStarProjection && !isMissingTypeArg) continue
+
+ // Verify FQN when the type resolves (guards against same-named classes)
+ val resolved = (node.type as? PsiClassType)?.resolve()
+ if (resolved != null && resolved.qualifiedName != fqn) continue
+
+ val (floatArg, doubleArg) = typeArgs
+ val (oldText, newFloat, newDouble) = if (isStarProjection)
+ Triple("$simpleName<*>", "$simpleName<$floatArg>", "$simpleName<$doubleArg>")
+ else
+ Triple(simpleName, "$simpleName<$floatArg>", "$simpleName<$doubleArg>")
+
+ context.report(
+ ISSUE,
+ node,
+ context.getLocation(sourcePsi),
+ if (isStarProjection) "Replace `$simpleName<*>` with an explicit entry type for type-safety"
+ else "Add missing type argument to `$simpleName`",
+ fix().alternatives(
+ fix().replace()
+ .name("Replace with $newFloat")
+ .text(oldText).with(newFloat)
+ .autoFix().build(),
+ fix().replace()
+ .name("Replace with $newDouble (high precision)")
+ .text(oldText).with(newDouble)
+ .autoFix().build()
+ )
+ )
+ break
+ }
+ }
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 2edcf4e5b..2e108c247 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -1,3 +1,4 @@
include("chartLib")
include("chartLibCompose")
include("app")
+include("lint")