From a7c03fffa42bc81b7588b8cfaac0a75b1234ecd9 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Mon, 16 Mar 2026 09:53:40 +0100 Subject: [PATCH 01/36] Update README with package name change details Clarified package name change and noted future removals. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f0ce0f1b6..55b2fa066 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ It comes with library view based `chartLib` and composeable `chartLibCompose` ### Packagename changed ! 🛑 During the full Kotlin conversion, -* 🛑 package name changed to `info.appdev.charting`, but there are still the old main chart classes included, but they will be removed in the future +* 🛑 package name changed to `info.appdev.charting` * 🛑 `MPPointF` in renamed to `PointF` * 🛑 `MPPointD` in renamed to `PointD` * 🛑 methods like `setSomethingEnabled(true)` are now properties like `isSomething = true` From 1915b5f1d8eca040a79c7193cc62f353fe44627a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:03:03 +0000 Subject: [PATCH 02/36] Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 2.3.10 to 2.3.20 Bumps [org.jetbrains.kotlin:kotlin-gradle-plugin](https://github.com/JetBrains/kotlin) from 2.3.10 to 2.3.20. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v2.3.20/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.3.10...v2.3.20) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin:kotlin-gradle-plugin dependency-version: 2.3.20 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2453050cf..4fba12077 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -6,7 +6,7 @@ buildscript { dependencies { classpath("com.android.tools.build:gradle:9.1.0") classpath("com.github.dcendents:android-maven-gradle-plugin:2.1") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.20") } } From 103d166b0dc6214c6d2d1a4fe5ed22156854abc3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Mar 2026 15:04:28 +0000 Subject: [PATCH 03/36] Bump org.jetbrains.kotlin.plugin.compose from 2.3.10 to 2.3.20 Bumps [org.jetbrains.kotlin.plugin.compose](https://github.com/JetBrains/kotlin) from 2.3.10 to 2.3.20. - [Release notes](https://github.com/JetBrains/kotlin/releases) - [Changelog](https://github.com/JetBrains/kotlin/blob/v2.3.20/ChangeLog.md) - [Commits](https://github.com/JetBrains/kotlin/compare/v2.3.10...v2.3.20) --- updated-dependencies: - dependency-name: org.jetbrains.kotlin.plugin.compose dependency-version: 2.3.20 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- app/build.gradle.kts | 2 +- chartLibCompose/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c78733f72..3ed0523cb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget plugins { id("com.android.application") - id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" } android { diff --git a/chartLibCompose/build.gradle.kts b/chartLibCompose/build.gradle.kts index a540a0210..532f70ac3 100644 --- a/chartLibCompose/build.gradle.kts +++ b/chartLibCompose/build.gradle.kts @@ -5,7 +5,7 @@ import java.net.URI plugins { id("com.android.library") id("maven-publish") - id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" + id("org.jetbrains.kotlin.plugin.compose") version "2.3.20" id("com.vanniktech.maven.publish") version "0.36.0" } From c1a46f4d275a17fe9c9e27b9c545d94998122748 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 19 Mar 2026 14:43:53 +0000 Subject: [PATCH 04/36] Bump gradle-wrapper from 9.3.1 to 9.4.1 Bumps gradle-wrapper from 9.3.1 to 9.4.1. --- updated-dependencies: - dependency-name: gradle-wrapper dependency-version: 9.4.1 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- gradle/wrapper/gradle-wrapper.jar | Bin 46175 -> 48966 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 61285a659d17295f1de7c53e24fdf13ad755c379..d997cfc60f4cff0e7451d19d49a82fa986695d07 100644 GIT binary patch delta 39855 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

SdO`o?U^bCPz6Ehh!k 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


Xdyd*p!l1Y@nCA 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37f78a6af..c61a118f7 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index adff685a0..739907dfd 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. From 49468acf238bbe9878941fdd22446ccbd05d57bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 26 Mar 2026 14:43:09 +0000 Subject: [PATCH 05/36] Bump androidx.compose:compose-bom from 2026.03.00 to 2026.03.01 Bumps androidx.compose:compose-bom from 2026.03.00 to 2026.03.01. --- updated-dependencies: - dependency-name: androidx.compose:compose-bom dependency-version: 2026.03.01 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- app/build.gradle.kts | 2 +- chartLibCompose/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3ed0523cb..cb249759b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { implementation("androidx.viewpager2:viewpager2:1.1.0") // Compose BOM - val composeBom = platform("androidx.compose:compose-bom:2026.03.00") + val composeBom = platform("androidx.compose:compose-bom:2026.03.01") implementation(composeBom) androidTestImplementation(composeBom) diff --git a/chartLibCompose/build.gradle.kts b/chartLibCompose/build.gradle.kts index 532f70ac3..68f44b787 100644 --- a/chartLibCompose/build.gradle.kts +++ b/chartLibCompose/build.gradle.kts @@ -52,7 +52,7 @@ dependencies { api(project(":chartLib")) // Compose dependencies - val composeBom = platform("androidx.compose:compose-bom:2026.03.00") + val composeBom = platform("androidx.compose:compose-bom:2026.03.01") implementation(composeBom) implementation("androidx.compose.material3:material3") implementation("androidx.compose.ui:ui") From 4ad4e2117af8b1caf1e48d94bad67e7de80c4068 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sat, 28 Mar 2026 10:42:38 +0100 Subject: [PATCH 06/36] Cosmetic --- .../appdev/chartexample/HorizontalBarChartActivity.kt | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt index fc483fae6..8c4aff7ca 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt @@ -41,16 +41,11 @@ class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartV binding.chart1.setOnChartValueSelectedListener(this) binding.chart1.isLogging = true - - // chart.setHighlightEnabled(false); binding.chart1.isDrawBarShadow = false - binding.chart1.isDrawValueAboveBar = true - binding.chart1.description.isEnabled = false - // if more than 60 entries are displayed in the chart, no values will be - // drawn + // if more than 60 entries are displayed in the chart, no values will be drawn binding.chart1.setMaxVisibleValueCount(60) // scaling can now only be done on x- and y-axis separately @@ -73,14 +68,14 @@ class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartV yl.isDrawGridLines = true yl.axisMinimum = 0f // this replaces setStartAtZero(true) - // yl.setInverted(true); + // yl.setInverted(true); val yr = binding.chart1.axisRight yr.typeface = tfLight yr.isDrawAxisLine = true yr.isDrawGridLines = false yr.axisMinimum = 0f // this replaces setStartAtZero(true) - // yr.setInverted(true); + // yr.setInverted(true); binding.chart1.setFitBars(true) binding.chart1.animateY(2500) From 243a30a913eb9fb780c6ec6d3d00135d161725b7 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sat, 28 Mar 2026 14:14:25 +0100 Subject: [PATCH 07/36] Entity toString for extended classes --- .../src/main/kotlin/info/appdev/charting/data/BarEntry.kt | 4 ++++ .../src/main/kotlin/info/appdev/charting/data/Entry.kt | 7 ++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt index 775c23ed2..1dd7cecd9 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt @@ -216,6 +216,10 @@ open class BarEntry : Entry { } } + override fun toString(): String { + return super.toString() + " yVals=" + yVals.contentToString() + } + companion object { /** * Calculates the sum across all values of the given stack. diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt index 4706bc2de..db85d860b 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt @@ -1,6 +1,7 @@ package info.appdev.charting.data import android.graphics.drawable.Drawable +import android.os.Build import android.os.Parcel import android.os.ParcelFormatException import android.os.Parcelable @@ -100,7 +101,11 @@ open class Entry : BaseEntry, Parcelable, Serializable { * returns a string representation of the entry containing x-index and value */ override fun toString(): String { - return "Entry x=$x y=$y" + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + "${this.javaClass.typeName.substringAfterLast(".")} x=$x y=$y" + } else { + "Entry x=$x y=$y" + } } override fun describeContents(): Int { From 074f0bbd6e8cf47e299dad83e5324da3ccf059d6 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sat, 28 Mar 2026 14:31:19 +0100 Subject: [PATCH 08/36] BarEntry more idiomatic and concise Kotlin style --- .../main/kotlin/info/appdev/charting/data/BarEntry.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt index 1dd7cecd9..1e4f78b3d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt @@ -225,14 +225,7 @@ open class BarEntry : Entry { * Calculates the sum across all values of the given stack. */ private fun calcSum(vals: FloatArray?): Float { - if (vals == null) - return 0f - - var sum = 0f - - for (f in vals) sum += f - - return sum + return vals?.sum() ?: 0f } } } From aca0c7838fbbdd6ac7c1d4519dc3bb120e71098d Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sat, 28 Mar 2026 16:03:59 +0100 Subject: [PATCH 09/36] Cosmetic --- .../HorizontalBarChartActivity.kt | 46 +++++++++---------- .../appdev/chartexample/StackedBarActivity.kt | 4 +- 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt index 8c4aff7ca..e84a41d22 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt @@ -36,8 +36,8 @@ class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartV binding = ActivityHorizontalbarchartBinding.inflate(layoutInflater) setContentView(binding.root) - binding.seekBarY.setOnSeekBarChangeListener(this) binding.seekBarX.setOnSeekBarChangeListener(this) + binding.seekBarY.setOnSeekBarChangeListener(this) binding.chart1.setOnChartValueSelectedListener(this) binding.chart1.isLogging = true @@ -55,33 +55,33 @@ class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartV // chart.setDrawBarShadow(true); binding.chart1.setDrawGridBackground(false) - val xl = binding.chart1.xAxis - xl.position = XAxisPosition.BOTTOM - xl.typeface = tfLight - xl.isDrawAxisLine = true - xl.isDrawGridLines = false - xl.granularity = 10f - - val yl = binding.chart1.axisLeft - yl.typeface = tfLight - yl.isDrawAxisLine = true - yl.isDrawGridLines = true - yl.axisMinimum = 0f // this replaces setStartAtZero(true) - - // yl.setInverted(true); - val yr = binding.chart1.axisRight - yr.typeface = tfLight - yr.isDrawAxisLine = true - yr.isDrawGridLines = false - yr.axisMinimum = 0f // this replaces setStartAtZero(true) - - // yr.setInverted(true); + val xAxis = binding.chart1.xAxis + xAxis.position = XAxisPosition.BOTTOM + xAxis.typeface = tfLight + xAxis.isDrawAxisLine = true + xAxis.isDrawGridLines = false + xAxis.granularity = 10f + + val yAxis = binding.chart1.axisLeft + yAxis.typeface = tfLight + yAxis.isDrawAxisLine = true + yAxis.isDrawGridLines = true + yAxis.axisMinimum = 0f // this replaces setStartAtZero(true) + + // yl.setInverted(true) + val axisRight = binding.chart1.axisRight + axisRight.typeface = tfLight + axisRight.isDrawAxisLine = true + axisRight.isDrawGridLines = false + axisRight.axisMinimum = 0f // this replaces setStartAtZero(true) + + // yr.setInverted(true) binding.chart1.setFitBars(true) binding.chart1.animateY(2500) // setting data - binding.seekBarY.progress = 50 binding.seekBarX.progress = 12 + binding.seekBarY.progress = 50 binding.chart1.legend.apply { verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM diff --git a/app/src/main/kotlin/info/appdev/chartexample/StackedBarActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/StackedBarActivity.kt index 21c5bcbfe..443cc16ca 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/StackedBarActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/StackedBarActivity.kt @@ -67,8 +67,8 @@ class StackedBarActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSele val xLabels = binding.chart1.xAxis xLabels.position = XAxisPosition.TOP - // chart.setDrawXLabels(false); - // chart.setDrawYLabels(false); + // chart.setDrawXLabels(false) + // chart.setDrawYLabels(false) // setting data binding.seekBarX.progress = 12 From 74d757a1f10fc1036844e41527a7d3c5d3da357f Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 10:04:39 +0200 Subject: [PATCH 10/36] Cosmetic --- .../src/main/kotlin/info/appdev/charting/data/ChartData.kt | 4 ++-- .../appdev/charting/renderer/HorizontalBarChartRenderer.kt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) 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 bb4565974..6b7bbe116 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt @@ -529,9 +529,9 @@ abstract class ChartData> : Serializable { /** * Sets a custom IValueFormatter for all DataSets this data object contains. */ - fun setValueFormatter(f: IValueFormatter) { + fun setValueFormatter(formatter: IValueFormatter) { for (set in this.dataSets) { - set.valueFormatter = f + set.valueFormatter = formatter } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/HorizontalBarChartRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/HorizontalBarChartRenderer.kt index e5d088650..67fa0d74c 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/HorizontalBarChartRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/HorizontalBarChartRenderer.kt @@ -227,7 +227,7 @@ open class HorizontalBarChartRenderer( val formattedValue = formatter.getFormattedValue(valueY, barEntry, i, viewPortHandler) // calculate the correct offset depending on the draw position of the value val valueTextWidth = paintValues.calcTextWidth(formattedValue).toFloat() - posOffset = (if (drawValueAboveBar) valueOffsetPlus else -(valueTextWidth + valueOffsetPlus)) + posOffset = if (drawValueAboveBar) valueOffsetPlus else -(valueTextWidth + valueOffsetPlus) negOffset = ((if (drawValueAboveBar) -(valueTextWidth + valueOffsetPlus) else valueOffsetPlus) - (buffer.buffer[j + 2] - buffer.buffer[j])) From c81844155b69b710d1b1a40abcf6a43ce09ecfb8 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 10:56:13 +0200 Subject: [PATCH 11/36] Rename variable --- .../info/appdev/charting/data/BarDataSet.kt | 4 +- .../info/appdev/charting/data/BaseDataSet.kt | 2 +- .../appdev/charting/data/BubbleDataSet.kt | 4 +- .../appdev/charting/data/CandleDataSet.kt | 4 +- .../info/appdev/charting/data/ChartData.kt | 6 +- .../info/appdev/charting/data/DataSet.kt | 74 +++++++++---------- .../info/appdev/charting/data/LineDataSet.kt | 4 +- .../info/appdev/charting/data/PieDataSet.kt | 4 +- .../info/appdev/charting/data/RadarDataSet.kt | 4 +- .../appdev/charting/data/ScatterDataSet.kt | 4 +- .../charting/highlight/ChartHighlighter.kt | 2 +- .../charting/interfaces/datasets/IDataSet.kt | 2 +- 12 files changed, 57 insertions(+), 57 deletions(-) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarDataSet.kt index db79610ff..c4114435e 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarDataSet.kt @@ -52,8 +52,8 @@ open class BarDataSet(yVals: MutableList, label: String) : BarLineScat override fun copy(): DataSet? { val entries: MutableList = mutableListOf() - for (i in mEntries.indices) { - entries.add(mEntries[i].copy()) + for (i in entriesInternal.indices) { + entries.add(entriesInternal[i].copy()) } val copied = BarDataSet(entries, label) copy(copied) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt index a186c6eb3..10208d61f 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt @@ -16,7 +16,7 @@ import info.appdev.charting.utils.Utils import info.appdev.charting.utils.convertDpToPixel /** - * This is the base dataset of all DataSets. It's purpose is to implement critical methods + * This is the base dataset of all DataSets. Its purpose is to implement critical methods * provided by the IDataSet interface. */ abstract class BaseDataSet() : IDataSet { diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleDataSet.kt index 07f229a2c..05c8183c6 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleDataSet.kt @@ -21,8 +21,8 @@ open class BubbleDataSet(yVals: MutableList, label: String) : BarLi override fun copy(): DataSet { val entries: MutableList = ArrayList() - for (i in mEntries.indices) { - entries.add(mEntries[i].copy()) + for (i in entriesInternal.indices) { + entries.add(entriesInternal[i].copy()) } val copied = BubbleDataSet(entries, label) copy(copied) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/CandleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/CandleDataSet.kt index 85e5b8bf1..685d9afac 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/CandleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/CandleDataSet.kt @@ -71,8 +71,8 @@ open class CandleDataSet(yVals: MutableList, label: String = "") : override fun copy(): DataSet { val entries: MutableList = mutableListOf() - for (i in mEntries.indices) { - entries.add(mEntries[i].copy()) + for (i in entriesInternal.indices) { + entries.add(entriesInternal[i].copy()) } val copied = CandleDataSet(entries, label) 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 6b7bbe116..698d142be 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt @@ -197,7 +197,7 @@ abstract class ChartData> : Serializable { /** * Retrieve the index of a DataSet with a specific label from the ChartData. - * Search can be case sensitive or not. IMPORTANT: This method does + * Search can be case-sensitive or not. IMPORTANT: This method does * calculations at runtime, do not over-use in performance critical * situations. * @@ -252,8 +252,8 @@ abstract class ChartData> : Serializable { } /** - * Returns the DataSet object with the given label. Search can be case - * sensitive or not. IMPORTANT: This method does calculations at runtime. + * 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? { 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 12188cd9a..5c06043a6 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/DataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/DataSet.kt @@ -11,7 +11,7 @@ import kotlin.math.abs * LineChart, or the values of a specific group of bars in the BarChart). */ abstract class DataSet( - protected var mEntries: MutableList, + protected var entriesInternal: MutableList, label: String = "" ) : BaseDataSet(label), Serializable { /** @@ -53,11 +53,11 @@ abstract class DataSet( this.xMax = -Float.MAX_VALUE this.xMin = Float.MAX_VALUE - if (mEntries.isEmpty()) { + if (entriesInternal.isEmpty()) { return } - for (e in mEntries) { + for (e in entriesInternal) { calcMinMax(e) } } @@ -66,7 +66,7 @@ abstract class DataSet( this.yMax = -Float.MAX_VALUE this.yMin = Float.MAX_VALUE - if (mEntries.isEmpty()) { + if (entriesInternal.isEmpty()) { return } @@ -80,7 +80,7 @@ abstract class DataSet( for (i in indexFrom..indexTo) { // only recalculate y - calcMinMaxY(mEntries[i]) + calcMinMaxY(entriesInternal[i]) } } @@ -113,15 +113,15 @@ abstract class DataSet( } override val entryCount: Int - get() = mEntries.size + get() = entriesInternal.size /** * Returns the array of entries that this DataSet represents. */ var entries: MutableList - get() = mEntries + get() = entriesInternal set(entries) { - mEntries = entries + entriesInternal = entries notifyDataChanged() } @@ -137,8 +137,8 @@ abstract class DataSet( override fun toString(): String { val buffer = StringBuilder() buffer.append(toSimpleString()) - for (i in mEntries.indices) { - buffer.append(mEntries[i].toString()).append(" ") + for (i in entriesInternal.indices) { + buffer.append(entriesInternal[i].toString()).append(" ") } return buffer.toString() } @@ -146,21 +146,21 @@ abstract class DataSet( /** * Returns a simple string representation of the DataSet with the type and the number of Entries. */ - fun toSimpleString() = "DataSet, label: $label, entries: ${mEntries.size}" + fun toSimpleString() = "DataSet, label: $label, entries: ${entriesInternal.size}" override fun addEntryOrdered(entry: T) { calcMinMax(entry) - if (!mEntries.isEmpty() && mEntries[mEntries.size - 1].x > entry.x) { + if (!entriesInternal.isEmpty() && entriesInternal[entriesInternal.size - 1].x > entry.x) { val closestIndex = getEntryIndex(entry.x, entry.y, Rounding.UP) - mEntries.add(closestIndex, entry) + entriesInternal.add(closestIndex, entry) } else { - mEntries.add(entry) + entriesInternal.add(entry) } } override fun clear() { - mEntries.clear() + entriesInternal.clear() notifyDataChanged() } @@ -171,7 +171,7 @@ abstract class DataSet( } override fun removeEntry(entry: T): Boolean { - val removed = mEntries.remove(entry) + val removed = entriesInternal.remove(entry) if (removed) { calcMinMax() @@ -180,14 +180,14 @@ abstract class DataSet( } override fun getEntryIndex(entry: T): Int { - return mEntries.indexOf(entry) + return entriesInternal.indexOf(entry) } override fun getEntryForXValue(xValue: Float, closestToY: Float, rounding: Rounding?): T? { val index = getEntryIndex(xValue, closestToY, rounding) if (index > -1) { - return mEntries[index] + return entriesInternal[index] } return null } @@ -200,28 +200,28 @@ abstract class DataSet( if (index < 0) { Timber.e("index $index is < 0 for getEntryForIndex") return null - } else if (index >= mEntries.size) { - Timber.e("index $index / ${mEntries.size} is out of range for getEntryForIndex") + } else if (index >= entriesInternal.size) { + Timber.e("index $index / ${entriesInternal.size} is out of range for getEntryForIndex") return null } - return mEntries[index] + return entriesInternal[index] } override fun getEntryIndex(xValue: Float, closestToY: Float, rounding: Rounding?): Int { - if (mEntries.isEmpty()) { + if (entriesInternal.isEmpty()) { return -1 } var low = 0 - var high = mEntries.size - 1 + var high = entriesInternal.size - 1 var closest = high while (low < high) { val m = low + (high - low) / 2 - val currentEntry: Entry = mEntries[m] + val currentEntry: Entry = entriesInternal[m] - val nextEntry: Entry = mEntries[m + 1] + val nextEntry: Entry = entriesInternal[m + 1] val d1 = currentEntry.x - xValue val d2 = nextEntry.x - xValue @@ -230,7 +230,7 @@ abstract class DataSet( if (ad2 < ad1) { // [m + 1] is closer to xValue - // Search in an higher place + // Search in a higher place low = m + 1 } else if (ad1 < ad2) { // [m] is closer to xValue @@ -243,7 +243,7 @@ abstract class DataSet( // Search in a lower place high = m } else if (d1 < 0.0) { - // Search in an higher place + // Search in a higher place low = m + 1 } } @@ -251,11 +251,11 @@ abstract class DataSet( closest = high } - val closestEntry: Entry = mEntries[closest] + val closestEntry: Entry = entriesInternal[closest] val closestXValue = closestEntry.x if (rounding == Rounding.UP) { // If rounding up, and found x-value is lower than specified x, and we can go upper... - if (closestXValue < xValue && closest < mEntries.size - 1) { + if (closestXValue < xValue && closest < entriesInternal.size - 1) { ++closest } } else if (rounding == Rounding.DOWN) { @@ -267,7 +267,7 @@ abstract class DataSet( // Search by closest to y-value if (!closestToY.isNaN()) { - while (closest > 0 && mEntries[closest - 1].x == closestXValue) { + while (closest > 0 && entriesInternal[closest - 1].x == closestXValue) { closest -= 1 } @@ -276,11 +276,11 @@ abstract class DataSet( while (true) { closest += 1 - if (closest >= mEntries.size) { + if (closest >= entriesInternal.size) { break } - val value: T = mEntries[closest] + val value: T = entriesInternal[closest] if (value.x != closestXValue) { break @@ -301,23 +301,23 @@ abstract class DataSet( val entries: MutableList = mutableListOf() var low = 0 - var high = mEntries.size - 1 + var high = entriesInternal.size - 1 while (low <= high) { var m = (high + low) / 2 - var entry = mEntries[m] + var entry = entriesInternal[m] // if we have a match if (xValue == entry.x) { - while (m > 0 && mEntries[m - 1].x == xValue) { + while (m > 0 && entriesInternal[m - 1].x == xValue) { m-- } - high = mEntries.size + high = entriesInternal.size // loop over all "equal" entries while (m < high) { - entry = mEntries[m] + entry = entriesInternal[m] if (entry.x == xValue) { entries.add(entry) } else { 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 b8584b36b..be22ee8e5 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt @@ -75,8 +75,8 @@ open class LineDataSet(yVals: MutableList = mutableListOf(), label: Strin override fun copy(): DataSet { val entries: MutableList = mutableListOf() - for (i in mEntries.indices) { - entries.add(mEntries[i].copy()) + for (i in entriesInternal.indices) { + entries.add(entriesInternal[i].copy()) } val copied = LineDataSet(entries, label) copy(copied) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/PieDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/PieDataSet.kt index 717f9d6f7..fd1f5ca4e 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/PieDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/PieDataSet.kt @@ -31,8 +31,8 @@ open class PieDataSet(yVals: MutableList, label: String) : DataSet { val entries: MutableList = mutableListOf() - for (i in mEntries.indices) { - entries.add(mEntries[i].copy()) + for (i in entriesInternal.indices) { + entries.add(entriesInternal[i].copy()) } val copied = PieDataSet(entries, label) return copied diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarDataSet.kt index 5457390ce..8a93eebe2 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarDataSet.kt @@ -60,8 +60,8 @@ open class RadarDataSet(yVals: MutableList, label: String = "") : Li override fun copy(): DataSet { val entries: MutableList = mutableListOf() - for (i in mEntries.indices) { - entries.add(mEntries[i].copy()) + for (i in entriesInternal.indices) { + entries.add(entriesInternal[i].copy()) } val copied = RadarDataSet(entries, label) copy(copied) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/ScatterDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/ScatterDataSet.kt index 0b1b5dcbb..d9c1ef47f 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/ScatterDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/ScatterDataSet.kt @@ -40,8 +40,8 @@ open class ScatterDataSet(yVals: MutableList, label: String = "") : LineS override fun copy(): DataSet { val entries: MutableList = mutableListOf() - for (i in mEntries.indices) { - entries.add(mEntries[i].copy()) + for (i in entriesInternal.indices) { + entries.add(entriesInternal[i].copy()) } val copied = ScatterDataSet(entries, label) copy(copied) 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 a49a5a5ef..fb54b536d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt @@ -158,7 +158,7 @@ open class ChartHighlighter>(prote * y-axis. * * @param closestValues contains two Highlight objects per DataSet closest to the selected x-position (determined by - * rounding up an down) + * rounding up a down) * @param axis the closest axis */ fun getClosestHighlightByPixel( diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt index 2b2a75a6c..c401c661f 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt @@ -105,7 +105,7 @@ interface IDataSet { /** * Returns the position of the provided entry in the DataSets Entry array. - * Returns -1 if doesn't exist. + * Returns -1 if it doesn't exist. */ fun getEntryIndex(entry: T): Int From 47c89dd6958d5e29c7dedec917b838dcf5034574 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Mon, 30 Mar 2026 17:47:25 +0200 Subject: [PATCH 12/36] UnixTime relative to now AxisValueFormatter --- .../UnixTimeRelative2NowAxisValueFormatter.kt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeRelative2NowAxisValueFormatter.kt diff --git a/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeRelative2NowAxisValueFormatter.kt b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeRelative2NowAxisValueFormatter.kt new file mode 100644 index 000000000..0183239d5 --- /dev/null +++ b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeRelative2NowAxisValueFormatter.kt @@ -0,0 +1,17 @@ +package info.appdev.chartexample.formatter + +import info.appdev.charting.components.AxisBase +import info.appdev.charting.formatter.IAxisValueFormatter +import java.text.SimpleDateFormat +import java.util.Locale + +class UnixTimeRelative2NowAxisValueFormatter(val format: String = "yyyy-MM-dd'T'HH:mm:ss'Z'") : IAxisValueFormatter { + + val simpleDateFormat = SimpleDateFormat(format, Locale.getDefault()) + + override fun getFormattedValue(value: Float, axis: AxisBase?): String { + val relative2Now = System.currentTimeMillis() + (value.toLong() * 1000) + return simpleDateFormat.format(relative2Now) + } + +} From 8a7f2da2095268de11aec4041205c5d5de02c9d3 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 10:51:01 +0200 Subject: [PATCH 13/36] BaseEntry generic to be able to use it as Float or Double --- .../info/appdev/charting/data/BaseEntry.kt | 14 +- .../kotlin/info/appdev/charting/data/Entry.kt | 2 +- .../info/appdev/charting/data/EntryDouble.kt | 150 +++++++++++++++++- .../info/appdev/charting/utils/Utils.kt | 2 + 4 files changed, 159 insertions(+), 9 deletions(-) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseEntry.kt index d553eb652..b3f2725b5 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseEntry.kt @@ -2,10 +2,10 @@ package info.appdev.charting.data import android.graphics.drawable.Drawable -abstract class BaseEntry { +abstract class BaseEntry where T : Number, T : Comparable { - protected var yBase: Float = 0f - open var y: Float + protected lateinit var yBase: T + open var y: T get() = yBase set(value) { yBase = value @@ -17,19 +17,19 @@ abstract class BaseEntry { constructor() - constructor(y: Float) { + constructor(y: T) { this.yBase = y } - constructor(y: Float, data: Any?) : this(y) { + constructor(y: T, data: Any?) : this(y) { this.data = data } - constructor(y: Float, icon: Drawable?) : this(y) { + constructor(y: T, icon: Drawable?) : this(y) { this.icon = icon } - constructor(y: Float, icon: Drawable?, data: Any?) : this(y) { + constructor(y: T, icon: Drawable?, data: Any?) : this(y) { this.icon = icon this.data = data } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt index db85d860b..9a0b9fd19 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt @@ -13,7 +13,7 @@ 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. */ -open class Entry : BaseEntry, Parcelable, Serializable { +open class Entry : BaseEntry, Parcelable, Serializable { private var _x: Float = 0f open var x: Float diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt index 284880c97..955460af8 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt @@ -1,3 +1,151 @@ package info.appdev.charting.data -class EntryDouble(val xDouble: Double, y: Float) : Entry(xDouble.toFloat(), 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 + +open class EntryDouble : BaseEntry, Parcelable, Serializable { + + private var _x: Double = 0.0 + open var x: Double + get() = _x + set(value) { + _x = value + } + + constructor() + + /** + * A Entry 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: Double, y: Double) : super(y) { + this._x = x + } + + /** + * A Entry 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: Double, y: Double, data: Any?) : super(y, data) { + this._x = x + } + + /** + * A Entry 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: Double, y: Double, icon: Drawable?) : super(y, icon) { + this._x = x + } + + /** + * A Entry 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 Entry represents. + */ + constructor(x: Double, y: Double, icon: Drawable?, data: Any?) : super(y, icon, data) { + this._x = x + } + + /** + * returns an exact copy of the entry + */ + open fun copy(): EntryDouble { + val e = EntryDouble(x, y, data) + return e + } + + /** + * Compares value, xIndex and data of the entries. Returns true if entries + * are equal in those points, false if not. Does not check by hash-code like + * it's done by the "equals" method. + */ + fun equalTo(e: EntryDouble?): Boolean { + if (e == null) + return false + + if (e.data !== this.data) + return false + + if (abs((e.x - this.x)) > Utils.DOUBLE_EPSILON) + return false + + if (abs((e.y - this.y)) > Utils.DOUBLE_EPSILON) + return false + + return true + } + + /** + * returns a string representation of the entry containing x-index and value + */ + override fun toString(): String { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + "${this.javaClass.typeName.substringAfterLast(".")} x=$x y=$y" + } else { + "EntryDouble x=$x y=$y" + } + } + + override fun describeContents(): Int { + return 0 + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + dest.writeDouble(this.x) + dest.writeDouble(this.y) + if (data != null) { + if (data is Parcelable) { + dest.writeInt(1) + dest.writeParcelable(data as Parcelable?, flags) + } else { + throw ParcelFormatException("Cannot parcel an EntryDouble with non-parcelable data") + } + } else { + dest.writeInt(0) + } + } + + protected constructor(`in`: Parcel) { + this._x = `in`.readDouble() + this.yBase = `in`.readDouble() + if (`in`.readInt() == 1) { + this.data = if (Build.VERSION.SDK_INT >= 33) { + `in`.readParcelable(Any::class.java.classLoader, Any::class.java) + } else { + @Suppress("DEPRECATION") + `in`.readParcelable(Any::class.java.classLoader) + } + } + } + + companion object { + @JvmField + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): EntryDouble { + return EntryDouble(source) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } +} diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/Utils.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/Utils.kt index b9a2b0e00..f5cfd8614 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/Utils.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/Utils.kt @@ -7,6 +7,7 @@ import android.view.VelocityTracker import android.view.ViewConfiguration import info.appdev.charting.formatter.DefaultValueFormatter import info.appdev.charting.formatter.IValueFormatter +import java.lang.Float.intBitsToFloat import kotlin.math.abs import kotlin.math.cos import kotlin.math.sin @@ -23,6 +24,7 @@ object Utils { const val FDEG2RAD: Float = (Math.PI.toFloat() / 180f) val FLOAT_EPSILON: Float = java.lang.Float.intBitsToFloat(1) + val DOUBLE_EPSILON: Double = java.lang.Double.longBitsToDouble(1L) /** * initialize method, called inside the Chart.init() method. From 29a5b72adae047ae68bef54956c5294f3d7ab385 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 10:53:45 +0200 Subject: [PATCH 14/36] Rename Entry to EntryFloat --- .../appdev/chartexample/AnotherBarActivity.kt | 6 +- .../appdev/chartexample/BarChartActivity.kt | 16 ++--- .../BarChartActivityMultiDataset.kt | 24 ++++---- .../chartexample/BarChartActivitySinus.kt | 6 +- .../chartexample/BarChartPositiveNegative.kt | 10 ++-- .../chartexample/BubbleChartActivity.kt | 20 +++---- .../chartexample/CandleStickChartActivity.kt | 6 +- .../chartexample/CombinedChartActivity.kt | 32 +++++----- .../chartexample/CubicLineChartActivity.kt | 6 +- .../info/appdev/chartexample/DataTools.kt | 10 ++-- .../appdev/chartexample/DrawChartActivity.kt | 16 ++--- .../chartexample/DynamicalAddingActivity.kt | 12 ++-- .../appdev/chartexample/FilledLineActivity.kt | 10 ++-- .../appdev/chartexample/GradientActivity.kt | 6 +- .../chartexample/HalfPieChartActivity.kt | 6 +- .../HorizontalBarChartActivity.kt | 14 ++--- .../HorizontalBarNegativeChartActivity.kt | 14 ++--- .../chartexample/InvertedLineChartActivity.kt | 10 ++-- .../appdev/chartexample/LineChartActivity.kt | 6 +- .../chartexample/LineChartActivityColored.kt | 6 +- .../chartexample/LineChartDualAxisActivity.kt | 20 +++---- .../chartexample/LineChartTimeActivity.kt | 6 +- .../chartexample/ListViewBarChartActivity.kt | 6 +- .../ListViewMultiChartActivity.kt | 22 +++---- .../chartexample/MultiLineChartActivity.kt | 10 ++-- .../chartexample/PerformanceLineChart.kt | 6 +- .../appdev/chartexample/PieChartActivity.kt | 12 ++-- .../chartexample/PieChartRoundedActivity.kt | 12 ++-- .../chartexample/PiePolylineChartActivity.kt | 12 ++-- .../appdev/chartexample/RadarChartActivity.kt | 10 ++-- .../chartexample/RealtimeLineChartActivity.kt | 8 +-- .../chartexample/ScatterChartActivity.kt | 18 +++--- .../appdev/chartexample/ScrollViewActivity.kt | 6 +- .../SpecificPositionsLineChartActivity.kt | 12 ++-- .../appdev/chartexample/StackedBarActivity.kt | 12 ++-- .../StackedBarActivityNegative.kt | 36 +++++------ .../appdev/chartexample/TimeLineActivity.kt | 22 +++---- .../compose/HorizontalBarComposeActivity.kt | 14 ++--- .../HorizontalBarFullComposeActivity.kt | 6 +- .../compose/MultiLineComposeActivity.kt | 6 +- .../chartexample/custom/MyMarkerView.kt | 14 ++--- .../chartexample/custom/RadarMarkerView.kt | 8 +-- .../custom/StackedBarsMarkerView.kt | 18 +++--- .../chartexample/custom/TimeMarkerView.kt | 14 ++--- .../chartexample/custom/XYMarkerView.kt | 8 +-- .../formatter/MyValueFormatter.kt | 4 +- .../chartexample/fragments/SimpleFragment.kt | 18 +++--- .../info/appdev/charting/charts/BarChart.kt | 6 +- .../charting/charts/BarLineChartBase.kt | 8 +-- .../info/appdev/charting/charts/Chart.kt | 14 ++--- .../appdev/charting/charts/CombinedChart.kt | 4 +- .../charting/charts/HorizontalBarChart.kt | 8 +-- .../charting/charts/PieRadarChartBase.kt | 4 +- .../appdev/charting/components/IMarker.kt | 6 +- .../appdev/charting/components/MarkerImage.kt | 4 +- .../appdev/charting/components/MarkerView.kt | 4 +- .../info/appdev/charting/data/BarDataSet.kt | 12 ++-- .../data/{BarEntry.kt => BarEntryFloat.kt} | 6 +- .../data/BarLineScatterCandleBubbleData.kt | 4 +- .../data/BarLineScatterCandleBubbleDataSet.kt | 2 +- .../info/appdev/charting/data/BaseDataSet.kt | 2 +- .../appdev/charting/data/BubbleDataSet.kt | 8 +-- .../{BubbleEntry.kt => BubbleEntryFloat.kt} | 6 +- .../appdev/charting/data/CandleDataSet.kt | 10 ++-- .../{CandleEntry.kt => CandleEntryFloat.kt} | 6 +- .../info/appdev/charting/data/ChartData.kt | 26 ++++---- .../info/appdev/charting/data/CombinedData.kt | 12 ++-- .../info/appdev/charting/data/DataSet.kt | 16 ++--- .../charting/data/{Entry.kt => EntryFloat.kt} | 32 +++++----- .../info/appdev/charting/data/LineDataSet.kt | 6 +- .../appdev/charting/data/LineRadarDataSet.kt | 2 +- .../data/LineScatterCandleRadarDataSet.kt | 2 +- .../info/appdev/charting/data/PieData.kt | 2 +- .../info/appdev/charting/data/PieDataSet.kt | 8 +-- .../data/{PieEntry.kt => PieEntryFloat.kt} | 6 +- .../info/appdev/charting/data/RadarData.kt | 2 +- .../info/appdev/charting/data/RadarDataSet.kt | 6 +- .../{RadarEntry.kt => RadarEntryFloat.kt} | 6 +- .../appdev/charting/data/ScatterDataSet.kt | 6 +- .../charting/formatter/ColorFormatter.kt | 6 +- .../formatter/DefaultValueFormatter.kt | 4 +- .../charting/formatter/IValueFormatter.kt | 6 +- .../charting/formatter/LargeValueFormatter.kt | 4 +- .../charting/formatter/PercentFormatter.kt | 4 +- .../formatter/StackedValueFormatter.kt | 10 ++-- .../charting/highlight/ChartHighlighter.kt | 4 +- .../highlight/HorizontalBarHighlighter.kt | 6 +- .../charting/highlight/PieHighlighter.kt | 8 +-- .../charting/highlight/RadarHighlighter.kt | 8 +-- .../interfaces/datasets/IBarDataSet.kt | 4 +- .../IBarLineScatterCandleBubbleDataSet.kt | 4 +- .../interfaces/datasets/IBubbleDataSet.kt | 4 +- .../interfaces/datasets/ICandleDataSet.kt | 5 +- .../charting/interfaces/datasets/IDataSet.kt | 4 +- .../interfaces/datasets/ILineDataSet.kt | 4 +- .../interfaces/datasets/ILineRadarDataSet.kt | 4 +- .../ILineScatterCandleRadarDataSet.kt | 4 +- .../interfaces/datasets/IPieDataSet.kt | 4 +- .../interfaces/datasets/IRadarDataSet.kt | 4 +- .../interfaces/datasets/IScatterDataSet.kt | 4 +- .../listener/BarLineChartTouchListener.kt | 6 +- .../listener/OnChartValueSelectedListener.kt | 6 +- .../charting/listener/OnDrawListener.kt | 8 +-- .../BarLineScatterCandleBubbleRenderer.kt | 6 +- .../appdev/charting/renderer/DataRenderer.kt | 8 +-- .../charting/renderer/LineChartRenderer.kt | 42 ++++++------- .../charting/renderer/PieChartRenderer.kt | 2 +- .../charting/utils/AssetManagerUtils.kt | 18 +++--- .../appdev/charting/utils/EntryXComparator.kt | 8 +-- .../info/appdev/charting/utils/Transformer.kt | 4 +- .../info/appdev/charting/test/BarDataTest.kt | 10 ++-- .../appdev/charting/test/ChartDataTest.kt | 44 +++++++------- .../info/appdev/charting/test/DataSetTest.kt | 60 +++++++++---------- .../charting/compose/BarChartComposable.kt | 8 +-- .../charting/compose/BubbleChartComposable.kt | 6 +- .../compose/CandleStickChartComposable.kt | 6 +- .../compose/CombinedChartComposable.kt | 6 +- .../compose/HorizontalBarChartComposable.kt | 6 +- .../charting/compose/LineChartComposable.kt | 8 +-- .../charting/compose/PieChartComposable.kt | 8 +-- .../charting/compose/RadarChartComposable.kt | 6 +- .../compose/ScatterChartComposable.kt | 6 +- .../compose/examples/ChartExamples.kt | 36 +++++------ 123 files changed, 622 insertions(+), 623 deletions(-) rename chartLib/src/main/kotlin/info/appdev/charting/data/{BarEntry.kt => BarEntryFloat.kt} (97%) rename chartLib/src/main/kotlin/info/appdev/charting/data/{BubbleEntry.kt => BubbleEntryFloat.kt} (90%) rename chartLib/src/main/kotlin/info/appdev/charting/data/{CandleEntry.kt => CandleEntryFloat.kt} (95%) rename chartLib/src/main/kotlin/info/appdev/charting/data/{Entry.kt => EntryFloat.kt} (80%) rename chartLib/src/main/kotlin/info/appdev/charting/data/{PieEntry.kt => PieEntryFloat.kt} (92%) rename chartLib/src/main/kotlin/info/appdev/charting/data/{RadarEntry.kt => RadarEntryFloat.kt} (80%) diff --git a/app/src/main/kotlin/info/appdev/chartexample/AnotherBarActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/AnotherBarActivity.kt index f0cb98037..5db7c77a8 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/AnotherBarActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/AnotherBarActivity.kt @@ -16,7 +16,7 @@ import info.appdev.chartexample.notimportant.DemoBase import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet -import info.appdev.charting.data.BarEntry +import info.appdev.charting.data.BarEntryFloat import info.appdev.charting.interfaces.datasets.IBarDataSet import info.appdev.charting.utils.ColorTemplate @@ -62,13 +62,13 @@ class AnotherBarActivity : DemoBase(), OnSeekBarChangeListener { binding.tvXMax.text = binding.seekBarX.progress.toString() binding.tvYMax.text = binding.seekBarY.progress.toString() - val values = ArrayList() + val values = ArrayList() val sampleValues = getValues(100) for (i in 0..() + val values = ArrayList() val sampleValues = getValues(100) var i = start.toInt() @@ -130,9 +130,9 @@ class BarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelect val `val` = (sampleValues[i]!!.toFloat() * (range + 1)) if (`val` * 100 < 25) { - values.add(BarEntry(i.toFloat(), `val`, ResourcesCompat.getDrawable(resources, R.drawable.star, null))) + values.add(BarEntryFloat(i.toFloat(), `val`, ResourcesCompat.getDrawable(resources, R.drawable.star, null))) } else { - values.add(BarEntry(i.toFloat(), `val`)) + values.add(BarEntryFloat(i.toFloat(), `val`)) } i++ } @@ -281,10 +281,10 @@ class BarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelect private val onValueSelectedRectF = RectF() - override fun onValueSelected(entry: Entry, highlight: Highlight) { + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { val bounds = onValueSelectedRectF - binding.chart1.getBarBounds(entry as BarEntry, bounds) - val position = binding.chart1.getPosition(entry, AxisDependency.LEFT) + binding.chart1.getBarBounds(entryFloat as BarEntryFloat, bounds) + val position = binding.chart1.getPosition(entryFloat, AxisDependency.LEFT) Timber.i("bounds $bounds") Timber.i("position = $position") diff --git a/app/src/main/kotlin/info/appdev/chartexample/BarChartActivityMultiDataset.kt b/app/src/main/kotlin/info/appdev/chartexample/BarChartActivityMultiDataset.kt index 522a876de..eba9e3c41 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/BarChartActivityMultiDataset.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/BarChartActivityMultiDataset.kt @@ -19,8 +19,8 @@ import info.appdev.charting.components.AxisBase import info.appdev.charting.components.Legend import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet -import info.appdev.charting.data.BarEntry -import info.appdev.charting.data.Entry +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat import info.appdev.charting.formatter.IAxisValueFormatter import info.appdev.charting.formatter.LargeValueFormatter import info.appdev.charting.highlight.Highlight @@ -110,19 +110,19 @@ class BarChartActivityMultiDataset : DemoBase(), OnSeekBarChangeListener, OnChar binding.tvXMax.text = String.format(Locale.ENGLISH, "%d-%d", startYear, endYear) binding.tvYMax.text = binding.seekBarY.progress.toString() - val values1 = ArrayList() - val values2 = ArrayList() - val values3 = ArrayList() - val values4 = ArrayList() + val values1 = ArrayList() + val values2 = ArrayList() + val values3 = ArrayList() + val values4 = ArrayList() val randomMultiplier = binding.seekBarY.progress * 100000f val sampleValues = getValues(100 + 2) for (i in startYear.. + private lateinit var dataSinus: MutableList private lateinit var binding: ActivityBarchartSinusBinding @@ -87,7 +87,7 @@ class BarChartActivitySinus : DemoBase(), OnSeekBarChangeListener { } private fun setData(count: Int) { - val entries = ArrayList() + val entries = ArrayList() for (i in 0..) { - val values = ArrayList() + val values = ArrayList() val colors: MutableList = ArrayList() val green = Color.rgb(110, 190, 102) @@ -95,7 +95,7 @@ class BarChartPositiveNegative : DemoBase() { for (i in dataList.indices) { val d = dataList[i] - val entry = BarEntry(d.xValue, d.yValue) + val entry = BarEntryFloat(d.xValue, d.yValue) values.add(entry) // specific colors @@ -136,7 +136,7 @@ class BarChartPositiveNegative : DemoBase() { private class ValueFormatter : IValueFormatter { private val mFormat: DecimalFormat = DecimalFormat("######.0") - override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { return mFormat.format(value.toDouble()) } } diff --git a/app/src/main/kotlin/info/appdev/chartexample/BubbleChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/BubbleChartActivity.kt index a478a0f64..7207e7744 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/BubbleChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/BubbleChartActivity.kt @@ -19,8 +19,8 @@ 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.Entry +import info.appdev.charting.data.BubbleEntryFloat +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.interfaces.datasets.IBubbleDataSet import info.appdev.charting.listener.OnChartValueSelectedListener @@ -82,14 +82,14 @@ class BubbleChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSel binding.tvXMax.text = count.toString() binding.tvYMax.text = range.toString() - val values1 = ArrayList() - val values2 = ArrayList() - val values3 = ArrayList() + val values1 = ArrayList() + val values2 = ArrayList() + val values3 = ArrayList() val sampleValues = getValues(100) for (i in 0..() + val values = ArrayList() val sampleValues = getValues(100) for (i in 0..() + val entries = ArrayList() - for (index in 0..() - val entries2 = ArrayList() + val entries1 = ArrayList() + val entries2 = ArrayList() for (index in 0..() + val entries = ArrayList() var index = 0f while (index < sampleCount) { - entries.add(Entry(index + 0.25f, values[(index * 2).roundToInt()]!!.toFloat() * 10 + 55)) + entries.add(EntryFloat(index + 0.25f, values[(index * 2).roundToInt()]!!.toFloat() * 10 + 55)) index += 0.5f } @@ -183,11 +183,11 @@ class CombinedChartActivity : DemoBase() { private fun generateCandleData(): CandleData { val d = CandleData() - val entries = ArrayList() + val entries = ArrayList() var index = 0 while (index < sampleCount) { - entries.add(CandleEntry(index + 1f, 90f, 70f, 85f, 75f)) + entries.add(CandleEntryFloat(index + 1f, 90f, 70f, 85f, 75f)) index += 2 } @@ -205,12 +205,12 @@ class CombinedChartActivity : DemoBase() { private fun generateBubbleData(): BubbleData { val bd = BubbleData() - val entries = ArrayList() + val entries = ArrayList() for (index in 0..() + val values = ArrayList() val sampleValues = getMuchValues(count) for (i in 0 until count) { val `val` = (sampleValues[i]!!.toFloat() * (range + 1)) + 20 - values.add(Entry(i.toFloat(), `val`)) + values.add(EntryFloat(i.toFloat(), `val`)) } val set1: LineDataSet diff --git a/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt b/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt index a3f09c098..1c004b4ee 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt @@ -5,7 +5,7 @@ import android.graphics.Color import android.graphics.DashPathEffect import androidx.core.content.ContextCompat import info.appdev.charting.charts.LineChart -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet import info.appdev.charting.formatter.IFillFormatter @@ -164,15 +164,15 @@ class DataTools { fun setData(context: Context, lineChart: LineChart, count: Int = VAL_COUNT, range: Float = VAL_RANGE) { Timber.d("count=$count range=$range") - val values = ArrayList() + val values = ArrayList() if (count == VAL_COUNT) { VAL_FIX.forEachIndexed { index, d -> - values.add(Entry(index.toFloat(), d.toFloat(), ContextCompat.getDrawable(context, R.drawable.star))) + values.add(EntryFloat(index.toFloat(), d.toFloat(), ContextCompat.getDrawable(context, R.drawable.star))) } } else { for (i in 0 until count) { val value = (Math.random() * range).toFloat() - 30 - values.add(Entry(i.toFloat(), value, ContextCompat.getDrawable(context, R.drawable.star))) + values.add(EntryFloat(i.toFloat(), value, ContextCompat.getDrawable(context, R.drawable.star))) } } lineChart.data?.let { @@ -190,7 +190,7 @@ class DataTools { } private fun createDataset( - values: ArrayList, + values: ArrayList, lineChart: LineChart, context: Context ) { diff --git a/app/src/main/kotlin/info/appdev/chartexample/DrawChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/DrawChartActivity.kt index 9194ee49c..826c3c09b 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/DrawChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/DrawChartActivity.kt @@ -10,7 +10,7 @@ import androidx.core.content.ContextCompat import info.appdev.chartexample.databinding.ActivityDrawChartBinding import info.appdev.chartexample.notimportant.DemoBase import info.appdev.charting.data.DataSet -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet import info.appdev.charting.highlight.Highlight @@ -57,7 +57,7 @@ class DrawChartActivity : DemoBase(), OnChartValueSelectedListener, OnDrawListen } private fun initWithDummyData() { - val values = ArrayList() + val values = ArrayList() // create a dataset and give it a type (0) val set1 = LineDataSet(values, "DataSet") @@ -116,15 +116,15 @@ class DrawChartActivity : DemoBase(), OnChartValueSelectedListener, OnDrawListen saveToGallery(binding.chart1, "DrawChartActivity") } - override fun onValueSelected(entry: Entry, highlight: Highlight) { - Timber.i(("Value: ${entry.y}, xIndex: ${entry.x}, DataSet index:${highlight.dataSetIndex}")) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { + Timber.i(("Value: ${entryFloat.y}, xIndex: ${entryFloat.x}, DataSet index:${highlight.dataSetIndex}")) } override fun onNothingSelected() = Unit /** callback for each new entry drawn with the finger */ - override fun onEntryAdded(entry: Entry) { - Timber.i(entry.toString()) + override fun onEntryAdded(entryFloat: EntryFloat) { + Timber.i(entryFloat.toString()) } /** callback when a DataSet has been drawn (when lifting the finger) */ @@ -135,7 +135,7 @@ class DrawChartActivity : DemoBase(), OnChartValueSelectedListener, OnDrawListen binding.chart1.data?.let { binding.chart1.legendRenderer?.computeLegend(it) } } - override fun onEntryMoved(entry: Entry) { - Timber.i("Point moved $entry") + override fun onEntryMoved(entryFloat: EntryFloat) { + Timber.i("Point moved $entryFloat") } } diff --git a/app/src/main/kotlin/info/appdev/chartexample/DynamicalAddingActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/DynamicalAddingActivity.kt index aa371611f..82b3f64ae 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/DynamicalAddingActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/DynamicalAddingActivity.kt @@ -14,7 +14,7 @@ import info.appdev.chartexample.DataTools.Companion.getValues import info.appdev.chartexample.databinding.ActivityLinechartNoseekbarBinding import info.appdev.chartexample.notimportant.DemoBase import info.appdev.charting.components.YAxis.AxisDependency -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet import info.appdev.charting.highlight.Highlight @@ -66,7 +66,7 @@ class DynamicalAddingActivity : DemoBase(), OnChartValueSelectedListener { val value = (sampleValues[cycleValue]!!.toFloat() * 50) + 50f * (lastDataSetIndex + 1) - data.addEntry(Entry(lastSet.entryCount.toFloat(), value), lastDataSetIndex) + data.addEntry(EntryFloat(lastSet.entryCount.toFloat(), value), lastDataSetIndex) data.notifyDataChanged() // let the chart know it's data has changed @@ -109,12 +109,12 @@ class DynamicalAddingActivity : DemoBase(), OnChartValueSelectedListener { val count = (data.dataSetCount + 1) val amount = data.getDataSetByIndex(0)?.entryCount ?: 0 - val values = ArrayList() + val values = ArrayList() for (i in 0..() + val valuesArray1 = ArrayList() val sampleValues = getValues(count + 2) for (i in 0..() + val valuesArray2 = ArrayList() for (i in 0..(200) + val entries = kotlin.collections.ArrayList(200) val k = 2.5f for (i in 0 until 200) { - entries.add(Entry(i.toFloat(), i * k)) + entries.add(EntryFloat(i.toFloat(), i * k)) } val dataSet = LineDataSet(entries, "Linear").apply { diff --git a/app/src/main/kotlin/info/appdev/chartexample/HalfPieChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/HalfPieChartActivity.kt index ef11617bb..41d041fa1 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/HalfPieChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/HalfPieChartActivity.kt @@ -20,7 +20,7 @@ import info.appdev.charting.animation.Easing import info.appdev.charting.components.Legend import info.appdev.charting.data.PieData import info.appdev.charting.data.PieDataSet -import info.appdev.charting.data.PieEntry +import info.appdev.charting.data.PieEntryFloat import info.appdev.charting.formatter.PercentFormatter import info.appdev.charting.utils.ColorTemplate @@ -83,11 +83,11 @@ class HalfPieChartActivity : DemoBase() { private fun setData(range: Float) { val count = 4 - val values = ArrayList() + val values = ArrayList() val sampleValues = getValues(count) for (i in 0..() + val values = ArrayList() val sampleValues = getValues(100) for (i in 0..() + val values = ArrayList() val sampleValues = getValues(count + 2) for (i in 0..() + val entries = ArrayList() val sampleValues = getValues(count + 2) for (i in 0..() + val values = ArrayList() val sampleValues = getValues(count) for (i in 0..() + val values1 = ArrayList() val sampleValues = getValues(count) for (i in 0..() + val values2 = ArrayList() for (i in 0..() + val values3 = ArrayList() for (i in 0..() + val values = ArrayList() // count = hours val to = (now + count).toFloat() @@ -118,7 +118,7 @@ class LineChartTimeActivity : DemoBase(), OnSeekBarChangeListener { (valuesData[x.roundToInt()])!!.toFloat() * 50 + 50 else (Math.random() * 50 + 50).toFloat() // manually triggered - values.add(Entry(x, y)) // add one entry per hour + values.add(EntryFloat(x, y)) // add one entry per hour x++ } diff --git a/app/src/main/kotlin/info/appdev/chartexample/ListViewBarChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/ListViewBarChartActivity.kt index 08b1c2040..af361132d 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/ListViewBarChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/ListViewBarChartActivity.kt @@ -19,7 +19,7 @@ import info.appdev.charting.charts.BarChart import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet -import info.appdev.charting.data.BarEntry +import info.appdev.charting.data.BarEntryFloat import info.appdev.charting.interfaces.datasets.IBarDataSet import info.appdev.charting.utils.ColorTemplate @@ -110,11 +110,11 @@ class ListViewBarChartActivity : DemoBase() { */ private fun generateData(cnt: Int): BarData { val count = 12 - val entries = ArrayList() + val entries = ArrayList() val sampleValues = getValues(count) for (i in 0..() + val values1 = ArrayList() val count = 12 val sampleValues = getValues(count) for (i in 0..() + val values2 = ArrayList() for (i in 0..() + val entries = ArrayList() val sampleValues = getValues(count) for (i in 0..() + val entries = ArrayList() val sampleValues = getValues(cnt) for (i in 0..() for (datasetNumber in 0..2) { - val values = ArrayList() + val values = ArrayList() val sampleValues = when (datasetNumber) { 1 -> getValues(100).reversedArray() 2 -> generateSineWaves(3, 30).toTypedArray() @@ -96,7 +96,7 @@ class MultiLineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartGestu for (i in 0..() + val values = ArrayList() val sampleValues = getMuchValues(count) for (i in 0..() + val entries = ArrayList() val sampleValues = getValues(100) // NOTE: The order of the entries when being added to the entries array determines their position around the center of // the chart. for (i in 0..() + val entries = ArrayList() val sampleValues = getValues(100) // NOTE: The order of the entries when being added to the entries array determines their position around the center of // the chart. for (i in 0..() + val entries = ArrayList() // NOTE: The order of the entries when being added to the entries array determines their position around the center of // the chart. for (i in 0..() - val entries2 = ArrayList() + val entries1 = ArrayList() + val entries2 = ArrayList() // NOTE: The order of the entries when being added to the entries array determines their position around the center of // the chart. for (i in 0..() - val values2 = ArrayList() - val values3 = ArrayList() + val values1 = ArrayList() + val values2 = ArrayList() + val values3 = ArrayList() val sampleValues = getValues(100 + 2) for (i in 0..() + val values = ArrayList() for (i in 0..() + val values = ArrayList() val sampleValues = getValues(100) for (i in 0 until count) { val `val` = (sampleValues[i]!!.toFloat() * range) + 3 - values.add(Entry(i.toFloat(), `val`)) + values.add(EntryFloat(i.toFloat(), `val`)) } binding.chart1.data?.let { if (it.dataSetCount > 0) { @@ -245,7 +245,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, } } - private fun createDataset(values: ArrayList) { + private fun createDataset(values: ArrayList) { // create a dataset and give it a type val set11 = LineDataSet(values, "DataSet 1") @@ -316,8 +316,8 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, Timber.i("dX: $dX, dY: $dY") } - override fun onValueSelected(entry: Entry, highlight: Highlight) { - Timber.i(entry.toString()) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { + Timber.i(entryFloat.toString()) Timber.i("LOWHIGH low: ${binding.chart1.lowestVisibleX}, high: ${binding.chart1.highestVisibleX}") Timber.i("MIN MAX xmin: ${binding.chart1.xChartMin}, xmax: ${binding.chart1.xChartMax}, ymin: ${binding.chart1.yChartMin}, ymax: ${binding.chart1.yChartMax}") } diff --git a/app/src/main/kotlin/info/appdev/chartexample/StackedBarActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/StackedBarActivity.kt index 443cc16ca..b28b1ce4c 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/StackedBarActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/StackedBarActivity.kt @@ -21,8 +21,8 @@ import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet -import info.appdev.charting.data.BarEntry -import info.appdev.charting.data.Entry +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.interfaces.datasets.IBarDataSet import info.appdev.charting.listener.OnChartValueSelectedListener @@ -91,7 +91,7 @@ class StackedBarActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSele binding.tvXMax.text = binding.seekBarX.progress.toString() binding.tvYMax.text = binding.seekBarY.progress.toString() - val values = ArrayList() + val values = ArrayList() val sampleValues = getValues(100 + 2) for (i in 0..() - values.add(BarEntry(5f, floatArrayOf(-10f, 10f))) - values.add(BarEntry(15f, floatArrayOf(-12f, 13f))) - values.add(BarEntry(25f, floatArrayOf(-15f, 15f))) - values.add(BarEntry(35f, floatArrayOf(-17f, 17f))) - values.add(BarEntry(45f, floatArrayOf(-19f, 20f))) - values.add(BarEntry(45f, floatArrayOf(-19f, 20f), ResourcesCompat.getDrawable(resources, R.drawable.star, null))) - values.add(BarEntry(55f, floatArrayOf(-19f, 19f))) - values.add(BarEntry(65f, floatArrayOf(-16f, 16f))) - values.add(BarEntry(75f, floatArrayOf(-13f, 14f))) - values.add(BarEntry(85f, floatArrayOf(-10f, 11f))) - values.add(BarEntry(95f, floatArrayOf(-5f, 6f))) - values.add(BarEntry(105f, floatArrayOf(-1f, 2f))) + val values = ArrayList() + values.add(BarEntryFloat(5f, floatArrayOf(-10f, 10f))) + values.add(BarEntryFloat(15f, floatArrayOf(-12f, 13f))) + values.add(BarEntryFloat(25f, floatArrayOf(-15f, 15f))) + values.add(BarEntryFloat(35f, floatArrayOf(-17f, 17f))) + values.add(BarEntryFloat(45f, floatArrayOf(-19f, 20f))) + values.add(BarEntryFloat(45f, floatArrayOf(-19f, 20f), ResourcesCompat.getDrawable(resources, R.drawable.star, null))) + values.add(BarEntryFloat(55f, floatArrayOf(-19f, 19f))) + values.add(BarEntryFloat(65f, floatArrayOf(-16f, 16f))) + values.add(BarEntryFloat(75f, floatArrayOf(-13f, 14f))) + values.add(BarEntryFloat(85f, floatArrayOf(-10f, 11f))) + values.add(BarEntryFloat(95f, floatArrayOf(-5f, 6f))) + values.add(BarEntryFloat(105f, floatArrayOf(-1f, 2f))) val set = BarDataSet(values, "Age Distribution") set.isDrawIcons = false @@ -194,8 +194,8 @@ class StackedBarActivityNegative : DemoBase(), OnChartValueSelectedListener { saveToGallery(binding.chart1, "StackedBarActivityNegative") } - override fun onValueSelected(entry: Entry, highlight: Highlight) { - val barEntry = entry as BarEntry + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { + val barEntry = entryFloat as BarEntryFloat Timber.i("Value: ${abs(barEntry.yVals!![highlight.stackIndex])}") } @@ -205,7 +205,7 @@ class StackedBarActivityNegative : DemoBase(), OnChartValueSelectedListener { private val decimalFormat: DecimalFormat = DecimalFormat("###") // data - override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { return decimalFormat.format(abs(value).toDouble()) + "m" } diff --git a/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt index 7b71841fd..096c308e7 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt @@ -17,7 +17,7 @@ import info.appdev.charting.components.Description import info.appdev.charting.components.Legend.LegendForm import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.components.YAxis -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet import info.appdev.charting.formatter.IFillFormatter @@ -97,24 +97,24 @@ class TimeLineActivity : DemoBase() { val sampleEntries = if (sinus) generateSineWaves(3, 30).mapIndexed { index, data -> val valueY = (data.toFloat() * range) + 50 - Entry(timeOffset + index.toFloat() * 1000, valueY) + EntryFloat(timeOffset + index.toFloat() * 1000, valueY) } else { - var previousEntry: Entry? = null + var previousEntryFloat: EntryFloat? = null getSawtoothValues(14).mapIndexed { index, data -> val valueY = data.toFloat() * 20 - val entry = previousEntry?.let { + val entryFloat = previousEntryFloat?.let { // nay third value is 0, so we add here more than 1 second, otherwise we have a one-second entry if (index % 3 == 0) { - Entry(it.x + 3000, valueY) + EntryFloat(it.x + 3000, valueY) } else - Entry(it.x + 1000, valueY) + EntryFloat(it.x + 1000, valueY) } ?: run { - Entry(timeOffset + index.toFloat() * 1000, valueY) + EntryFloat(timeOffset + index.toFloat() * 1000, valueY) } - previousEntry = entry + previousEntryFloat = entryFloat // Now you can use 'prev' which holds the previous Entry - entry + entryFloat } } @@ -209,9 +209,9 @@ class TimeLineActivity : DemoBase() { val first = this[0] val second = this[1] // needed to get time diff val last = this[size - 1] - val timeDiff = (second as Entry).x - (first as Entry).x + val timeDiff = (second as EntryFloat).x - (first as EntryFloat).x removeAt(0) - first.x = (last as Entry).x + timeDiff + first.x = (last as EntryFloat).x + timeDiff add(first) } diff --git a/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarComposeActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarComposeActivity.kt index af8d6733d..713bce742 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarComposeActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarComposeActivity.kt @@ -44,8 +44,8 @@ import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet -import info.appdev.charting.data.BarEntry -import info.appdev.charting.data.Entry +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.interfaces.datasets.IBarDataSet import info.appdev.charting.listener.OnChartValueSelectedListener @@ -268,14 +268,14 @@ class HorizontalBarComposeActivity : DemoBaseCompose() { private fun setupChart(chart: HorizontalBarChart) { chart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) { + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { val bounds = RectF() - chart.getBarBounds(entry as BarEntry, bounds) + chart.getBarBounds(entryFloat as BarEntryFloat, bounds) val data = chart.barData if (data != null) { val position = chart.getPosition( - entry, data.getDataSetByIndex(highlight.dataSetIndex)?.axisDependency + entryFloat, data.getDataSetByIndex(highlight.dataSetIndex)?.axisDependency ) Timber.tag("bounds $bounds") @@ -335,12 +335,12 @@ class HorizontalBarComposeActivity : DemoBaseCompose() { val barWidth = 9f val spaceForBar = 10f - val values = ArrayList() + val values = ArrayList() val sampleValues = getValues(100) for (i in 0..() + val values = ArrayList() val sampleValues = getValues(100) for (i in 0..() for (datasetNumber in 0..2) { - val values = ArrayList() + val values = ArrayList() val sampleValues = when (datasetNumber) { 1 -> getValues(100).reversedArray() 2 -> generateSineWaves(3, 30).toTypedArray() @@ -338,7 +338,7 @@ class MultiLineComposeActivity : DemoBaseCompose() { for (i in 0.. - val myIndex = dataset?.getEntryIndex(entry) + val dataset = this.chartView?.data?.dataSets[0] as? IDataSet + val myIndex = dataset?.getEntryIndex(entryFloat) val nextEntry = myIndex?.let { if (it < dataset.entryCount - 1) dataset.getEntryForIndex(myIndex + 1) @@ -30,9 +30,9 @@ class TimeMarkerView(context: Context?, layoutResource: Int, val format: String null } ?: run { null } - val duration = if (nextEntry != null) " - duration:${(nextEntry.x - entry.x)}" else "" - tvContent.text = "${simpleDateFormat.format(entry.x)}$duration" - super.refreshContent(entry, highlight) + val duration = if (nextEntry != null) " - duration:${(nextEntry.x - entryFloat.x)}" else "" + tvContent.text = "${simpleDateFormat.format(entryFloat.x)}$duration" + super.refreshContent(entryFloat, highlight) } override var offset: PointF = PointF() diff --git a/app/src/main/kotlin/info/appdev/chartexample/custom/XYMarkerView.kt b/app/src/main/kotlin/info/appdev/chartexample/custom/XYMarkerView.kt index ffc7ce814..68a9ba4db 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/custom/XYMarkerView.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/custom/XYMarkerView.kt @@ -5,7 +5,7 @@ import android.content.Context import android.widget.TextView import info.appdev.chartexample.R import info.appdev.charting.components.MarkerView -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.formatter.IAxisValueFormatter import info.appdev.charting.highlight.Highlight import info.appdev.charting.utils.PointF @@ -21,10 +21,10 @@ class XYMarkerView(context: Context?, private val xAxisValueFormatter: IAxisValu private val format: DecimalFormat = DecimalFormat("###.0") // runs every time the MarkerView is redrawn, can be used to update the content (user-interface) - override fun refreshContent(entry: Entry, highlight: Highlight) { - tvContent.text = String.format("x: %s, y: %s", xAxisValueFormatter.getFormattedValue(entry.x, null), format.format(entry.y.toDouble())) + override fun refreshContent(entryFloat: EntryFloat, highlight: Highlight) { + tvContent.text = String.format("x: %s, y: %s", xAxisValueFormatter.getFormattedValue(entryFloat.x, null), format.format(entryFloat.y.toDouble())) - super.refreshContent(entry, highlight) + super.refreshContent(entryFloat, highlight) } override var offset: PointF = PointF() diff --git a/app/src/main/kotlin/info/appdev/chartexample/formatter/MyValueFormatter.kt b/app/src/main/kotlin/info/appdev/chartexample/formatter/MyValueFormatter.kt index a32ce5413..a61d8c58c 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/formatter/MyValueFormatter.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/formatter/MyValueFormatter.kt @@ -1,6 +1,6 @@ package info.appdev.chartexample.formatter -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.formatter.IValueFormatter import info.appdev.charting.utils.ViewPortHandler import java.text.DecimalFormat @@ -8,7 +8,7 @@ import java.text.DecimalFormat class MyValueFormatter : IValueFormatter { private val decimalFormat = DecimalFormat("###,###,###,##0.0") - override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { return decimalFormat.format(value.toDouble()) + " $" } } diff --git a/app/src/main/kotlin/info/appdev/chartexample/fragments/SimpleFragment.kt b/app/src/main/kotlin/info/appdev/chartexample/fragments/SimpleFragment.kt index 6a9053ea5..e5f5797ef 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/fragments/SimpleFragment.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/fragments/SimpleFragment.kt @@ -11,13 +11,13 @@ import info.appdev.chartexample.DataTools.Companion.getValues import info.appdev.charting.charts.ScatterChart.ScatterShape import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet -import info.appdev.charting.data.BarEntry -import info.appdev.charting.data.Entry +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet import info.appdev.charting.data.PieData import info.appdev.charting.data.PieDataSet -import info.appdev.charting.data.PieEntry +import info.appdev.charting.data.PieEntryFloat import info.appdev.charting.data.ScatterData import info.appdev.charting.data.ScatterDataSet import info.appdev.charting.interfaces.datasets.IBarDataSet @@ -41,10 +41,10 @@ abstract class SimpleFragment : Fragment() { val sets = ArrayList() for (i in 0..() + val entries = ArrayList() for (j in 0..() + val entries = ArrayList() for (j in 0..() + val entries1 = ArrayList() for (i in 0.., BarDataProvider { * Returns the bounding box of the specified Entry in the specified DataSet. Returns null if the Entry could not be * found in the charts data. Performance-intensive code should use void getBarBounds(BarEntry, RectF) instead. */ - fun getBarBounds(barEntry: BarEntry): RectF { + fun getBarBounds(barEntry: BarEntryFloat): RectF { val bounds = RectF() getBarBounds(barEntry, bounds) @@ -157,7 +157,7 @@ open class BarChart : BarLineChartBase, BarDataProvider { * The passed outputRect will be assigned the values of the bounding box of the specified Entry in the specified DataSet. * The rect will be assigned Float.MIN_VALUE in all locations if the Entry could not be found in the charts data. */ - open fun getBarBounds(barEntry: BarEntry, outputRect: RectF) { + open fun getBarBounds(barEntry: BarEntryFloat, outputRect: RectF) { mData?.let { barData -> val set = barData.getDataSetForEntry(barEntry) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt index a4eab0f84..cf38afb16 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt @@ -16,7 +16,7 @@ import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.components.YAxis import info.appdev.charting.components.YAxis.AxisDependency import info.appdev.charting.data.BarLineScatterCandleBubbleData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.ChartHighlighter import info.appdev.charting.interfaces.dataprovider.base.BarLineScatterCandleBubbleDataProvider import info.appdev.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet @@ -46,7 +46,7 @@ import kotlin.math.min */ @Suppress("unused") @SuppressLint("RtlHardcoded") -abstract class BarLineChartBase>> : Chart, +abstract class BarLineChartBase>> : Chart, BarLineScatterCandleBubbleDataProvider { /** * the maximum number of entries to which values will be drawn @@ -956,7 +956,7 @@ abstract class BarLineChartBase>> : ViewGroup, IBaseProvider { +abstract class Chart>> : ViewGroup, IBaseProvider { /** * Returns true if log-output is enabled for the chart, fals if not. */ @@ -527,7 +527,7 @@ abstract class Chart>> : ViewGroup, IBaseP */ fun highlightValue(high: Highlight?, callListener: Boolean) { var high = high - var entry: Entry? = null + var entryFloat: EntryFloat? = null if (high == null) { this.highlighted = null @@ -536,8 +536,8 @@ abstract class Chart>> : ViewGroup, IBaseP Timber.i("Highlighted: $high") } - entry = mData!!.getEntryForHighlight(high) - if (entry == null) { + entryFloat = mData!!.getEntryForHighlight(high) + if (entryFloat == null) { this.highlighted = null high = null } else { @@ -554,7 +554,7 @@ abstract class Chart>> : ViewGroup, IBaseP listener.onNothingSelected() } else { // notify the listener - listener.onValueSelected(entry!!, high!!) + listener.onValueSelected(entryFloat!!, high!!) } } // redraw the chart @@ -623,7 +623,7 @@ abstract class Chart>> : ViewGroup, IBaseP // Cast to non-star-projected type to allow calling getEntryIndex @Suppress("UNCHECKED_CAST") - val set = dataset as IDataSet + val set = dataset as IDataSet val entryIndex = set.getEntryIndex(entry) if (entryIndex > set.entryCount * mAnimator.phaseX) { diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/CombinedChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/CombinedChart.kt index 013bfc881..5a008d234 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/CombinedChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/CombinedChart.kt @@ -7,7 +7,7 @@ import info.appdev.charting.data.BarData import info.appdev.charting.data.BubbleData import info.appdev.charting.data.CandleData import info.appdev.charting.data.CombinedData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.ScatterData import info.appdev.charting.highlight.CombinedHighlighter @@ -488,7 +488,7 @@ open class CombinedChart : BarLineChartBase, CombinedDataProvider } @Suppress("UNCHECKED_CAST") - val set = dataset as IDataSet + val set = dataset as IDataSet val entryIndex = set.getEntryIndex(entry) // make sure entry not null diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/HorizontalBarChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/HorizontalBarChart.kt index 2ed51233d..33506ae2f 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/HorizontalBarChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/HorizontalBarChart.kt @@ -8,8 +8,8 @@ import info.appdev.charting.components.Legend.LegendOrientation import info.appdev.charting.components.Legend.LegendVerticalAlignment import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.components.YAxis.AxisDependency -import info.appdev.charting.data.BarEntry -import info.appdev.charting.data.Entry +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.highlight.HorizontalBarHighlighter import info.appdev.charting.renderer.HorizontalBarChartRenderer @@ -206,7 +206,7 @@ open class HorizontalBarChart : BarChart { return floatArrayOf(high.drawY, high.drawX) } - override fun getBarBounds(barEntry: BarEntry, outputRect: RectF) { + override fun getBarBounds(barEntry: BarEntryFloat, outputRect: RectF) { mData?.let { data -> val set = data.getDataSetForEntry(barEntry) @@ -231,7 +231,7 @@ open class HorizontalBarChart : BarChart { /** * Returns a recyclable PointF instance. */ - override fun getPosition(e: Entry?, axis: AxisDependency?): PointF? { + override fun getPosition(e: EntryFloat?, axis: AxisDependency?): PointF? { if (e == null) { return null } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/PieRadarChartBase.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/PieRadarChartBase.kt index 76b043d60..a5d0d82f5 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/PieRadarChartBase.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/PieRadarChartBase.kt @@ -10,7 +10,7 @@ import info.appdev.charting.components.Legend.LegendHorizontalAlignment import info.appdev.charting.components.Legend.LegendOrientation import info.appdev.charting.components.Legend.LegendVerticalAlignment import info.appdev.charting.data.ChartData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.interfaces.datasets.IDataSet import info.appdev.charting.listener.PieRadarChartTouchListener import info.appdev.charting.utils.PointF @@ -30,7 +30,7 @@ import kotlin.math.sqrt /** * Baseclass of PieChart and RadarChart. */ -abstract class PieRadarChartBase>> +abstract class PieRadarChartBase>> : Chart { /** * holds the normalized version of the current rotation angle of the chart diff --git a/chartLib/src/main/kotlin/info/appdev/charting/components/IMarker.kt b/chartLib/src/main/kotlin/info/appdev/charting/components/IMarker.kt index 61af265af..52cc7dc8a 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/components/IMarker.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/components/IMarker.kt @@ -1,7 +1,7 @@ package info.appdev.charting.components import android.graphics.Canvas -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.utils.PointF @@ -27,12 +27,12 @@ interface IMarker { /** * This method enables a specified custom IMarker to update it's content every time the IMarker is redrawn. * - * @param entry The Entry the IMarker belongs to. This can also be any subclass of Entry, like BarEntry or + * @param entryFloat The Entry the IMarker belongs to. This can also be any subclass of Entry, like BarEntry or * CandleEntry, simply cast it at runtime. * @param highlight The highlight object contains information about the highlighted value such as it's dataset-index, the * selected range or stack-index (only stacked bar entries). */ - fun refreshContent(entry: Entry, highlight: Highlight) + fun refreshContent(entryFloat: EntryFloat, highlight: Highlight) /** * Draws the IMarker on the given position on the screen with the given Canvas object. diff --git a/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerImage.kt b/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerImage.kt index c6ce67026..0db59d9b9 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerImage.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerImage.kt @@ -5,7 +5,7 @@ import android.graphics.Canvas import android.graphics.Rect import android.graphics.drawable.Drawable import info.appdev.charting.charts.Chart -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.utils.FSize import info.appdev.charting.utils.PointF @@ -94,7 +94,7 @@ class MarkerImage(private var mContext: Context, drawableResourceId: Int) : IMar return mOffset2 } - override fun refreshContent(entry: Entry, highlight: Highlight) = Unit + override fun refreshContent(entryFloat: EntryFloat, highlight: Highlight) = Unit override fun draw(canvas: Canvas, posX: Float, posY: Float) { if (drawable == null) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerView.kt b/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerView.kt index 7592d71e4..f7cf73ea0 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerView.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerView.kt @@ -5,7 +5,7 @@ import android.graphics.Canvas import android.view.LayoutInflater import android.widget.RelativeLayout import info.appdev.charting.charts.Chart -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.utils.PointF import java.lang.ref.WeakReference @@ -74,7 +74,7 @@ open class MarkerView(context: Context?, layoutResource: Int) : RelativeLayout(c return mOffset2 } - override fun refreshContent(entry: Entry, highlight: Highlight) { + override fun refreshContent(entryFloat: EntryFloat, highlight: Highlight) { measure( MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarDataSet.kt index c4114435e..1e94ef0ca 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarDataSet.kt @@ -5,7 +5,7 @@ import androidx.annotation.ColorInt import info.appdev.charting.interfaces.datasets.IBarDataSet import info.appdev.charting.utils.Fill -open class BarDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), IBarDataSet { +open class BarDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), IBarDataSet { /** * the maximum number of bars that are stacked upon each other, this value * is calculated from the Entries that are added to the DataSet @@ -50,8 +50,8 @@ open class BarDataSet(yVals: MutableList, label: String) : BarLineScat calcEntryCountIncludingStacks(yVals) } - override fun copy(): DataSet? { - val entries: MutableList = mutableListOf() + override fun copy(): DataSet? { + val entries: MutableList = mutableListOf() for (i in entriesInternal.indices) { entries.add(entriesInternal[i].copy()) } @@ -101,7 +101,7 @@ open class BarDataSet(yVals: MutableList, label: String) : BarLineScat * Calculates the total number of entries this DataSet represents, including * stacks. All values belonging to a stack are calculated separately. */ - private fun calcEntryCountIncludingStacks(yVals: MutableList) { + private fun calcEntryCountIncludingStacks(yVals: MutableList) { this.entryCountStacks = 0 for (i in yVals.indices) { @@ -115,7 +115,7 @@ open class BarDataSet(yVals: MutableList, label: String) : BarLineScat /** * calculates the maximum stackSize that occurs in the Entries array of this DataSet */ - private fun calcStackSize(yVals: MutableList) { + private fun calcStackSize(yVals: MutableList) { for (i in yVals.indices) { val vals = yVals[i].yVals @@ -123,7 +123,7 @@ open class BarDataSet(yVals: MutableList, label: String) : BarLineScat } } - override fun calcMinMax(entry: BarEntry) { + override fun calcMinMax(entry: BarEntryFloat) { if (!entry.y.isNaN()) { if (entry.yVals == null) { if (entry.y < yMin) yMin = entry.y diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntryFloat.kt similarity index 97% rename from chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt rename to chartLib/src/main/kotlin/info/appdev/charting/data/BarEntryFloat.kt index 1e4f78b3d..1ce910a01 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntryFloat.kt @@ -9,7 +9,7 @@ import kotlin.math.abs * Entry class for the BarChart. (especially stacked bars) */ @SuppressLint("ParcelCreator") -open class BarEntry : Entry { +open class BarEntryFloat : EntryFloat { /** * Returns the stacked values this BarEntry represents, or null, if only a single value is represented (then, use * getY()). @@ -123,8 +123,8 @@ open class BarEntry : Entry { /** * Returns an exact copy of the BarEntry. */ - override fun copy(): BarEntry { - val copied = BarEntry(x, y, data) + override fun copy(): BarEntryFloat { + val copied = BarEntryFloat(x, y, data) copied.setVals(this.yVals) return copied } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleData.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleData.kt index 96c08ef04..f58db3860 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleData.kt @@ -7,8 +7,8 @@ import info.appdev.charting.interfaces.datasets.IDataSet * Baseclass for all Line, Bar, Scatter, Candle and Bubble data. */ abstract class BarLineScatterCandleBubbleData : ChartData<@UnsafeVariance T> - where T : IDataSet, - T : IBarLineScatterCandleBubbleDataSet { + where T : IDataSet, + T : IBarLineScatterCandleBubbleDataSet { constructor() : super() diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleDataSet.kt index fbc22d82e..40096e196 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleDataSet.kt @@ -7,7 +7,7 @@ import info.appdev.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataS /** * Baseclass of all DataSets for Bar-, Line-, Scatter- and CandleStickChart. */ -abstract class BarLineScatterCandleBubbleDataSet(yVals: MutableList, label: String) : +abstract class BarLineScatterCandleBubbleDataSet(yVals: MutableList, label: String) : DataSet(yVals, label), IBarLineScatterCandleBubbleDataSet { /** * Sets the color that is used for drawing the highlight indicators. diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt index 10208d61f..6ba5af063 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt @@ -19,7 +19,7 @@ import info.appdev.charting.utils.convertDpToPixel * This is the base dataset of all DataSets. Its purpose is to implement critical methods * provided by the IDataSet interface. */ -abstract class BaseDataSet() : IDataSet { +abstract class BaseDataSet() : IDataSet { /** * List representing all colors that are used for this DataSet */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleDataSet.kt index 05c8183c6..4bf313283 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleDataSet.kt @@ -3,13 +3,13 @@ package info.appdev.charting.data import info.appdev.charting.interfaces.datasets.IBubbleDataSet import info.appdev.charting.utils.convertDpToPixel -open class BubbleDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), IBubbleDataSet { +open class BubbleDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), IBubbleDataSet { protected var mMaxSize: Float = 0f protected var mNormalizeSize: Boolean = true private var mHighlightCircleWidth = 2.5f - override fun calcMinMax(entry: BubbleEntry) { + override fun calcMinMax(entry: BubbleEntryFloat) { super.calcMinMax(entry) val size = entry.size @@ -19,8 +19,8 @@ open class BubbleDataSet(yVals: MutableList, label: String) : BarLi } } - override fun copy(): DataSet { - val entries: MutableList = ArrayList() + override fun copy(): DataSet { + val entries: MutableList = ArrayList() for (i in entriesInternal.indices) { entries.add(entriesInternal[i].copy()) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntryFloat.kt similarity index 90% rename from chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntry.kt rename to chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntryFloat.kt index 0ea928773..8cba5b64e 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BubbleEntryFloat.kt @@ -8,7 +8,7 @@ import android.graphics.drawable.Drawable * chart implementation: Copyright 2015 Pierre-Marc Airoldi Licensed under Apache License 2.0 */ @SuppressLint("ParcelCreator") -class BubbleEntry : Entry { +class BubbleEntryFloat : EntryFloat { /** * Returns the size of this entry (the size of the bubble). */ @@ -49,7 +49,7 @@ class BubbleEntry : Entry { this.size = size } - override fun copy(): BubbleEntry { - return BubbleEntry(x, y, this.size, data) + override fun copy(): BubbleEntryFloat { + return BubbleEntryFloat(x, y, this.size, data) } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/CandleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/CandleDataSet.kt index 685d9afac..79e4d142d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/CandleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/CandleDataSet.kt @@ -9,7 +9,7 @@ import info.appdev.charting.utils.convertDpToPixel /** * DataSet for the CandleStickChart. */ -open class CandleDataSet(yVals: MutableList, label: String = "") : LineScatterCandleRadarDataSet(yVals, label), ICandleDataSet { +open class CandleDataSet(yVals: MutableList, label: String = "") : LineScatterCandleRadarDataSet(yVals, label), ICandleDataSet { /** * the width of the shadow of the candle */ @@ -69,8 +69,8 @@ open class CandleDataSet(yVals: MutableList, label: String = "") : @ColorInt protected var mShadowColor: Int = ColorTemplate.COLOR_SKIP - override fun copy(): DataSet { - val entries: MutableList = mutableListOf() + override fun copy(): DataSet { + val entries: MutableList = mutableListOf() for (i in entriesInternal.indices) { entries.add(entriesInternal[i].copy()) } @@ -95,7 +95,7 @@ open class CandleDataSet(yVals: MutableList, label: String = "") : candleDataSet.mShadowColor = mShadowColor } - override fun calcMinMax(entry: CandleEntry) { + override fun calcMinMax(entry: CandleEntryFloat) { entry.let { if (entry.low < yMin) yMin = entry.low @@ -104,7 +104,7 @@ open class CandleDataSet(yVals: MutableList, label: String = "") : calcMinMaxX(entry) } - override fun calcMinMaxY(entry: CandleEntry) { + override fun calcMinMaxY(entry: CandleEntryFloat) { entry.let { if (entry.high < yMin) yMin = entry.high diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntryFloat.kt similarity index 95% rename from chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntry.kt rename to chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntryFloat.kt index 82c07b47c..a138d1881 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/CandleEntryFloat.kt @@ -8,7 +8,7 @@ import kotlin.math.abs * Subclass of Entry that holds all values for one entry in a CandleStickChart. */ @SuppressLint("ParcelCreator") -class CandleEntry : Entry { +class CandleEntryFloat : EntryFloat { /** * Returns the upper shadows highest value. */ @@ -121,7 +121,7 @@ class CandleEntry : Entry { super.y = value } - override fun copy(): CandleEntry { - return CandleEntry(x, this.high, this.low, this.open, this.close, data) + override fun copy(): CandleEntryFloat { + return CandleEntryFloat(x, this.high, this.low, this.open, this.close, data) } } 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 698d142be..7902f3ecd 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt @@ -12,7 +12,7 @@ import java.io.Serializable * 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 { +abstract class ChartData> : Serializable { /** * maximum y-value in the value array across all axes */ @@ -243,7 +243,7 @@ abstract class ChartData> : Serializable { * Get the Entry for a corresponding highlight object * @return the entry that is highlighted */ - open fun getEntryForHighlight(highlight: Highlight): Entry? { + open fun getEntryForHighlight(highlight: Highlight): EntryFloat? { return if (highlight.dataSetIndex >= dataSets.size) { null } else { @@ -326,17 +326,17 @@ abstract class ChartData> : Serializable { * Entries are added to the end of the list. */ @Suppress("UNCHECKED_CAST") - fun addEntry(entry: Entry, dataSetIndex: Int) { + fun addEntry(entryFloat: EntryFloat, 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)) { + val dataSet = set as IDataSet + if (!dataSet.addEntry(entryFloat)) { return } - calcMinMax(entry, set.axisDependency) + calcMinMax(entryFloat, set.axisDependency) } else { Timber.e("Cannot add Entry because dataSetIndex too high or too low.") } @@ -345,7 +345,7 @@ abstract class ChartData> : Serializable { /** * Adjusts the current minimum and maximum values based on the provided Entry object. */ - protected fun calcMinMax(e: Entry, axis: AxisDependency?) { + protected fun calcMinMax(e: EntryFloat, axis: AxisDependency?) { if (this.yMax < e.y) { this.yMax = e.y } @@ -416,7 +416,7 @@ abstract class ChartData> : Serializable { * Removes the given Entry object from the DataSet at the specified index. */ @Suppress("UNCHECKED_CAST") - open fun removeEntry(entry: Entry, dataSetIndex: Int): Boolean { + open fun removeEntry(entryFloat: EntryFloat, dataSetIndex: Int): Boolean { // entry null, out of bounds if (dataSetIndex >= dataSets.size) { return false @@ -425,8 +425,8 @@ abstract class ChartData> : Serializable { val set: T = dataSets[dataSetIndex] // remove the entry from the dataset - val dataSet = set as IDataSet - val removed: Boolean = dataSet.removeEntry(entry) + val dataSet = set as IDataSet + val removed: Boolean = dataSet.removeEntry(entryFloat) if (removed) { notifyDataChanged() @@ -446,15 +446,15 @@ abstract class ChartData> : Serializable { } val dataSet: IDataSet<*> = dataSets[dataSetIndex] - val entry: Entry = dataSet.getEntryForXValue(xValue, Float.NaN) ?: return false + val entryFloat: EntryFloat = dataSet.getEntryForXValue(xValue, Float.NaN) ?: return false - return removeEntry(entry, dataSetIndex) + return removeEntry(entryFloat, dataSetIndex) } /** * Returns the DataSet that contains the provided Entry, or null, if no DataSet contains this Entry. */ - fun getDataSetForEntry(e: Entry): T? { + fun getDataSetForEntry(e: EntryFloat): T? { for (i in dataSets.indices) { val set = dataSets[i] diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/CombinedData.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/CombinedData.kt index f90fe2b56..c2a18bd66 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/CombinedData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/CombinedData.kt @@ -9,7 +9,7 @@ import timber.log.Timber * Data object that allows the combination of Line-, Bar-, Scatter-, Bubble- and * CandleData. Used in the CombinedChart class. */ -class CombinedData : BarLineScatterCandleBubbleData>() { +class CombinedData : BarLineScatterCandleBubbleData>() { var lineData: LineData? = null private set var barData: BarData? = null @@ -130,7 +130,7 @@ class CombinedData : BarLineScatterCandleBubbleData= this.allData.size || highlight.dataIndex < 0) return null @@ -156,7 +156,7 @@ class CombinedData : BarLineScatterCandleBubbleData? { + fun getDataSetByHighlight(highlight: Highlight): IBarLineScatterCandleBubbleDataSet? { if (highlight.dataIndex >= this.allData.size) return null @@ -165,14 +165,14 @@ class CombinedData : BarLineScatterCandleBubbleData= data.dataSetCount) return null - return data.dataSets[highlight.dataSetIndex] as IBarLineScatterCandleBubbleDataSet? + return data.dataSets[highlight.dataSetIndex] as IBarLineScatterCandleBubbleDataSet? } fun getDataIndex(data: ChartData<*>?): Int { return this.allData.indexOf(data) } - override fun removeDataSet(d: IBarLineScatterCandleBubbleDataSet?): Boolean { + override fun removeDataSet(d: IBarLineScatterCandleBubbleDataSet?): Boolean { val datas = this.allData var success = false @@ -195,7 +195,7 @@ class CombinedData : BarLineScatterCandleBubbleData( +abstract class DataSet( protected var entriesInternal: MutableList, label: String = "" ) : BaseDataSet(label), Serializable { @@ -219,12 +219,12 @@ abstract class DataSet( while (low < high) { val m = low + (high - low) / 2 - val currentEntry: Entry = entriesInternal[m] + val currentEntryFloat: EntryFloat = entriesInternal[m] - val nextEntry: Entry = entriesInternal[m + 1] + val nextEntryFloat: EntryFloat = entriesInternal[m + 1] - val d1 = currentEntry.x - xValue - val d2 = nextEntry.x - xValue + val d1 = currentEntryFloat.x - xValue + val d2 = nextEntryFloat.x - xValue val ad1 = abs(d1) val ad2 = abs(d2) @@ -251,8 +251,8 @@ abstract class DataSet( closest = high } - val closestEntry: Entry = entriesInternal[closest] - val closestXValue = closestEntry.x + val closestEntryFloat: EntryFloat = entriesInternal[closest] + val closestXValue = closestEntryFloat.x if (rounding == Rounding.UP) { // If rounding up, and found x-value is lower than specified x, and we can go upper... if (closestXValue < xValue && closest < entriesInternal.size - 1) { @@ -271,7 +271,7 @@ abstract class DataSet( closest -= 1 } - var closestYValue = closestEntry.y + var closestYValue = closestEntryFloat.y var closestYIndex = closest while (true) { diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryFloat.kt similarity index 80% rename from chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt rename to chartLib/src/main/kotlin/info/appdev/charting/data/EntryFloat.kt index 9a0b9fd19..431d26155 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryFloat.kt @@ -13,7 +13,7 @@ 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. */ -open class Entry : BaseEntry, Parcelable, Serializable { +open class EntryFloat : BaseEntry, Parcelable, Serializable { private var _x: Float = 0f open var x: Float @@ -25,7 +25,7 @@ open class Entry : BaseEntry, Parcelable, Serializable { constructor() /** - * A Entry represents one single entry in the chart. + * A EntryFloat represents one single entry in the chart. * * @param x the x value * @param y the y value (the actual value of the entry) @@ -35,7 +35,7 @@ open class Entry : BaseEntry, Parcelable, Serializable { } /** - * A Entry represents one single entry in the chart. + * A EntryFloat represents one single entry in the chart. * * @param x the x value * @param y the y value (the actual value of the entry) @@ -46,7 +46,7 @@ open class Entry : BaseEntry, Parcelable, Serializable { } /** - * A Entry represents one single entry in the chart. + * A EntryFloat represents one single entry in the chart. * * @param x the x value * @param y the y value (the actual value of the entry) @@ -57,12 +57,12 @@ open class Entry : BaseEntry, Parcelable, Serializable { } /** - * A Entry represents one single entry in the chart. + * A 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 Entry represents. + * @param data Spot for additional data this EntryFloat represents. */ constructor(x: Float, y: Float, icon: Drawable?, data: Any?) : super(y, icon, data) { this._x = x @@ -71,8 +71,8 @@ open class Entry : BaseEntry, Parcelable, Serializable { /** * returns an exact copy of the entry */ - open fun copy(): Entry { - val e = Entry(x, y, data) + open fun copy(): EntryFloat { + val e = EntryFloat(x, y, data) return e } @@ -81,7 +81,7 @@ open class Entry : BaseEntry, Parcelable, Serializable { * are equal in those points, false if not. Does not check by hash-code like * it's done by the "equals" method. */ - fun equalTo(e: Entry?): Boolean { + fun equalTo(e: EntryFloat?): Boolean { if (e == null) return false @@ -104,7 +104,7 @@ open class Entry : BaseEntry, Parcelable, Serializable { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { "${this.javaClass.typeName.substringAfterLast(".")} x=$x y=$y" } else { - "Entry x=$x y=$y" + "EntryFloat x=$x y=$y" } } @@ -120,7 +120,7 @@ open class Entry : BaseEntry, Parcelable, Serializable { dest.writeInt(1) dest.writeParcelable(data as Parcelable?, flags) } else { - throw ParcelFormatException("Cannot parcel an Entry with non-parcelable data") + throw ParcelFormatException("Cannot parcel an EntryFloat with non-parcelable data") } } else { dest.writeInt(0) @@ -131,7 +131,7 @@ open class Entry : BaseEntry, Parcelable, Serializable { this._x = `in`.readFloat() this.yBase = `in`.readFloat() if (`in`.readInt() == 1) { - this.data = if (android.os.Build.VERSION.SDK_INT >= 33) { + this.data = if (Build.VERSION.SDK_INT >= 33) { `in`.readParcelable(Any::class.java.classLoader, Any::class.java) } else { @Suppress("DEPRECATION") @@ -142,12 +142,12 @@ open class Entry : BaseEntry, Parcelable, Serializable { companion object { @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): Entry { - return Entry(source) + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(source: Parcel): EntryFloat { + return EntryFloat(source) } - override fun newArray(size: Int): Array { + override fun newArray(size: Int): Array { return arrayOfNulls(size) } } 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 be22ee8e5..161aa24eb 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt @@ -12,7 +12,7 @@ import info.appdev.charting.utils.ColorTemplate import info.appdev.charting.utils.convertDpToPixel import timber.log.Timber -open class LineDataSet(yVals: MutableList = mutableListOf(), label: String = "") : LineRadarDataSet(yVals, label), ILineDataSet { +open class LineDataSet(yVals: MutableList = mutableListOf(), label: String = "") : LineRadarDataSet(yVals, label), ILineDataSet { /** * Drawing mode for this line dataset */ @@ -73,8 +73,8 @@ open class LineDataSet(yVals: MutableList = mutableListOf(), label: Strin circleColors.add(Color.rgb(140, 234, 255)) } - override fun copy(): DataSet { - val entries: MutableList = mutableListOf() + override fun copy(): DataSet { + val entries: MutableList = mutableListOf() for (i in entriesInternal.indices) { entries.add(entriesInternal[i].copy()) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/LineRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/LineRadarDataSet.kt index db3e59332..cb52f42bc 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineRadarDataSet.kt @@ -9,7 +9,7 @@ import info.appdev.charting.utils.convertDpToPixel /** * Base dataset for line and radar DataSets. */ -abstract class LineRadarDataSet(yVals: MutableList, label: String) : LineScatterCandleRadarDataSet(yVals, label), ILineRadarDataSet { +abstract class LineRadarDataSet(yVals: MutableList, label: String) : LineScatterCandleRadarDataSet(yVals, label), ILineRadarDataSet { // TODO: Move to using `Fill` class /** * the color that is used for filling the line surface diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/LineScatterCandleRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/LineScatterCandleRadarDataSet.kt index 1f9535dff..c5f782fc4 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineScatterCandleRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineScatterCandleRadarDataSet.kt @@ -4,7 +4,7 @@ import android.graphics.DashPathEffect import info.appdev.charting.interfaces.datasets.ILineScatterCandleRadarDataSet import info.appdev.charting.utils.convertDpToPixel -abstract class LineScatterCandleRadarDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), +abstract class LineScatterCandleRadarDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), ILineScatterCandleRadarDataSet { override var isVerticalHighlightIndicator: Boolean = true override var isHorizontalHighlightIndicator: Boolean = true diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/PieData.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/PieData.kt index 340488548..cdf80df62 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/PieData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/PieData.kt @@ -58,7 +58,7 @@ class PieData : ChartData { null } - override fun getEntryForHighlight(highlight: Highlight): Entry? { + override fun getEntryForHighlight(highlight: Highlight): EntryFloat? { return this.dataSet.getEntryForIndex(highlight.x.toInt()) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/PieDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/PieDataSet.kt index fd1f5ca4e..aa0161ee4 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/PieDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/PieDataSet.kt @@ -4,7 +4,7 @@ import androidx.annotation.ColorInt import info.appdev.charting.interfaces.datasets.IPieDataSet import info.appdev.charting.utils.convertDpToPixel -open class PieDataSet(yVals: MutableList, label: String) : DataSet(yVals, label), IPieDataSet { +open class PieDataSet(yVals: MutableList, label: String) : DataSet(yVals, label), IPieDataSet { /** * the space in pixels between the chart-slices, default 0f */ @@ -29,8 +29,8 @@ open class PieDataSet(yVals: MutableList, label: String) : DataSet { - val entries: MutableList = mutableListOf() + override fun copy(): DataSet { + val entries: MutableList = mutableListOf() for (i in entriesInternal.indices) { entries.add(entriesInternal[i].copy()) } @@ -42,7 +42,7 @@ open class PieDataSet(yVals: MutableList, label: String) : DataSet?)!!) } - override fun calcMinMax(entry: PieEntry) { + override fun calcMinMax(entry: PieEntryFloat) { calcMinMaxY(entry) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntryFloat.kt similarity index 92% rename from chartLib/src/main/kotlin/info/appdev/charting/data/PieEntry.kt rename to chartLib/src/main/kotlin/info/appdev/charting/data/PieEntryFloat.kt index 172e62dcf..4ed9dc512 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/PieEntryFloat.kt @@ -5,7 +5,7 @@ import android.graphics.drawable.Drawable import timber.log.Timber @SuppressLint("ParcelCreator") -class PieEntry : Entry { +class PieEntryFloat : EntryFloat { var label: String? = null constructor(value: Float) : super(0f, value) @@ -50,7 +50,7 @@ class PieEntry : Entry { Timber.i("Pie entries do not have x values") } - override fun copy(): PieEntry { - return PieEntry(y, label, data) + override fun copy(): PieEntryFloat { + return PieEntryFloat(y, label, data) } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarData.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarData.kt index fc5e4d8bb..74427ee3a 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarData.kt @@ -14,7 +14,7 @@ class RadarData : ChartData { constructor(dataSets: MutableList) : super(dataSets) - override fun getEntryForHighlight(highlight: Highlight): Entry? { + override fun getEntryForHighlight(highlight: Highlight): EntryFloat? { return getDataSetByIndex(highlight.dataSetIndex)!!.getEntryForIndex(highlight.x.toInt()) } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarDataSet.kt index 8a93eebe2..d4d3ce261 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarDataSet.kt @@ -5,7 +5,7 @@ import androidx.annotation.ColorInt import info.appdev.charting.interfaces.datasets.IRadarDataSet import info.appdev.charting.utils.ColorTemplate -open class RadarDataSet(yVals: MutableList, label: String = "") : LineRadarDataSet(yVals, label), IRadarDataSet { +open class RadarDataSet(yVals: MutableList, label: String = "") : LineRadarDataSet(yVals, label), IRadarDataSet { /** flag indicating whether highlight circle should be drawn or not */ protected var mIsDrawHighlightCircle: Boolean = false @@ -58,8 +58,8 @@ open class RadarDataSet(yVals: MutableList, label: String = "") : Li mHighlightCircleStrokeWidth = value } - override fun copy(): DataSet { - val entries: MutableList = mutableListOf() + override fun copy(): DataSet { + val entries: MutableList = mutableListOf() for (i in entriesInternal.indices) { entries.add(entriesInternal[i].copy()) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntryFloat.kt similarity index 80% rename from chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntry.kt rename to chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntryFloat.kt index 0b6e6a306..5b37a6769 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/RadarEntryFloat.kt @@ -3,7 +3,7 @@ package info.appdev.charting.data import android.annotation.SuppressLint @SuppressLint("ParcelCreator") -class RadarEntry : Entry { +class RadarEntryFloat : EntryFloat { constructor(value: Float) : super(0f, value) constructor(value: Float, data: Any?) : super(0f, value, data) @@ -14,8 +14,8 @@ class RadarEntry : Entry { val value: Float get() = y - override fun copy(): RadarEntry { - return RadarEntry(y, data) + override fun copy(): RadarEntryFloat { + return RadarEntryFloat(y, data) } @get:Deprecated("") diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/ScatterDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/ScatterDataSet.kt index d9c1ef47f..aadb8848e 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/ScatterDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/ScatterDataSet.kt @@ -13,7 +13,7 @@ import info.appdev.charting.renderer.scatter.TriangleShapeRenderer import info.appdev.charting.renderer.scatter.XShapeRenderer import info.appdev.charting.utils.ColorTemplate -open class ScatterDataSet(yVals: MutableList, label: String = "") : LineScatterCandleRadarDataSet(yVals, label), IScatterDataSet { +open class ScatterDataSet(yVals: MutableList, label: String = "") : LineScatterCandleRadarDataSet(yVals, label), IScatterDataSet { /** * the size the scatterShape will have, in density pixels */ @@ -38,8 +38,8 @@ open class ScatterDataSet(yVals: MutableList, label: String = "") : LineS @ColorInt private var mScatterShapeHoleColor = ColorTemplate.COLOR_NONE - override fun copy(): DataSet { - val entries: MutableList = mutableListOf() + override fun copy(): DataSet { + val entries: MutableList = mutableListOf() for (i in entriesInternal.indices) { entries.add(entriesInternal[i].copy()) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/formatter/ColorFormatter.kt b/chartLib/src/main/kotlin/info/appdev/charting/formatter/ColorFormatter.kt index 3293e103c..b015dbed9 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/formatter/ColorFormatter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/formatter/ColorFormatter.kt @@ -1,6 +1,6 @@ package info.appdev.charting.formatter -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.interfaces.datasets.IDataSet /** @@ -11,8 +11,8 @@ interface ColorFormatter { * Returns the color to be used for the given Entry at the given index (in the entries array) * * @param index index in the entries array - * @param entry the entry to color + * @param entryFloat the entry to color * @param set the DataSet the entry belongs to */ - fun getColor(index: Int, entry: Entry?, set: IDataSet<*>?): Int + fun getColor(index: Int, entryFloat: EntryFloat?, set: IDataSet<*>?): Int } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/formatter/DefaultValueFormatter.kt b/chartLib/src/main/kotlin/info/appdev/charting/formatter/DefaultValueFormatter.kt index b722ff084..1164c220a 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/formatter/DefaultValueFormatter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/formatter/DefaultValueFormatter.kt @@ -1,6 +1,6 @@ package info.appdev.charting.formatter -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.utils.ViewPortHandler import java.text.DecimalFormat @@ -42,7 +42,7 @@ open class DefaultValueFormatter(digits: Int) : IValueFormatter { decimalFormat = DecimalFormat("###,###,###,##0$b") } - override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String? { + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String? { // put more logic here ... // avoid memory allocations here (for performance reasons) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/formatter/IValueFormatter.kt b/chartLib/src/main/kotlin/info/appdev/charting/formatter/IValueFormatter.kt index 8e05dedc0..8275036a1 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/formatter/IValueFormatter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/formatter/IValueFormatter.kt @@ -1,6 +1,6 @@ package info.appdev.charting.formatter -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.utils.ViewPortHandler /** @@ -16,10 +16,10 @@ interface IValueFormatter { * and memory allocations inside this method. * * @param value the value to be formatted - * @param entry the entry the value belongs to - in e.g. BarChart, this is of class BarEntry + * @param entryFloat the entry the value belongs to - in e.g. BarChart, this is of class BarEntry * @param dataSetIndex the index of the DataSet the entry in focus belongs to * @param viewPortHandler provides information about the current chart state (scale, translation, ...) * @return the formatted label ready for being drawn */ - fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String? + fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String? } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/formatter/LargeValueFormatter.kt b/chartLib/src/main/kotlin/info/appdev/charting/formatter/LargeValueFormatter.kt index 12692b54c..4bdc96900 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/formatter/LargeValueFormatter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/formatter/LargeValueFormatter.kt @@ -1,7 +1,7 @@ package info.appdev.charting.formatter import info.appdev.charting.components.AxisBase -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.utils.ViewPortHandler import java.text.DecimalFormat @@ -32,7 +32,7 @@ open class LargeValueFormatter() : IValueFormatter, IAxisValueFormatter { } // IValueFormatter - override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { return makePretty(value.toDouble()) + text } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/formatter/PercentFormatter.kt b/chartLib/src/main/kotlin/info/appdev/charting/formatter/PercentFormatter.kt index 271fab7bf..e785c9833 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/formatter/PercentFormatter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/formatter/PercentFormatter.kt @@ -1,7 +1,7 @@ package info.appdev.charting.formatter import info.appdev.charting.components.AxisBase -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.utils.ViewPortHandler import java.text.DecimalFormat @@ -23,7 +23,7 @@ open class PercentFormatter : IValueFormatter, IAxisValueFormatter { } // IValueFormatter - override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String? { + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String? { return decimalFormat.format(value.toDouble()) + " %" } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/formatter/StackedValueFormatter.kt b/chartLib/src/main/kotlin/info/appdev/charting/formatter/StackedValueFormatter.kt index 957957acd..606bfeb0c 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/formatter/StackedValueFormatter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/formatter/StackedValueFormatter.kt @@ -1,7 +1,7 @@ package info.appdev.charting.formatter -import info.appdev.charting.data.BarEntry -import info.appdev.charting.data.Entry +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat import info.appdev.charting.utils.ViewPortHandler import java.text.DecimalFormat @@ -29,9 +29,9 @@ open class StackedValueFormatter(private val drawWholeStack: Boolean, private va this.decimalFormat = DecimalFormat("###,###,###,##0$b") } - override fun getFormattedValue(value: Float, entry: Entry?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { - if (!drawWholeStack && entry is BarEntry) { - val barEntry = entry + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + if (!drawWholeStack && entryFloat is BarEntryFloat) { + val barEntry = entryFloat val vals = barEntry.yVals if (vals != null) { 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 fb54b536d..9d0eac051 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt @@ -3,7 +3,7 @@ package info.appdev.charting.highlight import info.appdev.charting.components.YAxis.AxisDependency import info.appdev.charting.data.ChartData import info.appdev.charting.data.DataSet -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.interfaces.dataprovider.base.BarLineScatterCandleBubbleDataProvider import info.appdev.charting.interfaces.datasets.IDataSet import info.appdev.charting.utils.PointD @@ -125,7 +125,7 @@ open class ChartHighlighter>(prote var entries = set.getEntriesForXValue(xVal) if (entries != null && entries.isEmpty()) { // Try to find closest x-value and take all entries for that x-value - val closest: Entry? = set.getEntryForXValue(xVal, Float.NaN, rounding) + val closest: EntryFloat? = set.getEntryForXValue(xVal, Float.NaN, rounding) if (closest != null) { entries = set.getEntriesForXValue(closest.x) } 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 9ac9af962..8354c9fe4 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/HorizontalBarHighlighter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/HorizontalBarHighlighter.kt @@ -1,7 +1,7 @@ package info.appdev.charting.highlight import info.appdev.charting.data.DataSet -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.interfaces.dataprovider.BarDataProvider import info.appdev.charting.interfaces.datasets.IDataSet import info.appdev.charting.utils.PointD @@ -38,8 +38,8 @@ class HorizontalBarHighlighter(dataProvider: BarDataProvider) : BarHighlighter(d var entries = set.getEntriesForXValue(xVal) if (entries != null && entries.isEmpty()) { // Try to find closest x-value and take all entries for that x-value - val closestEntry: Entry? = set.getEntryForXValue(xVal, Float.NaN, rounding) - closestEntry?.let { closestE -> + val closestEntryFloat: EntryFloat? = set.getEntryForXValue(xVal, Float.NaN, rounding) + closestEntryFloat?.let { closestE -> entries = set.getEntriesForXValue(closestE.x) } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/highlight/PieHighlighter.kt b/chartLib/src/main/kotlin/info/appdev/charting/highlight/PieHighlighter.kt index d846dcf66..388229271 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/PieHighlighter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/PieHighlighter.kt @@ -1,16 +1,16 @@ package info.appdev.charting.highlight import info.appdev.charting.charts.PieChart -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat open class PieHighlighter(chart: PieChart) : PieRadarHighlighter(chart) { override fun getClosestHighlight(index: Int, x: Float, y: Float): Highlight? { val pieDataSet = chartPieRadar.data?.dataSets[0] - val entry: Entry? = pieDataSet?.getEntryForIndex(index) + val entryFloat: EntryFloat? = pieDataSet?.getEntryForIndex(index) - entry?.let { - return Highlight(index.toFloat(), entry.y, x, y, 0, pieDataSet.axisDependency) + entryFloat?.let { + return Highlight(index.toFloat(), entryFloat.y, x, y, 0, pieDataSet.axisDependency) } return null } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/highlight/RadarHighlighter.kt b/chartLib/src/main/kotlin/info/appdev/charting/highlight/RadarHighlighter.kt index 7e7450be5..5002eb762 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/RadarHighlighter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/RadarHighlighter.kt @@ -1,7 +1,7 @@ package info.appdev.charting.highlight import info.appdev.charting.charts.RadarChart -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.utils.PointF import info.appdev.charting.utils.changePosition import kotlin.math.abs @@ -47,16 +47,16 @@ open class RadarHighlighter(chart: RadarChart) : PieRadarHighlighter for (i in 0.. { +interface IBarDataSet : IBarLineScatterCandleBubbleDataSet { var fills: MutableList fun getFill(index: Int): Fill? diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBarLineScatterCandleBubbleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBarLineScatterCandleBubbleDataSet.kt index c54774425..ead34a8bc 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBarLineScatterCandleBubbleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBarLineScatterCandleBubbleDataSet.kt @@ -1,8 +1,8 @@ package info.appdev.charting.interfaces.datasets -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat -interface IBarLineScatterCandleBubbleDataSet : IDataSet { +interface IBarLineScatterCandleBubbleDataSet : IDataSet { val highLightColor: Int } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBubbleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBubbleDataSet.kt index 43f8efb49..faa14a164 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBubbleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBubbleDataSet.kt @@ -1,8 +1,8 @@ package info.appdev.charting.interfaces.datasets -import info.appdev.charting.data.BubbleEntry +import info.appdev.charting.data.BubbleEntryFloat -interface IBubbleDataSet : IBarLineScatterCandleBubbleDataSet { +interface IBubbleDataSet : IBarLineScatterCandleBubbleDataSet { val maxSize: Float val isNormalizeSizeEnabled: Boolean diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ICandleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ICandleDataSet.kt index 366c78344..ce86fccf2 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ICandleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ICandleDataSet.kt @@ -1,10 +1,9 @@ package info.appdev.charting.interfaces.datasets import android.graphics.Paint -import androidx.annotation.ColorInt -import info.appdev.charting.data.CandleEntry +import info.appdev.charting.data.CandleEntryFloat -interface ICandleDataSet : ILineScatterCandleRadarDataSet { +interface ICandleDataSet : ILineScatterCandleRadarDataSet { /** * Returns the space that is left out on the left and right side of each candle. */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt index c401c661f..43db6096f 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt @@ -5,11 +5,11 @@ import android.graphics.Typeface import info.appdev.charting.components.Legend import info.appdev.charting.components.YAxis import info.appdev.charting.data.DataSet -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.formatter.IValueFormatter import info.appdev.charting.utils.PointF -interface IDataSet { +interface IDataSet { /** * returns the minimum y-value this DataSet holds */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineDataSet.kt index 3f2ccd2dc..adb355415 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineDataSet.kt @@ -1,11 +1,11 @@ package info.appdev.charting.interfaces.datasets import android.graphics.DashPathEffect -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineDataSet import info.appdev.charting.formatter.IFillFormatter -interface ILineDataSet : ILineRadarDataSet { +interface ILineDataSet : ILineRadarDataSet { /** * Returns the drawing mode for this line dataset */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineRadarDataSet.kt index d0ccfe7d9..050bc462f 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineRadarDataSet.kt @@ -1,9 +1,9 @@ package info.appdev.charting.interfaces.datasets import android.graphics.drawable.Drawable -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat -interface ILineRadarDataSet : ILineScatterCandleRadarDataSet { +interface ILineRadarDataSet : ILineScatterCandleRadarDataSet { /** * Returns the color that is used for filling the line surface area. */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineScatterCandleRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineScatterCandleRadarDataSet.kt index 56cb22950..f260a293e 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineScatterCandleRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineScatterCandleRadarDataSet.kt @@ -1,9 +1,9 @@ package info.appdev.charting.interfaces.datasets import android.graphics.DashPathEffect -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat -interface ILineScatterCandleRadarDataSet : IBarLineScatterCandleBubbleDataSet { +interface ILineScatterCandleRadarDataSet : IBarLineScatterCandleBubbleDataSet { /** * Returns true if vertical highlight indicator lines are enabled (drawn) */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IPieDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IPieDataSet.kt index 658602dc7..13df4852b 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IPieDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IPieDataSet.kt @@ -1,9 +1,9 @@ package info.appdev.charting.interfaces.datasets import info.appdev.charting.data.PieDataSet.ValuePosition -import info.appdev.charting.data.PieEntry +import info.appdev.charting.data.PieEntryFloat -interface IPieDataSet : IDataSet { +interface IPieDataSet : IDataSet { /** * Returns the space that is set to be between the PieChart-slices of this DataSet, in pixels. */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IRadarDataSet.kt index 986849fcf..6406f1c89 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IRadarDataSet.kt @@ -1,8 +1,8 @@ package info.appdev.charting.interfaces.datasets -import info.appdev.charting.data.RadarEntry +import info.appdev.charting.data.RadarEntryFloat -interface IRadarDataSet : ILineRadarDataSet { +interface IRadarDataSet : ILineRadarDataSet { /** flag indicating whether highlight circle should be drawn or not */ /** Sets whether highlight circle should be drawn or not */ var isDrawHighlightCircle: Boolean diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IScatterDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IScatterDataSet.kt index 9e44a3251..796808a2e 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IScatterDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IScatterDataSet.kt @@ -1,9 +1,9 @@ package info.appdev.charting.interfaces.datasets -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.renderer.scatter.IShapeRenderer -interface IScatterDataSet : ILineScatterCandleRadarDataSet { +interface IScatterDataSet : ILineScatterCandleRadarDataSet { /** * the currently set scatter shape size */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/listener/BarLineChartTouchListener.kt b/chartLib/src/main/kotlin/info/appdev/charting/listener/BarLineChartTouchListener.kt index 786af91c6..faaaeca8d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/listener/BarLineChartTouchListener.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/listener/BarLineChartTouchListener.kt @@ -9,7 +9,7 @@ import android.view.animation.AnimationUtils import info.appdev.charting.charts.BarLineChartBase import info.appdev.charting.charts.HorizontalBarChart import info.appdev.charting.data.BarLineScatterCandleBubbleData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet import info.appdev.charting.interfaces.datasets.IDataSet import info.appdev.charting.utils.PointF @@ -25,11 +25,11 @@ import kotlin.math.sqrt */ @Suppress("MemberVisibilityCanBePrivate") class BarLineChartTouchListener( - chart: BarLineChartBase>>, + chart: BarLineChartBase>>, touchMatrix: Matrix, dragTriggerDistance: Float ) : - ChartTouchListener>>>(chart) { + ChartTouchListener>>>(chart) { /** * the original touch-matrix from the chart */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/listener/OnChartValueSelectedListener.kt b/chartLib/src/main/kotlin/info/appdev/charting/listener/OnChartValueSelectedListener.kt index 188ed5ea1..5e54d6db3 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/listener/OnChartValueSelectedListener.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/listener/OnChartValueSelectedListener.kt @@ -1,6 +1,6 @@ package info.appdev.charting.listener -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight /** @@ -11,11 +11,11 @@ interface OnChartValueSelectedListener { /** * Called when a value has been selected inside the chart. * - * @param entry The selected Entry + * @param entryFloat The selected Entry * @param highlight The corresponding highlight object that contains information * about the highlighted position such as dataSetIndex, ... */ - fun onValueSelected(entry: Entry, highlight: Highlight) + fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) /** * Called when nothing has been selected or an "un-select" has been made. 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 6e755a23e..45b9af8ac 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/listener/OnDrawListener.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/listener/OnDrawListener.kt @@ -1,7 +1,7 @@ package info.appdev.charting.listener import info.appdev.charting.data.DataSet -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat /** * Listener for callbacks when drawing on the chart. @@ -11,14 +11,14 @@ interface OnDrawListener { * Called whenever an entry is added with the finger. Note this is also called for entries that are generated by the * library, when the touch gesture is too fast and skips points. * - * @param entry the last drawn entry + * @param entryFloat the last drawn entry */ - fun onEntryAdded(entry: Entry) + fun onEntryAdded(entryFloat: EntryFloat) /** * Called whenever an entry is moved by the user after being highlighted */ - fun onEntryMoved(entry: Entry) + fun onEntryMoved(entryFloat: EntryFloat) /** * Called when drawing finger is lifted and the draw is finished. 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 d54b178d9..577b42a86 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/BarLineScatterCandleBubbleRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/BarLineScatterCandleBubbleRenderer.kt @@ -2,7 +2,7 @@ package info.appdev.charting.renderer import info.appdev.charting.animation.ChartAnimator import info.appdev.charting.data.DataSet -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.interfaces.dataprovider.base.BarLineScatterCandleBubbleDataProvider import info.appdev.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet import info.appdev.charting.interfaces.datasets.IDataSet @@ -29,7 +29,7 @@ abstract class BarLineScatterCandleBubbleRenderer( /** * Checks if the provided entry object is in bounds for drawing considering the current animation phase. */ - protected fun isInBoundsX(entry: T, set: IBarLineScatterCandleBubbleDataSet): Boolean { + protected fun isInBoundsX(entry: T, set: IBarLineScatterCandleBubbleDataSet): Boolean { val entryIndex = set.getEntryIndex(entry).toFloat() return if (entryIndex >= set.entryCount * animator.phaseX) { @@ -61,7 +61,7 @@ abstract class BarLineScatterCandleBubbleRenderer( /** * Calculates the minimum and maximum x values as well as the range between them. */ - fun set(chart: BarLineScatterCandleBubbleDataProvider<*>, dataSet: IBarLineScatterCandleBubbleDataSet) { + fun set(chart: BarLineScatterCandleBubbleDataProvider<*>, dataSet: IBarLineScatterCandleBubbleDataSet) { val phaseX = max(0f, min(1f, animator.phaseX)) val low = chart.lowestVisibleX 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 09b1c3acd..ba5e970ad 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/DataRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/DataRenderer.kt @@ -5,7 +5,7 @@ import android.graphics.Color import android.graphics.Paint import android.graphics.Paint.Align import info.appdev.charting.animation.ChartAnimator -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.formatter.IValueFormatter import info.appdev.charting.highlight.Highlight import info.appdev.charting.interfaces.dataprovider.base.IBaseProvider @@ -98,14 +98,14 @@ abstract class DataRenderer( * @param canvas canvas * @param formatter formatter for custom value-formatting * @param value the value to be drawn - * @param entry the entry the value belongs to + * @param entryFloat the entry the value belongs to * @param dataSetIndex the index of the DataSet the drawn Entry belongs to * @param x position * @param y position */ - fun drawValue(canvas: Canvas, formatter: IValueFormatter, value: Float, entry: Entry?, dataSetIndex: Int, x: Float, y: Float, color: Int) { + fun drawValue(canvas: Canvas, formatter: IValueFormatter, value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, x: Float, y: Float, color: Int) { paintValues.color = color - canvas.drawText(formatter.getFormattedValue(value, entry, dataSetIndex, viewPortHandler)!!, x, y, paintValues) + canvas.drawText(formatter.getFormattedValue(value, entryFloat, dataSetIndex, viewPortHandler)!!, x, y, paintValues) } /** 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 9745460e6..dbadb870d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineChartRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineChartRenderer.kt @@ -7,7 +7,7 @@ import android.graphics.Color import android.graphics.Paint import android.graphics.Path import info.appdev.charting.animation.ChartAnimator -import info.appdev.charting.data.Entry +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 @@ -175,7 +175,7 @@ open class LineChartRenderer( // And in the `lastIndex`, add +1 val firstIndex = xBounds.min + 1 - var prevPrev: Entry? + var prevPrev: EntryFloat? var prev = dataSet.getEntryForIndex(max((firstIndex - 2).toDouble(), 0.0).toInt()) var cur = dataSet.getEntryForIndex(max((firstIndex - 1).toDouble(), 0.0).toInt()) var next = cur @@ -286,24 +286,24 @@ open class LineChartRenderer( val max = xBounds.min + xBounds.range for (j in xBounds.min.. { - val entries: MutableList = ArrayList() +fun AssetManager.loadEntriesFromAssets(path: String): MutableList { + val entries: MutableList = ArrayList() try { BufferedReader( @@ -31,7 +31,7 @@ fun AssetManager.loadEntriesFromAssets(path: String): MutableList { val split: Array = line.split("#".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() if (split.size <= 2) { - entries.add(Entry(split[1]!!.toFloat(), split[0]!!.toFloat())) + entries.add(EntryFloat(split[1]!!.toFloat(), split[0]!!.toFloat())) } else { val vals = FloatArray(split.size - 1) @@ -39,7 +39,7 @@ fun AssetManager.loadEntriesFromAssets(path: String): MutableList { vals[i] = split[i]!!.toFloat() } - entries.add(BarEntry(split[split.size - 1]!!.toInt().toFloat(), vals)) + entries.add(BarEntryFloat(split[split.size - 1]!!.toInt().toFloat(), vals)) } line = reader.readLine() } @@ -51,15 +51,15 @@ fun AssetManager.loadEntriesFromAssets(path: String): MutableList { return entries } -fun AssetManager.loadBarEntriesFromAssets(path: String): MutableList { - val entries: MutableList = ArrayList() +fun AssetManager.loadBarEntriesFromAssets(path: String): MutableList { + val entries: MutableList = ArrayList() try { BufferedReader(InputStreamReader(this.open(path), StandardCharsets.UTF_8)).use { reader -> var line = reader.readLine() while (line != null) { val split: Array = line.split("#".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - entries.add(BarEntry(split[1]!!.toFloat(), split[0]!!.toFloat())) + entries.add(BarEntryFloat(split[1]!!.toFloat(), split[0]!!.toFloat())) line = reader.readLine() } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/EntryXComparator.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/EntryXComparator.kt index d380b4deb..3aaafeb5f 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/EntryXComparator.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/EntryXComparator.kt @@ -1,13 +1,13 @@ package info.appdev.charting.utils -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat /** * Comparator for comparing Entry-objects by their x-value. */ -class EntryXComparator : Comparator { - override fun compare(entry1: Entry, entry2: Entry): Int { - val diff = entry1.x - entry2.x +class EntryXComparator : Comparator { + override fun compare(entryFloat1: EntryFloat, entryFloat2: EntryFloat): Int { + val diff = entryFloat1.x - entryFloat2.x return diff.compareTo(0f) } 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 e43e58582..0836d3857 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/Transformer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/Transformer.kt @@ -3,7 +3,7 @@ package info.appdev.charting.utils import android.graphics.Matrix import android.graphics.Path import android.graphics.RectF -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.interfaces.datasets.IBubbleDataSet import info.appdev.charting.interfaces.datasets.ICandleDataSet import info.appdev.charting.interfaces.datasets.ILineDataSet @@ -118,7 +118,7 @@ open class Transformer(protected var viewPortHandler: ViewPortHandler) { var j = 0 while (j < count) { - val e: Entry? = data.getEntryForIndex(j / 2 + from) + val e: EntryFloat? = data.getEntryForIndex(j / 2 + from) if (e != null) { valuePoints[j] = e.x diff --git a/chartLib/src/test/kotlin/info/appdev/charting/test/BarDataTest.kt b/chartLib/src/test/kotlin/info/appdev/charting/test/BarDataTest.kt index 0869e638d..51deb135a 100644 --- a/chartLib/src/test/kotlin/info/appdev/charting/test/BarDataTest.kt +++ b/chartLib/src/test/kotlin/info/appdev/charting/test/BarDataTest.kt @@ -2,7 +2,7 @@ package info.appdev.charting.test import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet -import info.appdev.charting.data.BarEntry +import info.appdev.charting.data.BarEntryFloat import org.junit.Assert import org.junit.Test @@ -12,12 +12,12 @@ class BarDataTest { val groupSpace = 5f val barSpace = 1f - val values1: MutableList = ArrayList() - val values2: MutableList = ArrayList() + val values1: MutableList = ArrayList() + val values2: MutableList = ArrayList() for (i in 0..4) { - values1.add(BarEntry(i.toFloat(), 50f)) - values2.add(BarEntry(i.toFloat(), 60f)) + values1.add(BarEntryFloat(i.toFloat(), 50f)) + values2.add(BarEntryFloat(i.toFloat(), 60f)) } val barDataSet1 = BarDataSet(values1, "Set1") diff --git a/chartLib/src/test/kotlin/info/appdev/charting/test/ChartDataTest.kt b/chartLib/src/test/kotlin/info/appdev/charting/test/ChartDataTest.kt index 468aaf40b..2f4b84cad 100644 --- a/chartLib/src/test/kotlin/info/appdev/charting/test/ChartDataTest.kt +++ b/chartLib/src/test/kotlin/info/appdev/charting/test/ChartDataTest.kt @@ -1,7 +1,7 @@ package info.appdev.charting.test import info.appdev.charting.components.YAxis -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet import info.appdev.charting.data.ScatterData @@ -12,17 +12,17 @@ import org.junit.Test class ChartDataTest { @Test fun testDynamicChartData() { - val entries1: MutableList = ArrayList() - entries1.add(Entry(10f, 10f)) - entries1.add(Entry(15f, -2f)) - entries1.add(Entry(21f, 50f)) + val entries1: MutableList = ArrayList() + entries1.add(EntryFloat(10f, 10f)) + entries1.add(EntryFloat(15f, -2f)) + entries1.add(EntryFloat(21f, 50f)) val set1 = ScatterDataSet(entries1, "") - val entries2: MutableList = ArrayList() - entries2.add(Entry(-1f, 10f)) - entries2.add(Entry(10f, 2f)) - entries2.add(Entry(20f, 5f)) + val entries2: MutableList = ArrayList() + entries2.add(EntryFloat(-1f, 10f)) + entries2.add(EntryFloat(10f, 2f)) + entries2.add(EntryFloat(20f, 5f)) val set2 = ScatterDataSet(entries2, "") @@ -42,7 +42,7 @@ class ChartDataTest { Assert.assertEquals(3, data.maxEntryCountSet?.entryCount) // now add and remove values - data.addEntry(Entry(-10f, -10f), 0) + data.addEntry(EntryFloat(-10f, -10f), 0) Assert.assertEquals(set1, data.maxEntryCountSet) Assert.assertEquals(4, data.maxEntryCountSet?.entryCount) @@ -56,8 +56,8 @@ class ChartDataTest { Assert.assertEquals(-10f, data.yMin, 0.01f) Assert.assertEquals(50f, data.yMax, 0.01f) - data.addEntry(Entry(-100f, 100f), 0) - data.addEntry(Entry(0f, -100f), 0) + data.addEntry(EntryFloat(-100f, 100f), 0) + data.addEntry(EntryFloat(0f, -100f), 0) Assert.assertEquals(-100f, data.getYMin(YAxis.AxisDependency.LEFT), 0.01f) Assert.assertEquals(100f, data.getYMax(YAxis.AxisDependency.LEFT), 0.01f) @@ -66,9 +66,9 @@ class ChartDataTest { Assert.assertEquals(-100f, data.getYMin(YAxis.AxisDependency.RIGHT), 0.01f) Assert.assertEquals(100f, data.getYMax(YAxis.AxisDependency.RIGHT), 0.01f) - val entries3: MutableList = ArrayList() - entries3.add(Entry(0f, 200f)) - entries3.add(Entry(0f, -50f)) + val entries3: MutableList = ArrayList() + entries3.add(EntryFloat(0f, 200f)) + entries3.add(EntryFloat(0f, -50f)) val set3 = ScatterDataSet(entries3, "") set3.axisDependency = YAxis.AxisDependency.RIGHT @@ -96,9 +96,9 @@ class ChartDataTest { Assert.assertEquals(0, lineData.dataSetCount) - val lineEntries1: MutableList = ArrayList() - lineEntries1.add(Entry(10f, 90f)) - lineEntries1.add(Entry(1000f, 1000f)) + val lineEntries1: MutableList = ArrayList() + lineEntries1.add(EntryFloat(10f, 90f)) + lineEntries1.add(EntryFloat(1000f, 1000f)) val lineSet1 = LineDataSet(lineEntries1, "") @@ -120,11 +120,11 @@ class ChartDataTest { Assert.assertEquals(90f, lineData.getYMin(YAxis.AxisDependency.RIGHT), 0.01f) Assert.assertEquals(1000f, lineData.getYMax(YAxis.AxisDependency.RIGHT), 0.01f) - val lineEntries2: MutableList = ArrayList() - lineEntries2.add(Entry(-1000f, 2000f)) - lineEntries2.add(Entry(2000f, -3000f)) + val lineEntries2: MutableList = ArrayList() + lineEntries2.add(EntryFloat(-1000f, 2000f)) + lineEntries2.add(EntryFloat(2000f, -3000f)) - val e = Entry(-1000f, 2500f) + val e = EntryFloat(-1000f, 2500f) lineEntries2.add(e) val lineSet2 = LineDataSet(lineEntries2, "") diff --git a/chartLib/src/test/kotlin/info/appdev/charting/test/DataSetTest.kt b/chartLib/src/test/kotlin/info/appdev/charting/test/DataSetTest.kt index 62b0b0efb..482c1b953 100644 --- a/chartLib/src/test/kotlin/info/appdev/charting/test/DataSetTest.kt +++ b/chartLib/src/test/kotlin/info/appdev/charting/test/DataSetTest.kt @@ -1,7 +1,7 @@ package info.appdev.charting.test import info.appdev.charting.data.DataSet -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.ScatterDataSet import org.junit.Assert import org.junit.Test @@ -9,10 +9,10 @@ import org.junit.Test class DataSetTest { @Test fun testCalcMinMax() { - val entries: MutableList = mutableListOf() - entries.add(Entry(10f, 10f)) - entries.add(Entry(15f, 2f)) - entries.add(Entry(21f, 5f)) + val entries: MutableList = mutableListOf() + entries.add(EntryFloat(10f, 10f)) + entries.add(EntryFloat(15f, 2f)) + entries.add(EntryFloat(21f, 5f)) val set = ScatterDataSet(entries, "") @@ -24,7 +24,7 @@ class DataSetTest { Assert.assertEquals(3, set.entryCount) - set.addEntry(Entry(25f, 1f)) + set.addEntry(EntryFloat(25f, 1f)) Assert.assertEquals(10f, set.xMin, 0.01f) Assert.assertEquals(25f, set.xMax, 0.01f) @@ -45,16 +45,16 @@ class DataSetTest { @Test fun testAddRemoveEntry() { - val entries: MutableList = mutableListOf() - entries.add(Entry(10f, 10f)) - entries.add(Entry(15f, 2f)) - entries.add(Entry(21f, 5f)) + val entries: MutableList = mutableListOf() + entries.add(EntryFloat(10f, 10f)) + entries.add(EntryFloat(15f, 2f)) + entries.add(EntryFloat(21f, 5f)) val set = ScatterDataSet(entries, "") Assert.assertEquals(3, set.entryCount) - set.addEntryOrdered(Entry(5f, 1f)) + set.addEntryOrdered(EntryFloat(5f, 1f)) Assert.assertEquals(4, set.entryCount) @@ -67,7 +67,7 @@ class DataSetTest { Assert.assertEquals(5f, set.getEntryForIndex(0)!!.x, 0.01f) Assert.assertEquals(1f, set.getEntryForIndex(0)!!.y, 0.01f) - set.addEntryOrdered(Entry(20f, 50f)) + set.addEntryOrdered(EntryFloat(20f, 50f)) Assert.assertEquals(5, set.entryCount) @@ -91,7 +91,7 @@ class DataSetTest { Assert.assertEquals(10f, set.getEntryForIndex(0)!!.x, 0.01f) Assert.assertEquals(10f, set.getEntryForIndex(0)!!.y, 0.01f) - set.addEntryOrdered(Entry(15f, 3f)) + set.addEntryOrdered(EntryFloat(15f, 3f)) Assert.assertEquals(4, set.entryCount) @@ -129,10 +129,10 @@ class DataSetTest { @Test fun testGetEntryForXValue() { - val entries: MutableList = mutableListOf() - entries.add(Entry(10f, 10f)) - entries.add(Entry(15f, 5f)) - entries.add(Entry(21f, 5f)) + val entries: MutableList = mutableListOf() + entries.add(EntryFloat(10f, 10f)) + entries.add(EntryFloat(15f, 5f)) + entries.add(EntryFloat(21f, 5f)) val set = ScatterDataSet(entries, "") @@ -169,19 +169,19 @@ class DataSetTest { fun testGetEntryForXValueWithDuplicates() { // sorted list of values (by x position) - val values: MutableList = ArrayList() - values.add(Entry(0f, 10f)) - values.add(Entry(1f, 20f)) - values.add(Entry(2f, 30f)) - values.add(Entry(3f, 40f)) - values.add(Entry(3f, 50f)) // duplicate - values.add(Entry(4f, 60f)) - values.add(Entry(4f, 70f)) // duplicate - values.add(Entry(5f, 80f)) - values.add(Entry(6f, 90f)) - values.add(Entry(7f, 100f)) - values.add(Entry(8f, 110f)) - values.add(Entry(8f, 120f)) // duplicate + val values: MutableList = ArrayList() + values.add(EntryFloat(0f, 10f)) + values.add(EntryFloat(1f, 20f)) + values.add(EntryFloat(2f, 30f)) + values.add(EntryFloat(3f, 40f)) + values.add(EntryFloat(3f, 50f)) // duplicate + values.add(EntryFloat(4f, 60f)) + values.add(EntryFloat(4f, 70f)) // duplicate + values.add(EntryFloat(5f, 80f)) + values.add(EntryFloat(6f, 90f)) + values.add(EntryFloat(7f, 100f)) + values.add(EntryFloat(8f, 110f)) + values.add(EntryFloat(8f, 120f)) // duplicate val set = ScatterDataSet(values, "") diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BarChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BarChartComposable.kt index 66541d4e8..a63a0f564 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BarChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BarChartComposable.kt @@ -14,7 +14,7 @@ import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis import info.appdev.charting.components.YAxis import info.appdev.charting.data.BarData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -53,7 +53,7 @@ fun BarChart( data: BarData?, modifier: Modifier = Modifier, state: BarChartState = rememberBarChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, xAxisConfig: ((XAxis) -> Unit)? = null, @@ -140,8 +140,8 @@ fun BarChart( // Selection listener if (onValueSelected != null) { barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) { - onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { + onValueSelected(entryFloat, highlight) } override fun onNothingSelected() { diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BubbleChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BubbleChartComposable.kt index 42134afa1..f2175a281 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BubbleChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BubbleChartComposable.kt @@ -14,7 +14,7 @@ import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis import info.appdev.charting.components.YAxis import info.appdev.charting.data.BubbleData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -26,7 +26,7 @@ fun BubbleChart( data: BubbleData?, modifier: Modifier = Modifier, state: BubbleChartState = rememberBubbleChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, xAxisConfig: ((XAxis) -> Unit)? = null, @@ -92,7 +92,7 @@ fun BubbleChart( if (onValueSelected != null) { bubbleChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = onValueSelected(entryFloat, highlight) override fun onNothingSelected() = onValueSelected(null, null) }) } diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CandleStickChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CandleStickChartComposable.kt index 892d2fbf6..de893eaf8 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CandleStickChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CandleStickChartComposable.kt @@ -14,7 +14,7 @@ import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis import info.appdev.charting.components.YAxis import info.appdev.charting.data.CandleData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -26,7 +26,7 @@ fun CandleStickChart( data: CandleData?, modifier: Modifier = Modifier, state: CandleStickChartState = rememberCandleStickChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, xAxisConfig: ((XAxis) -> Unit)? = null, @@ -92,7 +92,7 @@ fun CandleStickChart( if (onValueSelected != null) { candleChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = onValueSelected(entryFloat, highlight) override fun onNothingSelected() = onValueSelected(null, null) }) } diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CombinedChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CombinedChartComposable.kt index 3c196f471..e5a9d7fe4 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CombinedChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CombinedChartComposable.kt @@ -14,7 +14,7 @@ import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis import info.appdev.charting.components.YAxis import info.appdev.charting.data.CombinedData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -26,7 +26,7 @@ fun CombinedChart( data: CombinedData?, modifier: Modifier = Modifier, state: CombinedChartState = rememberCombinedChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, xAxisConfig: ((XAxis) -> Unit)? = null, @@ -108,7 +108,7 @@ fun CombinedChart( if (onValueSelected != null) { combinedChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = onValueSelected(entryFloat, highlight) override fun onNothingSelected() = onValueSelected(null, null) }) } diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/HorizontalBarChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/HorizontalBarChartComposable.kt index d76bae0ff..6a0a43320 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/HorizontalBarChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/HorizontalBarChartComposable.kt @@ -14,7 +14,7 @@ import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis import info.appdev.charting.components.YAxis import info.appdev.charting.data.BarData -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -26,7 +26,7 @@ fun HorizontalBarChart( data: BarData?, modifier: Modifier = Modifier, state: HorizontalBarChartState = rememberHorizontalBarChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, xAxisConfig: ((XAxis) -> Unit)? = null, @@ -88,7 +88,7 @@ fun HorizontalBarChart( if (onValueSelected != null) { horizontalBarChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = onValueSelected(entryFloat, highlight) override fun onNothingSelected() = onValueSelected(null, null) }) } diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/LineChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/LineChartComposable.kt index 97c511ac9..fd7de38fd 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/LineChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/LineChartComposable.kt @@ -13,7 +13,7 @@ import info.appdev.charting.charts.LineChart import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis import info.appdev.charting.components.YAxis -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -54,7 +54,7 @@ fun LineChart( data: LineData?, modifier: Modifier = Modifier, state: LineChartState = rememberLineChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, xAxisConfig: ((XAxis) -> Unit)? = null, @@ -145,8 +145,8 @@ fun LineChart( // Selection listener if (onValueSelected != null) { lineChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) { - onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { + onValueSelected(entryFloat, highlight) } override fun onNothingSelected() { diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/PieChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/PieChartComposable.kt index 987c4f27f..bd8957433 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/PieChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/PieChartComposable.kt @@ -11,7 +11,7 @@ import androidx.compose.ui.viewinterop.AndroidView import info.appdev.charting.animation.Easing import info.appdev.charting.charts.PieChart import info.appdev.charting.components.Legend -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.PieData import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -47,7 +47,7 @@ fun PieChart( data: PieData?, modifier: Modifier = Modifier, state: PieChartState = rememberPieChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, @@ -124,8 +124,8 @@ fun PieChart( // Selection listener if (onValueSelected != null) { pieChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) { - onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { + onValueSelected(entryFloat, highlight) } override fun onNothingSelected() { diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/RadarChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/RadarChartComposable.kt index 42b23c963..c43374268 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/RadarChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/RadarChartComposable.kt @@ -13,7 +13,7 @@ import info.appdev.charting.charts.RadarChart import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis import info.appdev.charting.components.YAxis -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.RadarData import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -26,7 +26,7 @@ fun RadarChart( data: RadarData?, modifier: Modifier = Modifier, state: RadarChartState = rememberRadarChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, xAxisConfig: ((XAxis) -> Unit)? = null, @@ -82,7 +82,7 @@ fun RadarChart( if (onValueSelected != null) { radarChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = onValueSelected(entryFloat, highlight) override fun onNothingSelected() = onValueSelected(null, null) }) } diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ScatterChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ScatterChartComposable.kt index 0ec1bef76..7e84575c6 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ScatterChartComposable.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ScatterChartComposable.kt @@ -13,7 +13,7 @@ import info.appdev.charting.charts.ScatterChart import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis import info.appdev.charting.components.YAxis -import info.appdev.charting.data.Entry +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.ScatterData import info.appdev.charting.highlight.Highlight import info.appdev.charting.listener.OnChartValueSelectedListener @@ -26,7 +26,7 @@ fun ScatterChart( data: ScatterData?, modifier: Modifier = Modifier, state: ScatterChartState = rememberScatterChartState(), - onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + onValueSelected: ((EntryFloat?, Highlight?) -> Unit)? = null, description: String? = null, legend: ((Legend) -> Unit)? = null, xAxisConfig: ((XAxis) -> Unit)? = null, @@ -92,7 +92,7 @@ fun ScatterChart( if (onValueSelected != null) { scatterChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { - override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = onValueSelected(entryFloat, highlight) override fun onNothingSelected() = onValueSelected(null, null) }) } diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/examples/ChartExamples.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/examples/ChartExamples.kt index df3e43848..7ffb5fbca 100644 --- a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/examples/ChartExamples.kt +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/examples/ChartExamples.kt @@ -33,16 +33,16 @@ import info.appdev.charting.compose.ScatterChart import info.appdev.charting.compose.rememberLineChartState import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet -import info.appdev.charting.data.BarEntry -import info.appdev.charting.data.Entry +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet import info.appdev.charting.data.PieData import info.appdev.charting.data.PieDataSet -import info.appdev.charting.data.PieEntry +import info.appdev.charting.data.PieEntryFloat import info.appdev.charting.data.RadarData import info.appdev.charting.data.RadarDataSet -import info.appdev.charting.data.RadarEntry +import info.appdev.charting.data.RadarEntryFloat import info.appdev.charting.data.ScatterData import info.appdev.charting.data.ScatterDataSet @@ -77,7 +77,7 @@ fun LineChartExample() { Spacer(modifier = Modifier.height(8.dp)) val entries = remember { - (0..10).map { Entry(it.toFloat(), (10..50).random().toFloat()) } + (0..10).map { EntryFloat(it.toFloat(), (10..50).random().toFloat()) } } val dataSet = remember(entries) { @@ -144,7 +144,7 @@ fun BarChartExample() { Spacer(modifier = Modifier.height(8.dp)) val entries = remember { - (0..5).map { BarEntry(it.toFloat(), (20..80).random().toFloat()) } + (0..5).map { BarEntryFloat(it.toFloat(), (20..80).random().toFloat()) } } val dataSet = remember(entries) { @@ -188,11 +188,11 @@ fun PieChartExample() { val entries = remember { listOf( - PieEntry(30f, "Product A"), - PieEntry(25f, "Product B"), - PieEntry(20f, "Product C"), - PieEntry(15f, "Product D"), - PieEntry(10f, "Product E") + PieEntryFloat(30f, "Product A"), + PieEntryFloat(25f, "Product B"), + PieEntryFloat(20f, "Product C"), + PieEntryFloat(15f, "Product D"), + PieEntryFloat(10f, "Product E") ) } @@ -247,7 +247,7 @@ fun ScatterChartExample() { val entries = remember { (0..20).map { - Entry(it.toFloat(), (10..50).random().toFloat()) + EntryFloat(it.toFloat(), (10..50).random().toFloat()) } } @@ -286,11 +286,11 @@ fun RadarChartExample() { val entries = remember { listOf( - RadarEntry(8f, "Speed"), - RadarEntry(7f, "Strength"), - RadarEntry(6f, "Defense"), - RadarEntry(9f, "Agility"), - RadarEntry(7f, "Intelligence") + RadarEntryFloat(8f, "Speed"), + RadarEntryFloat(7f, "Strength"), + RadarEntryFloat(6f, "Defense"), + RadarEntryFloat(9f, "Agility"), + RadarEntryFloat(7f, "Intelligence") ) } @@ -367,7 +367,7 @@ fun DynamicChartExample() { Button( onClick = { val entries = (0 until dataPoints).map { - Entry(it.toFloat(), (10..50).random().toFloat()) + EntryFloat(it.toFloat(), (10..50).random().toFloat()) } val dataSet = LineDataSet(entries.toMutableList(), "Random Data") state.data = LineData(dataSet) From a39367112eb3cb7c7cfada9573be21fcb5dd5516 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 11:40:14 +0200 Subject: [PATCH 15/36] IDataSet generic Use BaseEntry instead of FloatEntry --- .../src/main/kotlin/info/appdev/charting/data/ChartData.kt | 4 ++-- .../info/appdev/charting/highlight/ChartHighlighter.kt | 6 +++--- .../appdev/charting/highlight/HorizontalBarHighlighter.kt | 6 +++--- .../datasets/IBarLineScatterCandleBubbleDataSet.kt | 4 ++-- .../info/appdev/charting/interfaces/datasets/IDataSet.kt | 4 ++-- .../charting/interfaces/datasets/ILineRadarDataSet.kt | 4 ++-- .../interfaces/datasets/ILineScatterCandleRadarDataSet.kt | 4 ++-- .../charting/renderer/BarLineScatterCandleBubbleRenderer.kt | 6 +++--- 8 files changed, 19 insertions(+), 19 deletions(-) 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 7902f3ecd..84ee4ea1c 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/ChartData.kt @@ -52,7 +52,7 @@ abstract class ChartData> : Serializable { protected set constructor() { - this.dataSets = ArrayList() + this.dataSets = ArrayList() } constructor(vararg dataSets: T) { @@ -446,7 +446,7 @@ abstract class ChartData> : Serializable { } val dataSet: IDataSet<*> = dataSets[dataSetIndex] - val entryFloat: EntryFloat = dataSet.getEntryForXValue(xValue, Float.NaN) ?: return false + val entryFloat: EntryFloat = dataSet.getEntryForXValue(xValue, Float.NaN) as? EntryFloat ?: return false return removeEntry(entryFloat, dataSetIndex) } 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 9d0eac051..ddd760648 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/ChartHighlighter.kt @@ -122,12 +122,12 @@ open class ChartHighlighter>(prote ): MutableList { val highlights = ArrayList() - var entries = set.getEntriesForXValue(xVal) + var entries = set.getEntriesForXValue(xVal)?.map { it as EntryFloat }?.toMutableList() if (entries != null && entries.isEmpty()) { // Try to find closest x-value and take all entries for that x-value - val closest: EntryFloat? = set.getEntryForXValue(xVal, Float.NaN, rounding) + val closest: EntryFloat? = set.getEntryForXValue(xVal, Float.NaN, rounding) as? EntryFloat if (closest != null) { - entries = set.getEntriesForXValue(closest.x) + entries = set.getEntriesForXValue(closest.x)?.map { it as EntryFloat }?.toMutableList() } } 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 8354c9fe4..8e61df89d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/highlight/HorizontalBarHighlighter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/HorizontalBarHighlighter.kt @@ -35,12 +35,12 @@ class HorizontalBarHighlighter(dataProvider: BarDataProvider) : BarHighlighter(d override fun buildHighlights(set: IDataSet<*>, dataSetIndex: Int, xVal: Float, rounding: DataSet.Rounding?): MutableList { val highlights = ArrayList() - var entries = set.getEntriesForXValue(xVal) + var entries = set.getEntriesForXValue(xVal)?.map { it as EntryFloat }?.toMutableList() if (entries != null && entries.isEmpty()) { // Try to find closest x-value and take all entries for that x-value - val closestEntryFloat: EntryFloat? = set.getEntryForXValue(xVal, Float.NaN, rounding) + val closestEntryFloat: EntryFloat? = set.getEntryForXValue(xVal, Float.NaN, rounding) as? EntryFloat closestEntryFloat?.let { closestE -> - entries = set.getEntriesForXValue(closestE.x) + entries = set.getEntriesForXValue(closestE.x)?.map { it as EntryFloat }?.toMutableList() } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBarLineScatterCandleBubbleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBarLineScatterCandleBubbleDataSet.kt index ead34a8bc..620d555cb 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBarLineScatterCandleBubbleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IBarLineScatterCandleBubbleDataSet.kt @@ -1,8 +1,8 @@ package info.appdev.charting.interfaces.datasets -import info.appdev.charting.data.EntryFloat +import info.appdev.charting.data.BaseEntry -interface IBarLineScatterCandleBubbleDataSet : IDataSet { +interface IBarLineScatterCandleBubbleDataSet> : IDataSet { val highLightColor: Int } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt index 43db6096f..d0f0ceef2 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/IDataSet.kt @@ -5,11 +5,11 @@ import android.graphics.Typeface import info.appdev.charting.components.Legend import info.appdev.charting.components.YAxis import info.appdev.charting.data.DataSet -import info.appdev.charting.data.EntryFloat +import info.appdev.charting.data.BaseEntry import info.appdev.charting.formatter.IValueFormatter import info.appdev.charting.utils.PointF -interface IDataSet { +interface IDataSet> { /** * returns the minimum y-value this DataSet holds */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineRadarDataSet.kt index 050bc462f..31ebc9986 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineRadarDataSet.kt @@ -1,9 +1,9 @@ package info.appdev.charting.interfaces.datasets import android.graphics.drawable.Drawable -import info.appdev.charting.data.EntryFloat +import info.appdev.charting.data.BaseEntry -interface ILineRadarDataSet : ILineScatterCandleRadarDataSet { +interface ILineRadarDataSet> : ILineScatterCandleRadarDataSet { /** * Returns the color that is used for filling the line surface area. */ diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineScatterCandleRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineScatterCandleRadarDataSet.kt index f260a293e..8e1f2868e 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineScatterCandleRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineScatterCandleRadarDataSet.kt @@ -1,9 +1,9 @@ package info.appdev.charting.interfaces.datasets import android.graphics.DashPathEffect -import info.appdev.charting.data.EntryFloat +import info.appdev.charting.data.BaseEntry -interface ILineScatterCandleRadarDataSet : IBarLineScatterCandleBubbleDataSet { +interface ILineScatterCandleRadarDataSet> : IBarLineScatterCandleBubbleDataSet { /** * Returns true if vertical highlight indicator lines are enabled (drawn) */ 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 577b42a86..8b256869f 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/BarLineScatterCandleBubbleRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/BarLineScatterCandleBubbleRenderer.kt @@ -2,7 +2,7 @@ package info.appdev.charting.renderer import info.appdev.charting.animation.ChartAnimator import info.appdev.charting.data.DataSet -import info.appdev.charting.data.EntryFloat +import info.appdev.charting.data.BaseEntry import info.appdev.charting.interfaces.dataprovider.base.BarLineScatterCandleBubbleDataProvider import info.appdev.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet import info.appdev.charting.interfaces.datasets.IDataSet @@ -29,7 +29,7 @@ abstract class BarLineScatterCandleBubbleRenderer( /** * Checks if the provided entry object is in bounds for drawing considering the current animation phase. */ - protected fun isInBoundsX(entry: T, set: IBarLineScatterCandleBubbleDataSet): Boolean { + protected fun > isInBoundsX(entry: T, set: IBarLineScatterCandleBubbleDataSet): Boolean { val entryIndex = set.getEntryIndex(entry).toFloat() return if (entryIndex >= set.entryCount * animator.phaseX) { @@ -61,7 +61,7 @@ abstract class BarLineScatterCandleBubbleRenderer( /** * Calculates the minimum and maximum x values as well as the range between them. */ - fun set(chart: BarLineScatterCandleBubbleDataProvider<*>, dataSet: IBarLineScatterCandleBubbleDataSet) { + fun > set(chart: BarLineScatterCandleBubbleDataProvider<*>, dataSet: IBarLineScatterCandleBubbleDataSet) { val phaseX = max(0f, min(1f, animator.phaseX)) val low = chart.lowestVisibleX From e76041b01e8142e24f7254abf25a5f6b898fc370 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 14:01:16 +0200 Subject: [PATCH 16/36] Add x to BaseEntry --- .../info/appdev/charting/data/BaseEntry.kt | 27 +++++++++- .../info/appdev/charting/data/EntryDouble.kt | 49 +++++++------------ .../info/appdev/charting/data/EntryFloat.kt | 49 +++++++------------ 3 files changed, 64 insertions(+), 61 deletions(-) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseEntry.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseEntry.kt index b3f2725b5..fcc4bf3f1 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseEntry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseEntry.kt @@ -4,6 +4,13 @@ import android.graphics.drawable.Drawable abstract class BaseEntry where T : Number, T : Comparable { + protected lateinit var xBase: T + open var x: T + get() = xBase + set(value) { + xBase = value + } + protected lateinit var yBase: T open var y: T get() = yBase @@ -21,6 +28,11 @@ abstract class BaseEntry where T : Number, T : Comparable { this.yBase = y } + constructor(x: T, y: T) { + this.xBase = x + this.yBase = y + } + constructor(y: T, data: Any?) : this(y) { this.data = data } @@ -29,7 +41,20 @@ abstract class BaseEntry where T : Number, T : Comparable { this.icon = icon } - constructor(y: T, icon: Drawable?, data: Any?) : this(y) { + constructor(y: T, icon: Drawable?, data: Any?) : this(y = y) { + this.icon = icon + this.data = data + } + + constructor(x: T, y: T, data: Any?) : this(x = x, y = y) { + this.data = data + } + + constructor(x: T, y: T, icon: Drawable?) : this(x = x, y = y) { + this.icon = icon + } + + constructor(x: T, y: T, icon: Drawable?, data: Any?) : this(x = x, y = y) { this.icon = icon this.data = data } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt index 955460af8..0a88e0bed 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt @@ -11,64 +11,53 @@ import kotlin.math.abs open class EntryDouble : BaseEntry, Parcelable, Serializable { - private var _x: Double = 0.0 - open var x: Double - get() = _x - set(value) { - _x = value - } - constructor() /** - * A Entry represents one single entry in the chart. + * An Entry 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: Double, y: Double) : super(y) { - this._x = x - } + constructor(x: Double, y: Double) : super(x = x, y = y) /** - * A Entry represents one single entry in the chart. + * An Entry 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: Double, y: Double, data: Any?) : super(y, data) { - this._x = x - } + constructor(x: Double, y: Double, data: Any?) : super(x = x, y = y, data = data) /** - * A Entry represents one single entry in the chart. + * An Entry 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: Double, y: Double, icon: Drawable?) : super(y, icon) { - this._x = x - } + constructor(x: Double, y: Double, icon: Drawable?) : super(x = x, y = y, icon = icon) /** - * A Entry represents one single entry in the chart. + * An Entry 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 Entry represents. */ - constructor(x: Double, y: Double, icon: Drawable?, data: Any?) : super(y, icon, data) { - this._x = x - } + constructor(x: Double, y: Double, icon: Drawable?, data: Any?) : super(x = x, y = y, icon = icon, data = data) /** * returns an exact copy of the entry */ open fun copy(): EntryDouble { - val e = EntryDouble(x, y, data) + val e = EntryDouble( + x = x, + y = y, + data = data + ) return e } @@ -77,17 +66,17 @@ open class EntryDouble : BaseEntry, Parcelable, Serializable { * are equal in those points, false if not. Does not check by hash-code like * it's done by the "equals" method. */ - fun equalTo(e: EntryDouble?): Boolean { - if (e == null) + fun equalTo(entryDouble: EntryDouble?): Boolean { + if (entryDouble == null) return false - if (e.data !== this.data) + if (entryDouble.data !== this.data) return false - if (abs((e.x - this.x)) > Utils.DOUBLE_EPSILON) + if (abs((entryDouble.x - this.x)) > Utils.DOUBLE_EPSILON) return false - if (abs((e.y - this.y)) > Utils.DOUBLE_EPSILON) + if (abs((entryDouble.y - this.y)) > Utils.DOUBLE_EPSILON) return false return true @@ -124,7 +113,7 @@ open class EntryDouble : BaseEntry, Parcelable, Serializable { } protected constructor(`in`: Parcel) { - this._x = `in`.readDouble() + this.x = `in`.readDouble() this.yBase = `in`.readDouble() if (`in`.readInt() == 1) { this.data = if (Build.VERSION.SDK_INT >= 33) { diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/EntryFloat.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryFloat.kt index 431d26155..f25202e98 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/EntryFloat.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryFloat.kt @@ -15,64 +15,53 @@ import kotlin.math.abs */ open class EntryFloat : BaseEntry, Parcelable, Serializable { - private var _x: Float = 0f - open var x: Float - get() = _x - set(value) { - _x = value - } - constructor() /** - * A EntryFloat represents one single entry in the chart. + * 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(y) { - this._x = x - } + constructor(x: Float, y: Float) : super(x = x, y = y) /** - * A EntryFloat represents one single entry in the chart. + * 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(y, data) { - this._x = x - } + constructor(x: Float, y: Float, data: Any?) : super(x = x, y = y, data = data) /** - * A EntryFloat represents one single entry in the chart. + * 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(y, icon) { - this._x = x - } + constructor(x: Float, y: Float, icon: Drawable?) : super(x = x, y = y, icon = icon) /** - * A EntryFloat represents one single entry in the chart. + * 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(y, icon, data) { - this._x = x - } + constructor(x: Float, y: Float, icon: Drawable?, data: Any?) : super(x = x, y = y, icon = icon, data = data) /** * returns an exact copy of the entry */ open fun copy(): EntryFloat { - val e = EntryFloat(x, y, data) + val e = EntryFloat( + x = x, + y = y, + data = data + ) return e } @@ -81,17 +70,17 @@ open class EntryFloat : BaseEntry, Parcelable, Serializable { * are equal in those points, false if not. Does not check by hash-code like * it's done by the "equals" method. */ - fun equalTo(e: EntryFloat?): Boolean { - if (e == null) + fun equalTo(entryFloat: EntryFloat?): Boolean { + if (entryFloat == null) return false - if (e.data !== this.data) + if (entryFloat.data !== this.data) return false - if (abs((e.x - this.x).toDouble()) > Utils.FLOAT_EPSILON) + if (abs((entryFloat.x - this.x).toDouble()) > Utils.FLOAT_EPSILON) return false - if (abs((e.y - this.y).toDouble()) > Utils.FLOAT_EPSILON) + if (abs((entryFloat.y - this.y).toDouble()) > Utils.FLOAT_EPSILON) return false return true @@ -128,7 +117,7 @@ open class EntryFloat : BaseEntry, Parcelable, Serializable { } protected constructor(`in`: Parcel) { - this._x = `in`.readFloat() + this.x = `in`.readFloat() this.yBase = `in`.readFloat() if (`in`.readInt() == 1) { this.data = if (Build.VERSION.SDK_INT >= 33) { From ab5e1493425e3ce50280ba7c7641c9084d7e165c Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 14:43:16 +0200 Subject: [PATCH 17/36] DataSet more generic --- .../data/BarLineScatterCandleBubbleDataSet.kt | 2 +- .../info/appdev/charting/data/BaseDataSet.kt | 2 +- .../info/appdev/charting/data/DataSet.kt | 37 +++++-------------- .../appdev/charting/data/LineRadarDataSet.kt | 2 +- .../data/LineScatterCandleRadarDataSet.kt | 2 +- 5 files changed, 14 insertions(+), 31 deletions(-) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleDataSet.kt index 40096e196..2751e4703 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarLineScatterCandleBubbleDataSet.kt @@ -7,7 +7,7 @@ import info.appdev.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataS /** * Baseclass of all DataSets for Bar-, Line-, Scatter- and CandleStickChart. */ -abstract class BarLineScatterCandleBubbleDataSet(yVals: MutableList, label: String) : +abstract class BarLineScatterCandleBubbleDataSet>(yVals: MutableList, label: String) : DataSet(yVals, label), IBarLineScatterCandleBubbleDataSet { /** * Sets the color that is used for drawing the highlight indicators. diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt index 6ba5af063..110ae26cb 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BaseDataSet.kt @@ -19,7 +19,7 @@ import info.appdev.charting.utils.convertDpToPixel * This is the base dataset of all DataSets. Its purpose is to implement critical methods * provided by the IDataSet interface. */ -abstract class BaseDataSet() : IDataSet { +abstract class BaseDataSet>() : IDataSet { /** * List representing all colors that are used for this DataSet */ 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 9a0d5059e..33b87d315 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/DataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/DataSet.kt @@ -10,7 +10,7 @@ import kotlin.math.abs * groups of values inside the Chart (e.g. the values for a specific line in the * LineChart, or the values of a specific group of bars in the BarChart). */ -abstract class DataSet( +abstract class DataSet>( protected var entriesInternal: MutableList, label: String = "" ) : BaseDataSet(label), Serializable { @@ -219,31 +219,22 @@ abstract class DataSet( while (low < high) { val m = low + (high - low) / 2 - val currentEntryFloat: EntryFloat = entriesInternal[m] + val current = entriesInternal[m] + val next = entriesInternal[m + 1] - val nextEntryFloat: EntryFloat = entriesInternal[m + 1] - - val d1 = currentEntryFloat.x - xValue - val d2 = nextEntryFloat.x - xValue + val d1 = current.x - xValue + val d2 = next.x - xValue val ad1 = abs(d1) val ad2 = abs(d2) if (ad2 < ad1) { - // [m + 1] is closer to xValue - // Search in a higher place low = m + 1 } else if (ad1 < ad2) { - // [m] is closer to xValue - // Search in a lower place high = m } else { - // We have multiple sequential x-value with same distance - if (d1 >= 0.0) { - // Search in a lower place high = m } else if (d1 < 0.0) { - // Search in a higher place low = m + 1 } } @@ -251,40 +242,32 @@ abstract class DataSet( closest = high } - val closestEntryFloat: EntryFloat = entriesInternal[closest] - val closestXValue = closestEntryFloat.x + val closestEntry = entriesInternal[closest] + val closestXValue = closestEntry.x if (rounding == Rounding.UP) { - // If rounding up, and found x-value is lower than specified x, and we can go upper... if (closestXValue < xValue && closest < entriesInternal.size - 1) { ++closest } } else if (rounding == Rounding.DOWN) { - // If rounding down, and found x-value is upper than specified x, and we can go lower... if (closestXValue > xValue && closest > 0) { --closest } } - // Search by closest to y-value if (!closestToY.isNaN()) { while (closest > 0 && entriesInternal[closest - 1].x == closestXValue) { closest -= 1 } - var closestYValue = closestEntryFloat.y + var closestYValue = closestEntry.y var closestYIndex = closest while (true) { closest += 1 - if (closest >= entriesInternal.size) { - break - } + if (closest >= entriesInternal.size) break val value: T = entriesInternal[closest] - - if (value.x != closestXValue) { - break - } + if (value.x != closestXValue) break if (abs(value.y - closestToY) <= abs(closestYValue - closestToY)) { closestYValue = closestToY diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/LineRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/LineRadarDataSet.kt index cb52f42bc..c1b270955 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineRadarDataSet.kt @@ -9,7 +9,7 @@ import info.appdev.charting.utils.convertDpToPixel /** * Base dataset for line and radar DataSets. */ -abstract class LineRadarDataSet(yVals: MutableList, label: String) : LineScatterCandleRadarDataSet(yVals, label), ILineRadarDataSet { +abstract class LineRadarDataSet>(yVals: MutableList, label: String) : LineScatterCandleRadarDataSet(yVals, label), ILineRadarDataSet { // TODO: Move to using `Fill` class /** * the color that is used for filling the line surface diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/LineScatterCandleRadarDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/LineScatterCandleRadarDataSet.kt index c5f782fc4..5a2a9a811 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineScatterCandleRadarDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineScatterCandleRadarDataSet.kt @@ -4,7 +4,7 @@ import android.graphics.DashPathEffect import info.appdev.charting.interfaces.datasets.ILineScatterCandleRadarDataSet import info.appdev.charting.utils.convertDpToPixel -abstract class LineScatterCandleRadarDataSet(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), +abstract class LineScatterCandleRadarDataSet>(yVals: MutableList, label: String) : BarLineScatterCandleBubbleDataSet(yVals, label), ILineScatterCandleRadarDataSet { override var isVerticalHighlightIndicator: Boolean = true override var isHorizontalHighlightIndicator: Boolean = true From 9004471b1ddffc5f71f8efc9d451846f6da96ae3 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 18:33:54 +0200 Subject: [PATCH 18/36] DataSet generic --- .../chartexample/CubicLineChartActivity.kt | 6 +- .../info/appdev/chartexample/DataTools.kt | 4 +- .../chartexample/DynamicalAddingActivity.kt | 4 +- .../appdev/chartexample/FilledLineActivity.kt | 14 +- .../appdev/chartexample/GradientActivity.kt | 2 +- .../appdev/chartexample/LineChartActivity.kt | 8 +- .../chartexample/LineChartDualAxisActivity.kt | 12 +- .../ListViewMultiChartActivity.kt | 2 +- .../chartexample/MultiLineChartActivity.kt | 4 +- .../chartexample/RealtimeLineChartActivity.kt | 6 +- .../SpecificPositionsLineChartActivity.kt | 2 +- .../compose/MultiLineComposeActivity.kt | 5 +- .../chartexample/formatter/MyFillFormatter.kt | 2 +- .../chartexample/fragments/SimpleFragment.kt | 4 +- .../charting/charts/BarLineChartBase.kt | 1 + .../info/appdev/charting/data/EntryDouble.kt | 160 ++++++------------ .../info/appdev/charting/data/LineData.kt | 6 +- .../info/appdev/charting/data/LineDataSet.kt | 13 +- .../formatter/DefaultFillFormatter.kt | 2 +- .../charting/formatter/IFillFormatter.kt | 2 +- .../interfaces/datasets/ILineDataSet.kt | 4 +- .../charting/renderer/LineChartRenderer.kt | 33 ++-- .../info/appdev/charting/utils/Transformer.kt | 2 +- 23 files changed, 123 insertions(+), 175 deletions(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/CubicLineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/CubicLineChartActivity.kt index f20c39e37..73642a52f 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/CubicLineChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/CubicLineChartActivity.kt @@ -91,10 +91,10 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { values.add(EntryFloat(i.toFloat(), `val`)) } - val set1: LineDataSet + val set1: LineDataSet if (binding.chart1.lineData.dataSetCount > 0) { - set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet + set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet set1.entries = values binding.chart1.lineData.notifyDataChanged() binding.chart1.notifyDataSetChanged() @@ -115,7 +115,7 @@ class CubicLineChartActivity : DemoBase(), OnSeekBarChangeListener { set1.fillAlpha = 100 set1.isHorizontalHighlightIndicator = false set1.fillFormatter = object : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float { + override fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float { return binding.chart1.axisLeft.axisMinimum } } diff --git a/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt b/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt index 1c004b4ee..0ae78e982 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt @@ -226,7 +226,7 @@ class DataTools { // set the filled area lineDataSet01.isDrawFilled = true lineDataSet01.fillFormatter = object : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float { + override fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float { return lineChart.axisLeft.axisMinimum } } @@ -239,7 +239,7 @@ class DataTools { } else { lineDataSet01.fillColor = Color.BLACK } - val dataSets = ArrayList() + val dataSets = ArrayList>() dataSets.add(lineDataSet01) // add the data sets // create a data object with the data sets diff --git a/app/src/main/kotlin/info/appdev/chartexample/DynamicalAddingActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/DynamicalAddingActivity.kt index 82b3f64ae..e5a00dcc0 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/DynamicalAddingActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/DynamicalAddingActivity.kt @@ -147,8 +147,8 @@ class DynamicalAddingActivity : DemoBase(), OnChartValueSelectedListener { } } - private fun createSet(): LineDataSet { - val set = LineDataSet(label = "DataSet 1") + private fun createSet(): LineDataSet { + val set = LineDataSet(label = "DataSet 1") set.lineWidth = 2.5f set.circleRadius = 4.5f set.color = Color.rgb(240, 99, 99) diff --git a/app/src/main/kotlin/info/appdev/chartexample/FilledLineActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/FilledLineActivity.kt index da3e9ea1f..f8e082637 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/FilledLineActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/FilledLineActivity.kt @@ -84,12 +84,12 @@ class FilledLineActivity : DemoBase() { valuesArray2.add(EntryFloat(i.toFloat(), valueY)) } - val set1: LineDataSet - val set2: LineDataSet + val set1: LineDataSet + val set2: LineDataSet if (binding.chart1.lineData.dataSetCount > 0) { - set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet - set2 = binding.chart1.lineData.getDataSetByIndex(1) as LineDataSet + set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet + set2 = binding.chart1.lineData.getDataSetByIndex(1) as LineDataSet set1.entries = valuesArray1 set2.entries = valuesArray2 binding.chart1.lineData.notifyDataChanged() @@ -109,7 +109,7 @@ class FilledLineActivity : DemoBase() { set1.highLightColor = Color.rgb(244, 117, 117) set1.isDrawCircleHoleEnabled = false set1.fillFormatter = object : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float { + override fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float { // change the return value here to better understand the effect // return 0; return binding.chart1.axisLeft.axisMinimum @@ -129,14 +129,14 @@ class FilledLineActivity : DemoBase() { set2.isDrawCircleHoleEnabled = false set2.highLightColor = Color.rgb(244, 117, 117) set2.fillFormatter = object : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float { + override fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float { // change the return value here to better understand the effect // return 600; return binding.chart1.axisLeft.axisMaximum } } - val dataSets = ArrayList() + val dataSets = ArrayList>() dataSets.add(set1) // add the data sets dataSets.add(set2) diff --git a/app/src/main/kotlin/info/appdev/chartexample/GradientActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/GradientActivity.kt index 405ab48d7..c3fda989b 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/GradientActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/GradientActivity.kt @@ -62,7 +62,7 @@ class GradientActivity : DemoBase() { fillFormatter = object : IFillFormatter { override fun getFillLinePosition( - dataSet: ILineDataSet?, + dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider ): Float = binding.chart1.axisLeft.axisMinimum } diff --git a/app/src/main/kotlin/info/appdev/chartexample/LineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/LineChartActivity.kt index 62531d99d..ea61629c7 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/LineChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/LineChartActivity.kt @@ -181,14 +181,14 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec } R.id.actionToggleCircles -> { - binding.chart1.data?.dataSets?.map { it as LineDataSet }?.forEach { set -> + binding.chart1.data?.dataSets?.map { it as LineDataSet<*> }?.forEach { set -> set.isDrawCircles = !set.isDrawCircles } binding.chart1.invalidate() } R.id.actionToggleCubic -> { - binding.chart1.data?.dataSets?.map { it as LineDataSet }?.forEach { set -> + binding.chart1.data?.dataSets?.map { it as LineDataSet<*> }?.forEach { set -> set.lineMode = if (set.lineMode == LineDataSet.Mode.CUBIC_BEZIER) LineDataSet.Mode.LINEAR else @@ -198,7 +198,7 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec } R.id.actionToggleStepped -> { - binding.chart1.data?.dataSets?.map { it as LineDataSet }?.forEach { set -> + binding.chart1.data?.dataSets?.map { it as LineDataSet<*> }?.forEach { set -> set.lineMode = if (set.lineMode == LineDataSet.Mode.STEPPED) LineDataSet.Mode.LINEAR else @@ -208,7 +208,7 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec } R.id.actionToggleHorizontalCubic -> { - binding.chart1.data?.dataSets?.map { it as LineDataSet }?.forEach { set -> + binding.chart1.data?.dataSets?.map { it as LineDataSet<*> }?.forEach { set -> set.lineMode = if (set.lineMode == LineDataSet.Mode.HORIZONTAL_BEZIER) LineDataSet.Mode.LINEAR else diff --git a/app/src/main/kotlin/info/appdev/chartexample/LineChartDualAxisActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/LineChartDualAxisActivity.kt index aa94d3eb3..99559b132 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/LineChartDualAxisActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/LineChartDualAxisActivity.kt @@ -130,14 +130,14 @@ class LineChartDualAxisActivity : DemoBase(), OnSeekBarChangeListener, OnChartVa values3.add(EntryFloat(i.toFloat(), `val`)) } - val set1: LineDataSet - val set2: LineDataSet - val set3: LineDataSet + val set1: LineDataSet + val set2: LineDataSet + val set3: LineDataSet if (binding.chart1.lineData.dataSetCount > 0) { - set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet - set2 = binding.chart1.lineData.getDataSetByIndex(1) as LineDataSet - set3 = binding.chart1.lineData.getDataSetByIndex(2) as LineDataSet + set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet + set2 = binding.chart1.lineData.getDataSetByIndex(1) as LineDataSet + set3 = binding.chart1.lineData.getDataSetByIndex(2) as LineDataSet set1.entries = values1 set2.entries = values2 set3.entries = values3 diff --git a/app/src/main/kotlin/info/appdev/chartexample/ListViewMultiChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/ListViewMultiChartActivity.kt index 2465828a0..815516354 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/ListViewMultiChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/ListViewMultiChartActivity.kt @@ -116,7 +116,7 @@ class ListViewMultiChartActivity : DemoBase() { d2.setCircleColor(ColorTemplate.VORDIPLOM_COLORS[0]) d2.isDrawValues = false - val sets = ArrayList() + val sets = ArrayList>() sets.add(d1) sets.add(d2) diff --git a/app/src/main/kotlin/info/appdev/chartexample/MultiLineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/MultiLineChartActivity.kt index 0f6222011..3a45531cb 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/MultiLineChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/MultiLineChartActivity.kt @@ -84,7 +84,7 @@ class MultiLineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartGestu binding.tvXMax.text = binding.seekBarX.progress.toString() binding.tvYMax.text = binding.seekBarY.progress.toString() - val dataSets = ArrayList() + val dataSets = ArrayList>() for (datasetNumber in 0..2) { val values = ArrayList() @@ -111,7 +111,7 @@ class MultiLineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartGestu } // make the first DataSet dashed - (dataSets[0] as LineDataSet).enableDashedLine(10f, 10f, 0f) + (dataSets[0] as LineDataSet<*>).enableDashedLine(10f, 10f, 0f) val data = LineData(dataSets) binding.chart1.data = data diff --git a/app/src/main/kotlin/info/appdev/chartexample/RealtimeLineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/RealtimeLineChartActivity.kt index fbbe5ab59..1e9b361f3 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/RealtimeLineChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/RealtimeLineChartActivity.kt @@ -92,7 +92,7 @@ class RealtimeLineChartActivity : DemoBase(), OnChartValueSelectedListener { data.addDataSet(set) } - val cycleValue = (set.entryCount % 100.0).toInt() + val cycleValue = (set!!.entryCount % 100.0).toInt() data.addEntry(EntryFloat(set.entryCount.toFloat(), (sampleValues[cycleValue]!!.toFloat() * 40) + 30f), 0) data.notifyDataChanged() @@ -112,8 +112,8 @@ class RealtimeLineChartActivity : DemoBase(), OnChartValueSelectedListener { // AxisDependency.LEFT); } - private fun createSet(): LineDataSet { - val set = LineDataSet(label = "Dynamic Data") + private fun createSet(): LineDataSet { + val set = LineDataSet(label = "Dynamic Data") set.axisDependency = AxisDependency.LEFT set.color = ColorTemplate.holoBlue set.setCircleColor(Color.WHITE) diff --git a/app/src/main/kotlin/info/appdev/chartexample/SpecificPositionsLineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/SpecificPositionsLineChartActivity.kt index d4e53c47b..a586736b0 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/SpecificPositionsLineChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/SpecificPositionsLineChartActivity.kt @@ -269,7 +269,7 @@ class SpecificPositionsLineChartActivity : DemoBase(), OnSeekBarChangeListener, } else { set11.fillColor = Color.BLACK } - val dataSets = ArrayList() + val dataSets = ArrayList>() dataSets.add(set11) // add the datasets // create a data object with the datasets diff --git a/app/src/main/kotlin/info/appdev/chartexample/compose/MultiLineComposeActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/compose/MultiLineComposeActivity.kt index e69353595..5f7f4dbe0 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/compose/MultiLineComposeActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/compose/MultiLineComposeActivity.kt @@ -326,7 +326,7 @@ class MultiLineComposeActivity : DemoBaseCompose() { showCircles: Boolean, lineMode: LineDataSet.Mode ): LineData { - val dataSets = ArrayList() + val dataSets = ArrayList>() for (datasetNumber in 0..2) { val values = ArrayList() @@ -359,6 +359,7 @@ class MultiLineComposeActivity : DemoBaseCompose() { // Make the first DataSet dashed dataSets[0].enableDashedLine(10f, 10f, 0f) - return LineData(ArrayList(dataSets.map { it as info.appdev.charting.interfaces.datasets.ILineDataSet })) + @Suppress("UNCHECKED_CAST") + return LineData(ArrayList(dataSets.map { it as info.appdev.charting.interfaces.datasets.ILineDataSet })) } } diff --git a/app/src/main/kotlin/info/appdev/chartexample/formatter/MyFillFormatter.kt b/app/src/main/kotlin/info/appdev/chartexample/formatter/MyFillFormatter.kt index 808db2daf..7f7b20852 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/formatter/MyFillFormatter.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/formatter/MyFillFormatter.kt @@ -6,7 +6,7 @@ import info.appdev.charting.interfaces.datasets.ILineDataSet @Suppress("unused") class MyFillFormatter(private val fillPos: Float) : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float { + override fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float { // your logic could be here return fillPos } diff --git a/app/src/main/kotlin/info/appdev/chartexample/fragments/SimpleFragment.kt b/app/src/main/kotlin/info/appdev/chartexample/fragments/SimpleFragment.kt index e5f5797ef..5907041a9 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/fragments/SimpleFragment.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/fragments/SimpleFragment.kt @@ -111,7 +111,7 @@ abstract class SimpleFragment : Fragment() { } protected fun generateLineData(): LineData { - val sets = ArrayList() + val sets = ArrayList>() val ds1 = LineDataSet(requireContext().assets.loadEntriesFromAssets("sine.txt"), "Sine function") val ds2 = LineDataSet(requireContext().assets.loadEntriesFromAssets("cosine.txt"), "Cosine function") @@ -135,7 +135,7 @@ abstract class SimpleFragment : Fragment() { protected val complexity: LineData get() { - val sets = ArrayList() + val sets = ArrayList>() val ds1 = LineDataSet(requireContext().assets.loadEntriesFromAssets("n.txt"), "O(n)") val ds2 = LineDataSet(requireContext().assets.loadEntriesFromAssets("nlogn.txt"), "O(nlogn)") diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt index cf38afb16..350d7e1bf 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt @@ -16,6 +16,7 @@ import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.components.YAxis import info.appdev.charting.components.YAxis.AxisDependency import info.appdev.charting.data.BarLineScatterCandleBubbleData +import info.appdev.charting.data.BaseEntry import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.ChartHighlighter import info.appdev.charting.interfaces.dataprovider.base.BarLineScatterCandleBubbleDataProvider diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt index 0a88e0bed..31b3138a9 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/EntryDouble.kt @@ -2,139 +2,81 @@ package info.appdev.charting.data 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 +import info.appdev.charting.utils.Utils -open class EntryDouble : BaseEntry, Parcelable, Serializable { - - constructor() +/** + * High-precision entry that stores x and y as Double, but extends EntryFloat + * so it works seamlessly in the existing chart rendering pipeline. + * Use [xDouble] and [yDouble] to access the full-precision values. + */ +open class EntryDouble : EntryFloat, Serializable { - /** - * An Entry 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: Double, y: Double) : super(x = x, y = y) + var xDouble: Double = 0.0 + var yDouble: Double = 0.0 /** - * An Entry 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. + * Override x to store/return via xDouble for high precision; + * the base chart pipeline reads x.toFloat() transparently. */ - constructor(x: Double, y: Double, data: Any?) : super(x = x, y = y, data = data) + override var x: Float + get() = xDouble.toFloat() + set(value) { + xDouble = value.toDouble() + } /** - * An Entry 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 + * Override y to store/return via yDouble for high precision; + * the base chart pipeline reads y.toFloat() transparently. */ - constructor(x: Double, y: Double, icon: Drawable?) : super(x = x, y = y, icon = icon) + override var y: Float + get() = yDouble.toFloat() + set(value) { + yDouble = value.toDouble() + } - /** - * An Entry 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 Entry represents. - */ - constructor(x: Double, y: Double, icon: Drawable?, data: Any?) : super(x = x, y = y, icon = icon, data = data) + constructor() : super() - /** - * returns an exact copy of the entry - */ - open fun copy(): EntryDouble { - val e = EntryDouble( - x = x, - y = y, - data = data - ) - return e + constructor(x: Double, y: Double) : super() { + this.xDouble = x + this.yDouble = y } - /** - * Compares value, xIndex and data of the entries. Returns true if entries - * are equal in those points, false if not. Does not check by hash-code like - * it's done by the "equals" method. - */ - fun equalTo(entryDouble: EntryDouble?): Boolean { - if (entryDouble == null) - return false + constructor(x: Double, y: Double, data: Any?) : super() { + this.xDouble = x + this.yDouble = y + this.data = data + } - if (entryDouble.data !== this.data) - return false + constructor(x: Double, y: Double, icon: Drawable?) : super() { + this.xDouble = x + this.yDouble = y + this.icon = icon + } - if (abs((entryDouble.x - this.x)) > Utils.DOUBLE_EPSILON) - return false + constructor(x: Double, y: Double, icon: Drawable?, data: Any?) : super() { + this.xDouble = x + this.yDouble = y + this.icon = icon + this.data = data + } - if (abs((entryDouble.y - this.y)) > Utils.DOUBLE_EPSILON) - return false + open fun copyDouble(): EntryDouble = EntryDouble(xDouble, yDouble, data) + fun equalTo(other: EntryDouble?): Boolean { + if (other == null) return false + if (other.data !== this.data) return false + if (abs(other.xDouble - this.xDouble) > Utils.DOUBLE_EPSILON) return false + if (abs(other.yDouble - this.yDouble) > Utils.DOUBLE_EPSILON) return false return true } - /** - * returns a string representation of the entry containing x-index and value - */ override fun toString(): String { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - "${this.javaClass.typeName.substringAfterLast(".")} x=$x y=$y" - } else { - "EntryDouble x=$x y=$y" - } - } - - override fun describeContents(): Int { - return 0 - } - - override fun writeToParcel(dest: Parcel, flags: Int) { - dest.writeDouble(this.x) - dest.writeDouble(this.y) - if (data != null) { - if (data is Parcelable) { - dest.writeInt(1) - dest.writeParcelable(data as Parcelable?, flags) - } else { - throw ParcelFormatException("Cannot parcel an EntryDouble with non-parcelable data") - } + "${this.javaClass.typeName.substringAfterLast(".")} xDouble=$xDouble yDouble=$yDouble" } else { - dest.writeInt(0) - } - } - - protected constructor(`in`: Parcel) { - this.x = `in`.readDouble() - this.yBase = `in`.readDouble() - if (`in`.readInt() == 1) { - this.data = if (Build.VERSION.SDK_INT >= 33) { - `in`.readParcelable(Any::class.java.classLoader, Any::class.java) - } else { - @Suppress("DEPRECATION") - `in`.readParcelable(Any::class.java.classLoader) - } - } - } - - companion object { - @JvmField - val CREATOR: Parcelable.Creator = object : Parcelable.Creator { - override fun createFromParcel(source: Parcel): EntryDouble { - return EntryDouble(source) - } - - override fun newArray(size: Int): Array { - return arrayOfNulls(size) - } + "EntryDouble xDouble=$xDouble yDouble=$yDouble" } } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/LineData.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/LineData.kt index 4736b2021..eaf30862d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineData.kt @@ -5,10 +5,10 @@ import info.appdev.charting.interfaces.datasets.ILineDataSet /** * Data object that encapsulates all data associated with a LineChart. */ -class LineData : BarLineScatterCandleBubbleData { +class LineData : BarLineScatterCandleBubbleData> { constructor() : super() - constructor(vararg dataSets: ILineDataSet) : super(*dataSets) + constructor(vararg dataSets: ILineDataSet) : super(*dataSets) - constructor(dataSets: MutableList) : super(dataSets) + constructor(dataSets: MutableList>) : super(dataSets) } 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 161aa24eb..635c017d5 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/LineDataSet.kt @@ -12,7 +12,7 @@ import info.appdev.charting.utils.ColorTemplate import info.appdev.charting.utils.convertDpToPixel import timber.log.Timber -open class LineDataSet(yVals: MutableList = mutableListOf(), label: String = "") : LineRadarDataSet(yVals, label), ILineDataSet { +open class LineDataSet>(yVals: MutableList = mutableListOf(), label: String = "") : LineRadarDataSet(yVals, label), ILineDataSet { /** * Drawing mode for this line dataset */ @@ -73,17 +73,18 @@ open class LineDataSet(yVals: MutableList = mutableListOf(), label: circleColors.add(Color.rgb(140, 234, 255)) } - override fun copy(): DataSet { + @Suppress("UNCHECKED_CAST") + override fun copy(): DataSet? { val entries: MutableList = mutableListOf() for (i in entriesInternal.indices) { - entries.add(entriesInternal[i].copy()) + entries.add((entriesInternal[i] as EntryFloat).copy()) } - val copied = LineDataSet(entries, label) + val copied = LineDataSet(entries, label) copy(copied) - return copied + return copied as DataSet } - protected fun copy(lineDataSet: LineDataSet) { + protected fun copy(lineDataSet: LineDataSet<*>) { super.copy((lineDataSet as BaseDataSet<*>?)!!) lineDataSet.circleColors = this.circleColors lineDataSet.mCircleHoleColor = mCircleHoleColor diff --git a/chartLib/src/main/kotlin/info/appdev/charting/formatter/DefaultFillFormatter.kt b/chartLib/src/main/kotlin/info/appdev/charting/formatter/DefaultFillFormatter.kt index cedd7eed5..6b403b694 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/formatter/DefaultFillFormatter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/formatter/DefaultFillFormatter.kt @@ -8,7 +8,7 @@ import info.appdev.charting.interfaces.datasets.ILineDataSet */ open class DefaultFillFormatter : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float { + override fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float { val fillMin: Float val chartMaxY = dataProvider.yChartMax val chartMinY = dataProvider.yChartMin diff --git a/chartLib/src/main/kotlin/info/appdev/charting/formatter/IFillFormatter.kt b/chartLib/src/main/kotlin/info/appdev/charting/formatter/IFillFormatter.kt index cef5444c8..a92900306 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/formatter/IFillFormatter.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/formatter/IFillFormatter.kt @@ -14,5 +14,5 @@ interface IFillFormatter { * @param dataSet the ILineDataSet that is currently drawn * @param dataProvider */ - fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float + fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineDataSet.kt b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineDataSet.kt index adb355415..e1465aa8c 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineDataSet.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/interfaces/datasets/ILineDataSet.kt @@ -1,11 +1,11 @@ package info.appdev.charting.interfaces.datasets import android.graphics.DashPathEffect -import info.appdev.charting.data.EntryFloat +import info.appdev.charting.data.BaseEntry import info.appdev.charting.data.LineDataSet import info.appdev.charting.formatter.IFillFormatter -interface ILineDataSet : ILineRadarDataSet { +interface ILineDataSet> : ILineRadarDataSet { /** * Returns the drawing mode for this line dataset */ 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 dbadb870d..14a528102 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineChartRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/LineChartRenderer.kt @@ -7,10 +7,12 @@ import android.graphics.Color 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 +import info.appdev.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet import info.appdev.charting.interfaces.datasets.IDataSet import info.appdev.charting.interfaces.datasets.ILineDataSet import info.appdev.charting.utils.ColorTemplate @@ -85,7 +87,7 @@ open class LineChartRenderer( canvas.drawBitmap(drawBitmapLocal, 0f, 0f, null) } - protected fun drawDataSet(canvas: Canvas, dataSet: ILineDataSet) { + protected fun drawDataSet(canvas: Canvas, dataSet: ILineDataSet<*>) { if (dataSet.entryCount < 1) return @@ -102,7 +104,7 @@ open class LineChartRenderer( paintRender.pathEffect = null } - protected fun drawHorizontalBezier(dataSet: ILineDataSet) { + protected fun drawHorizontalBezier(dataSet: ILineDataSet<*>) { val phaseY = animator.phaseY val trans = dataProvider.getTransformer(dataSet.axisDependency) @@ -152,7 +154,7 @@ open class LineChartRenderer( paintRender.pathEffect = null } - protected fun drawCubicBezier(dataSet: ILineDataSet) { + protected fun drawCubicBezier(dataSet: ILineDataSet<*>) { val phaseY = animator.phaseY val trans = dataProvider.getTransformer(dataSet.axisDependency) @@ -175,7 +177,7 @@ open class LineChartRenderer( // And in the `lastIndex`, add +1 val firstIndex = xBounds.min + 1 - var prevPrev: EntryFloat? + var prevPrev: BaseEntry? var prev = dataSet.getEntryForIndex(max((firstIndex - 2).toDouble(), 0.0).toInt()) var cur = dataSet.getEntryForIndex(max((firstIndex - 1).toDouble(), 0.0).toInt()) var next = cur @@ -227,7 +229,7 @@ open class LineChartRenderer( paintRender.pathEffect = null } - protected fun drawCubicFill(canvas: Canvas, dataSet: ILineDataSet, spline: Path, trans: Transformer, bounds: XBounds) { + protected fun drawCubicFill(canvas: Canvas, dataSet: ILineDataSet<*>, spline: Path, trans: Transformer, bounds: XBounds) { val fillMin = dataSet.fillFormatter!!.getFillLinePosition(dataSet, dataProvider) dataSet.getEntryForIndex(bounds.min + bounds.range)?.let { @@ -251,7 +253,7 @@ open class LineChartRenderer( /** * Draws a normal line. */ - protected fun drawLinear(c: Canvas, dataSet: ILineDataSet) { + protected fun drawLinear(c: Canvas, dataSet: ILineDataSet<*>) { val entryCount = dataSet.entryCount val pointsPerEntryPair = if (dataSet.isDrawSteppedEnabled) 4 else 2 @@ -286,7 +288,7 @@ open class LineChartRenderer( val max = xBounds.min + xBounds.range for (j in xBounds.min.. = dataSet.getEntryForIndex(j) ?: continue lineBuffer[0] = entryFloat.x lineBuffer[1] = entryFloat.y * phaseY @@ -346,8 +348,8 @@ open class LineChartRenderer( (max(((entryCount) * pointsPerEntryPair).toDouble(), pointsPerEntryPair.toDouble()) * 4).toInt() ) - var e1: EntryFloat? - var e2: EntryFloat? + var e1: BaseEntry? + var e2: BaseEntry? e1 = dataSet.getEntryForIndex(xBounds.min) @@ -393,7 +395,7 @@ open class LineChartRenderer( /** * Draws a filled linear path on the canvas. */ - protected fun drawLinearFill(canvas: Canvas, dataSet: ILineDataSet, trans: Transformer, bounds: XBounds) { + protected fun drawLinearFill(canvas: Canvas, dataSet: ILineDataSet<*>, trans: Transformer, bounds: XBounds) { val filled = mGenerateFilledPathBuffer val startingIndex = bounds.min @@ -435,7 +437,7 @@ open class LineChartRenderer( * @param endIndex The index from which to stop reading the dataset * @param outputPath The path object that will be assigned the chart data. */ - private fun generateFilledPath(dataSet: ILineDataSet, startIndex: Int, endIndex: Int, outputPath: Path) { + private fun generateFilledPath(dataSet: ILineDataSet<*>, startIndex: Int, endIndex: Int, outputPath: Path) { val fillMin = dataSet.fillFormatter!!.getFillLinePosition(dataSet, dataProvider) val phaseY = animator.phaseY val isDrawSteppedEnabled = dataSet.lineMode == LineDataSet.Mode.STEPPED @@ -448,7 +450,7 @@ open class LineChartRenderer( outputPath.lineTo(entry.x, entry.y * phaseY) // create a new path - var currentEntryFloat: EntryFloat? = null + var currentEntryFloat: BaseEntry? = null var previousEntry = entry for (x in startIndex + 1..endIndex) { currentEntryFloat = dataSet.getEntryForIndex(x) @@ -650,7 +652,8 @@ open class LineChartRenderer( set.getEntryForXValue(high.x, high.y)?.let { entry -> - if (!isInBoundsX(entry, set)) + @Suppress("UNCHECKED_CAST") + if (!isInBoundsX(entry, set as IBarLineScatterCandleBubbleDataSet>)) continue val pix = dataProvider.getTransformer(set.axisDependency)!!.getPixelForValues( @@ -686,7 +689,7 @@ open class LineChartRenderer( /** * Sets up the cache, returns true if a change of cache was required. */ - fun init(set: ILineDataSet): Boolean { + fun init(set: ILineDataSet<*>): Boolean { val size = set.circleColorCount var changeRequired = false @@ -708,7 +711,7 @@ open class LineChartRenderer( * @param drawCircleHole * @param drawTransparentCircleHole */ - fun fill(set: ILineDataSet, drawCircleHole: Boolean, drawTransparentCircleHole: Boolean) { + fun fill(set: ILineDataSet<*>, drawCircleHole: Boolean, drawTransparentCircleHole: Boolean) { val colorCount = set.circleColorCount val circleRadius = set.circleRadius val circleHoleRadius = set.circleHoleRadius 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 0836d3857..e26be48d7 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/Transformer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/Transformer.kt @@ -142,7 +142,7 @@ open class Transformer(protected var viewPortHandler: ViewPortHandler) { * y values transformed with all matrices for the LINECHART. */ fun generateTransformedValuesLine( - data: ILineDataSet, + data: ILineDataSet<*>, phaseX: Float, phaseY: Float, min: Int, max: Int ): FloatArray { From ae2debc405aa6e03d8a4a397e70f3e9c8db438d1 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 18:45:19 +0200 Subject: [PATCH 19/36] BarEntry with high precision --- .../appdev/charting/data/BarEntryDouble.kt | 146 ++++++++++++++++++ .../appdev/charting/highlight/RangeDouble.kt | 12 ++ 2 files changed, 158 insertions(+) create mode 100644 chartLib/src/main/kotlin/info/appdev/charting/data/BarEntryDouble.kt create mode 100644 chartLib/src/main/kotlin/info/appdev/charting/highlight/RangeDouble.kt diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntryDouble.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntryDouble.kt new file mode 100644 index 000000000..6560d5d26 --- /dev/null +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/BarEntryDouble.kt @@ -0,0 +1,146 @@ +package info.appdev.charting.data + +import android.graphics.drawable.Drawable +import info.appdev.charting.highlight.RangeDouble +import kotlin.math.abs + +/** + * High-precision bar entry that stores x and y as Double, extending [BarEntryFloat] + * so it works seamlessly in the existing bar chart rendering pipeline. + * Use [xDouble] and [yDouble] for full-precision access. + * For stacked bars use [yValsDouble] and [rangesDouble]. + */ +open class BarEntryDouble : BarEntryFloat { + + var xDouble: Double = 0.0 + var yDouble: Double = 0.0 + + override var x: Float + get() = xDouble.toFloat() + set(value) { xDouble = value.toDouble() } + + /** + * Returns the double-precision stacked values, or null for simple bars. + */ + var yValsDouble: DoubleArray? = null + private set + + /** + * Double-precision ranges for stacked bars. + */ + var rangesDouble: Array = arrayOf() + private set + + var negativeDoubleSum: Double = 0.0 + private set + + var positiveDoubleSum: Double = 0.0 + private set + + // ── Simple bar constructors ────────────────────────────────────────────── + + constructor(x: Double, y: Double) : super(x.toFloat(), y.toFloat()) { + xDouble = x; yDouble = y + } + + constructor(x: Double, y: Double, data: Any?) : super(x.toFloat(), y.toFloat(), data) { + xDouble = x; yDouble = y + } + + constructor(x: Double, y: Double, icon: Drawable?) : super(x.toFloat(), y.toFloat(), icon) { + xDouble = x; yDouble = y + } + + constructor(x: Double, y: Double, icon: Drawable?, data: Any?) : super(x.toFloat(), y.toFloat(), icon, data) { + xDouble = x; yDouble = y + } + + // ── Stacked bar constructors ───────────────────────────────────────────── + + constructor(x: Double, vals: DoubleArray?) : super(x.toFloat(), calcDoubleSum(vals).toFloat()) { + xDouble = x + yDouble = calcDoubleSum(vals) + yValsDouble = vals + calcDoublePosNegSum() + calcDoubleRanges() + } + + constructor(x: Double, vals: DoubleArray?, data: Any?) : super(x.toFloat(), calcDoubleSum(vals).toFloat(), data) { + xDouble = x + yDouble = calcDoubleSum(vals) + yValsDouble = vals + calcDoublePosNegSum() + calcDoubleRanges() + } + + constructor(x: Double, vals: DoubleArray?, icon: Drawable?) : super(x.toFloat(), calcDoubleSum(vals).toFloat(), icon) { + xDouble = x + yDouble = calcDoubleSum(vals) + yValsDouble = vals + calcDoublePosNegSum() + calcDoubleRanges() + } + + // ── y override (always reflects yDouble) ──────────────────────────────── + + override var y: Float + get() = yDouble.toFloat() + set(value) { yDouble = value.toDouble() } + + // ── Stack helpers ──────────────────────────────────────────────────────── + + val isStackedDouble: Boolean get() = yValsDouble != null + + fun setValsDouble(vals: DoubleArray?) { + yDouble = calcDoubleSum(vals) + yValsDouble = vals + calcDoublePosNegSum() + calcDoubleRanges() + } + + fun getSumBelowDouble(stackIndex: Int): Double { + val vals = yValsDouble ?: return 0.0 + var remainder = 0.0 + var index = vals.size - 1 + while (index > stackIndex && index >= 0) { + remainder += vals[index] + index-- + } + return remainder + } + + private fun calcDoublePosNegSum() { + val vals = yValsDouble + if (vals == null) { negativeDoubleSum = 0.0; positiveDoubleSum = 0.0; return } + var sumNeg = 0.0; var sumPos = 0.0 + for (v in vals) { if (v <= 0.0) sumNeg += abs(v) else sumPos += v } + negativeDoubleSum = sumNeg; positiveDoubleSum = sumPos + } + + private fun calcDoubleRanges() { + val values = yValsDouble + if (values == null || values.isEmpty()) return + + rangesDouble = Array(values.size) { RangeDouble(0.0, 0.0) } + var negRemain = -negativeDoubleSum + var posRemain = 0.0 + + for (i in values.indices) { + val value = values[i] + if (value < 0) { + rangesDouble[i] = RangeDouble(negRemain, negRemain - value) + negRemain -= value + } else { + rangesDouble[i] = RangeDouble(posRemain, posRemain + value) + posRemain += value + } + } + } + + override fun toString(): String = "BarEntryDouble xDouble=$xDouble yDouble=$yDouble yValsDouble=${yValsDouble?.contentToString()}" + + companion object { + private fun calcDoubleSum(vals: DoubleArray?): Double = vals?.sum() ?: 0.0 + } +} + diff --git a/chartLib/src/main/kotlin/info/appdev/charting/highlight/RangeDouble.kt b/chartLib/src/main/kotlin/info/appdev/charting/highlight/RangeDouble.kt new file mode 100644 index 000000000..cfaae498d --- /dev/null +++ b/chartLib/src/main/kotlin/info/appdev/charting/highlight/RangeDouble.kt @@ -0,0 +1,12 @@ +package info.appdev.charting.highlight + +/** + * Double-precision equivalent of [Range] for use with [info.appdev.charting.data.BarEntryDouble] + * stacked entries. e.g. stack values are -10.0, 5.0, 20.0 -> ranges are (-10 to 0, 0 to 5, 5 to 25). + */ +class RangeDouble(var from: Double, var to: Double) { + fun contains(value: Double): Boolean = value > from && value <= to + fun isLarger(value: Double): Boolean = value > to + fun isSmaller(value: Double): Boolean = value < from +} + From 95f37766ce3ba53dc7f0650b3b369ef100a5dbd6 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 18:45:57 +0200 Subject: [PATCH 20/36] HorizontalBarChartActivity with high precision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The problem is invariant generics. BarDataSet is DataSet, so entries expects MutableList. Even though BarEntryDouble : BarEntryFloat, MutableList is not a MutableList because MutableList is invariant (it's both a producer and consumer). MutableList is invariant in T because it both reads (get() → T) and writes (add(T)). Even though BarEntryDouble : BarEntryFloat, the compiler must reject the assignment to prevent this kind of unsound write: val list: MutableList = values // if allowed... list.add(BarEntryFloat(1f, 2f)) // would corrupt the original BarEntryDouble list! The @Suppress("UNCHECKED_CAST") cast is safe here because: * JVM erases generics at runtime — both are just MutableList on the heap * BarDataSet only reads entries from the list as BarEntryFloat, and BarEntryDouble IS a BarEntryFloat * Nothing writes a plain BarEntryFloat back into the list through BarDataSet --- .../HorizontalBarChartActivity.kt | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt index c87b4f068..9340c22ee 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt @@ -19,6 +19,7 @@ import info.appdev.charting.components.Legend import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.data.BarData import info.appdev.charting.data.BarDataSet +import info.appdev.charting.data.BarEntryDouble import info.appdev.charting.data.BarEntryFloat import info.appdev.charting.data.EntryFloat import info.appdev.charting.highlight.Highlight @@ -96,14 +97,14 @@ class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartV private fun setData(count: Int, range: Float) { val barWidth = 9f val spaceForBar = 10f - val values = ArrayList() + val values = ArrayList() val sampleValues = getValues(100) for (i in 0.. 0 ) { set1 = binding.chart1.barData!!.getDataSetByIndex(0) as BarDataSet - set1.entries = values + @Suppress("UNCHECKED_CAST") + set1.entries = values as MutableList binding.chart1.barData?.notifyDataChanged() binding.chart1.notifyDataSetChanged() } else { - set1 = BarDataSet(values, "DataSet 1") + @Suppress("UNCHECKED_CAST") + set1 = BarDataSet(values as MutableList, "DataSet 1") set1.isDrawIcons = false @@ -230,8 +237,9 @@ class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartV private val mOnValueSelectedRectF = RectF() override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) { + val barEntry = entryFloat as BarEntryDouble val bounds = mOnValueSelectedRectF - binding.chart1.getBarBounds(entryFloat as BarEntryFloat, bounds) + binding.chart1.getBarBounds(barEntry, bounds) val position = binding.chart1.getPosition( entryFloat, binding.chart1.barData!!.getDataSetByIndex(highlight.dataSetIndex)?.axisDependency From c3470d68654f5daeb3c3e5f43857ae3f43074dcd Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 29 Mar 2026 13:50:42 +0200 Subject: [PATCH 21/36] TimeLineActivity with high precision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The problem is invariant generics. LineDataSet is LineDataSet, so entries expects MutableList. Even though EntryDouble : EntryFloat, MutableList is not a MutableList because MutableList is invariant (it's both a producer and consumer). MutableList is invariant in T because it both reads (get() → T) and writes (add(T)). Even though EntryDouble : EntryFloat, the compiler must reject the assignment to prevent this kind of unsound write: val list: MutableList = values // if allowed... list.add(EntryFloat(1f, 2f)) // would corrupt the original EntryDouble list! The @Suppress("UNCHECKED_CAST") cast is safe here because: * JVM erases generics at runtime — both are just MutableList on the heap * LineDataSet only reads entries from the list as EntryFloat, and EntryDouble IS a EntryFloat * Nothing writes a plain EntryFloat back into the list through DataSet --- .../appdev/chartexample/TimeLineActivity.kt | 42 +++++++++++------- ...TimeLineActivity-Timeline-1SampleClick.png | Bin 19818 -> 19373 bytes 2 files changed, 25 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt index 096c308e7..9e29813ef 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt @@ -17,6 +17,7 @@ import info.appdev.charting.components.Description import info.appdev.charting.components.Legend.LegendForm import info.appdev.charting.components.XAxis.XAxisPosition import info.appdev.charting.components.YAxis +import info.appdev.charting.data.EntryDouble import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet @@ -96,23 +97,23 @@ class TimeLineActivity : DemoBase() { val sampleEntries = if (sinus) generateSineWaves(3, 30).mapIndexed { index, data -> - val valueY = (data.toFloat() * range) + 50 - EntryFloat(timeOffset + index.toFloat() * 1000, valueY) + val valueY = (data * range) + 50 + EntryDouble(timeOffset + index * 1000.0, valueY) } else { - var previousEntryFloat: EntryFloat? = null + var previousEntryDouble: EntryDouble? = null getSawtoothValues(14).mapIndexed { index, data -> - val valueY = data.toFloat() * 20 - val entryFloat = previousEntryFloat?.let { + val valueY = data * 20 + val entryFloat = previousEntryDouble?.let { // nay third value is 0, so we add here more than 1 second, otherwise we have a one-second entry if (index % 3 == 0) { - EntryFloat(it.x + 3000, valueY) + EntryDouble(it.xDouble + 3000.0, valueY) } else - EntryFloat(it.x + 1000, valueY) + EntryDouble(it.xDouble + 1000.0, valueY) } ?: run { - EntryFloat(timeOffset + index.toFloat() * 1000, valueY) + EntryDouble(timeOffset + index.toFloat() * 1000.0, valueY) } - previousEntryFloat = entryFloat + previousEntryDouble = entryFloat // Now you can use 'prev' which holds the previous Entry entryFloat } @@ -120,14 +121,19 @@ class TimeLineActivity : DemoBase() { val simpleDateFormat = SimpleDateFormat("HH:mm:ss.sss", Locale.getDefault()) sampleEntries.forEach { entry -> - val entryText = "Entry: x=${simpleDateFormat.format(entry.x)} x=${entry.x}, y=${entry.y}" + val entryText = "Entry: x=${simpleDateFormat.format(entry.xDouble.toLong())} xDouble=${entry.xDouble}, y=${entry.y}" Timber.d(entryText) } - val set1: LineDataSet + val set1: LineDataSet + /*The @Suppress("UNCHECKED_CAST") cast is safe here because: + * JVM erases generics at runtime — both are just MutableList on the heap + * LineDataSet only reads entries from the list as EntryFloat, and EntryDouble IS a EntryFloat + * Nothing writes a plain EntryFloat back into the list through LineDataSet*/ if (binding.chart1.lineData.dataSetCount > 0) { - set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet + @Suppress("UNCHECKED_CAST") + set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet set1.entries = sampleEntries.toMutableList() if (sinus) set1.lineMode = LineDataSet.Mode.LINEAR @@ -153,15 +159,17 @@ class TimeLineActivity : DemoBase() { set1.highLightColor = Color.rgb(244, 117, 117) set1.isDrawCircleHoleEnabled = false set1.fillFormatter = object : IFillFormatter { - override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float { + override fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float { // change the return value here to better understand the effect // return 0; return binding.chart1.axisLeft.axisMinimum } } - val dataSets = ArrayList() - dataSets.add(set1) // add the data sets + @Suppress("UNCHECKED_CAST") + val dataSets = ArrayList>() + @Suppress("UNCHECKED_CAST") + dataSets.add(set1 as ILineDataSet) // add the data sets // create a data object with the data sets val data = LineData(dataSets) @@ -209,9 +217,9 @@ class TimeLineActivity : DemoBase() { val first = this[0] val second = this[1] // needed to get time diff val last = this[size - 1] - val timeDiff = (second as EntryFloat).x - (first as EntryFloat).x + val timeDiff = (second as EntryDouble).xDouble - (first as EntryDouble).xDouble removeAt(0) - first.x = (last as EntryFloat).x + timeDiff + (first as EntryDouble).xDouble = (last as EntryDouble).xDouble + timeDiff add(first) } diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeLineActivity-Timeline-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeLineActivity-Timeline-1SampleClick.png index b291c3f2fac90168ce95725dd2cac4cb623c6322..95aa7ac05e3290fd09a4811b45ffbea7fbb524c0 100644 GIT binary patch literal 19373 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

Y3X^YngOf2eB9gnkPo&_tiIe2XzE&wvd5v6KUP z^cp60`qc||O~$>>|LIx3e^H}~lz6vA^r~YRpSW&N$-p4P53KrAxt-}6u3cB1a>GAB zn>x17?-+RU7jA~88gWx8^7>Wof4tL7^Wnhwa$RQ|(C&>UJ!S!@tRYV3Xe!%sQt_Q- zbNL2mE>45T#>Az-=egrk{W9+MzY_*Nlg)G8V22R@eNa2I#`o3IX}<0Kt6z04Q>NiK zZ4Rb|)lls+{qF7A=4%z2rp`H5ZEsmvZGS2m1m=6H+L9=o(G{QNY&87HuPx<6ZO%aX zsx?13KPr^K{K4rDO}Wl2Z*m{_F4iIg4a}+X9rk@%~l80Yj*q!P{&GPMbD0$#?fFo>7w4scF^z$nH?3h0~DLF20sIBu_tjh$U?QBef2liv6o zpvOON7FPVk->A6@N{C&S;-qWbe||N5zFtz%1k5QD8_SB9tv>^R3dgY%m_gKF~F&~0)r!noOfsJY$%6Ou2JEBxR5 za%_$PCpX;9umtD81joiC|MF691G*Jxh8inN7Mqm+Z(D#Z#T>Kf&;Q?pLZRTHQ{BJM z^FJTtubTiW-gF{2u_XKW{=a8df6hCr&VS~tC07Ohyb9l66dfH6N*&kZs?Y8PmbFgO z>vsURnB3)+Hrui8^{(@Fzt{X;n_qkS?$+$^lvH4uj9< zc2{$~bbH{p#x&2vzy6oS>fhrQ*DLw_vnFG{X|(7bz5AE#X#U=>*AAR6yL<5cZ-KCz z&8i1CoO<`>O$jg)x^3UP_iin4v7*!7>#e=>i?8!OpJxPIU^D&9eEa+LGSB|90}DvK z*se`)CIMSDn}A0dfeJ~`!9`m#FEi;&f7}1wc}8kHaP^%HFxO3-Vhe2D?zi5NcXwC7 z`_jb6&t(qnT<){R%vpj{`hD$%ix&?9o4kxWfCr~k05`q-xY%EJgqh#wz|8b{Gf!;- z_9>)a{x`qzGtxWz=1gEs_iUEnHC-`a@#O++0Y5(0`xsctCqF$kHKz1x=)yYO+4+^J=K0oHs*ZT zCVC5aECFzB=%KpXS}Wf?xcMv6JNf2H&jux>sa|I#7Ib(lCFcMsGP_{E{xkAb=2flQ T+#Lu!e~ZD>)z4*}Q$iB}XRY4v literal 19818 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 Date: Mon, 30 Mar 2026 16:09:51 +0000 Subject: [PATCH 22/36] Bump gradle/actions from 5 to 6 Bumps [gradle/actions](https://github.com/gradle/actions) from 5 to 6. - [Release notes](https://github.com/gradle/actions/releases) - [Commits](https://github.com/gradle/actions/compare/v5...v6) --- updated-dependencies: - dependency-name: gradle/actions dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/pullrequest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 350a77f3c..49d10d9a9 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -44,7 +44,7 @@ jobs: with: distribution: "adopt" java-version: ${{ matrix.java_version }} - - uses: gradle/actions/wrapper-validation@v5 + - uses: gradle/actions/wrapper-validation@v6 - name: Install Android SDK uses: hannesa2/action-android/install-sdk@0.1.16.7 - name: Run instrumentation tests From 68fbffc6cede7227edad607a48e360e0a1375b31 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sat, 28 Mar 2026 10:38:22 +0100 Subject: [PATCH 23/36] Bump Android action --- .github/workflows/Android-CI-release.yml | 2 +- .github/workflows/pullrequest.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/Android-CI-release.yml b/.github/workflows/Android-CI-release.yml index 2eac40077..0c0b5c712 100644 --- a/.github/workflows/Android-CI-release.yml +++ b/.github/workflows/Android-CI-release.yml @@ -27,7 +27,7 @@ jobs: abbrev: false commit-ish: HEAD - name: Install Android SDK - uses: hannesa2/action-android/install-sdk@0.1.16.7 + uses: hannesa2/action-android/install-sdk@0.17.0 - name: Build project run: ./gradlew assembleRelease env: diff --git a/.github/workflows/pullrequest.yml b/.github/workflows/pullrequest.yml index 49d10d9a9..0b3456ceb 100644 --- a/.github/workflows/pullrequest.yml +++ b/.github/workflows/pullrequest.yml @@ -46,9 +46,9 @@ jobs: java-version: ${{ matrix.java_version }} - uses: gradle/actions/wrapper-validation@v6 - name: Install Android SDK - uses: hannesa2/action-android/install-sdk@0.1.16.7 + uses: hannesa2/action-android/install-sdk@0.17.0 - name: Run instrumentation tests - uses: hannesa2/action-android/emulator-run-cmd@0.1.16.7 + uses: hannesa2/action-android/emulator-run-cmd@0.17.0 with: cmd: ./gradlew cAT --continue api: ${{ matrix.api }} @@ -112,7 +112,7 @@ jobs: distribution: "adopt" java-version: ${{ matrix.java_version }} - name: Install Android SDK - uses: hannesa2/action-android/install-sdk@0.1.16.7 + uses: hannesa2/action-android/install-sdk@0.17.0 - name: Run tests run: ./gradlew test - name: Test jitpack publish command From 737bc8c0cde9ea17771a414ba228dcb5c0d00782 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Fri, 3 Apr 2026 07:37:26 +0200 Subject: [PATCH 24/36] Remove unnecessary non-null assertion --- .../info/appdev/chartexample/RealtimeLineChartActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/RealtimeLineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/RealtimeLineChartActivity.kt index 1e9b361f3..6012129bc 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/RealtimeLineChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/RealtimeLineChartActivity.kt @@ -92,7 +92,7 @@ class RealtimeLineChartActivity : DemoBase(), OnChartValueSelectedListener { data.addDataSet(set) } - val cycleValue = (set!!.entryCount % 100.0).toInt() + val cycleValue = (set.entryCount % 100.0).toInt() data.addEntry(EntryFloat(set.entryCount.toFloat(), (sampleValues[cycleValue]!!.toFloat() * 40) + 30f), 0) data.notifyDataChanged() From 70e3d0b78d462be0819797fe84203f3cff6442a1 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Fri, 3 Apr 2026 08:20:33 +0200 Subject: [PATCH 25/36] More time value formatter --- .../formatter/TimeRangeValueFormatter.kt | 38 +++++++++++++++++++ .../formatter/UnixTimeValueFormatter.kt | 38 +++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 app/src/main/kotlin/info/appdev/chartexample/formatter/TimeRangeValueFormatter.kt create mode 100644 app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt diff --git a/app/src/main/kotlin/info/appdev/chartexample/formatter/TimeRangeValueFormatter.kt b/app/src/main/kotlin/info/appdev/chartexample/formatter/TimeRangeValueFormatter.kt new file mode 100644 index 000000000..247a4df0f --- /dev/null +++ b/app/src/main/kotlin/info/appdev/chartexample/formatter/TimeRangeValueFormatter.kt @@ -0,0 +1,38 @@ +package info.appdev.chartexample.formatter + +import info.appdev.charting.data.BarEntryDouble +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat +import info.appdev.charting.formatter.IValueFormatter +import info.appdev.charting.utils.ViewPortHandler +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.Locale + +class TimeRangeValueFormatter(val format: String = "yyyy-MM-dd'T'HH:mm:ss'Z'") : IValueFormatter { + + val simpleDateFormat = SimpleDateFormat(format, Locale.getDefault()) + + // For bar/line values + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + return when (entryFloat) { + is BarEntryDouble if entryFloat.yValsDouble != null -> { + // High-precision double path + val vals = entryFloat.yValsDouble!! + val start = vals.first().toLong() + val end = vals.last().toLong() + "$start - $end" + } + + is BarEntryFloat if entryFloat.yVals != null -> { + // Float path + val vals = entryFloat.yVals!! + val start = vals.first().toLong() + val end = vals.last().toLong() + "$start - $end" + } + + else -> simpleDateFormat.format(value.toLong()) + } + } +} diff --git a/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt new file mode 100644 index 000000000..dd7d48530 --- /dev/null +++ b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt @@ -0,0 +1,38 @@ +package info.appdev.chartexample.formatter + +import info.appdev.charting.data.BarEntryDouble +import info.appdev.charting.data.BarEntryFloat +import info.appdev.charting.data.EntryFloat +import info.appdev.charting.formatter.IValueFormatter +import info.appdev.charting.utils.ViewPortHandler +import timber.log.Timber +import java.text.SimpleDateFormat +import java.util.Locale + +class UnixTimeValueFormatter(val format: String = "yyyy-MM-dd'T'HH:mm:ss'Z'") : IValueFormatter { + + val simpleDateFormat = SimpleDateFormat(format, Locale.getDefault()) + + // For bar/line values + override fun getFormattedValue(value: Float, entryFloat: EntryFloat?, dataSetIndex: Int, viewPortHandler: ViewPortHandler?): String { + return when (entryFloat) { + is BarEntryDouble if entryFloat.yValsDouble != null -> { + // High-precision double path + val vals = entryFloat.yValsDouble!! + val start = simpleDateFormat.format(vals.first().toLong()) + val end = simpleDateFormat.format(vals.last().toLong()) + "$start - $end" + } + + is BarEntryFloat if entryFloat.yVals != null -> { + // Float path + val vals = entryFloat.yVals!! + val start = simpleDateFormat.format(vals.first().toLong()) + val end = simpleDateFormat.format(vals.last().toLong()) + "$start - $end" + } + + else -> simpleDateFormat.format(value.toLong()) + } + } +} From 7462b9a43557486510d9ef2713154b91ee5f4198 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Fri, 3 Apr 2026 08:34:48 +0200 Subject: [PATCH 26/36] Rename color activity --- app/src/main/AndroidManifest.xml | 2 +- ...tivityColored.kt => LineChartColoredActivity.kt} | 2 +- .../chartexample/notimportant/MainActivity.kt | 4 ++-- ...eChartColoredActivity-Colorful-1SampleClick.png} | Bin ...t-6-LineChartColoredActivity-Colorful-click.png} | Bin ...LineChartColoredActivity-Colorful-click2020.png} | Bin ...LineChartColoredActivity-Colorful-click7070.png} | Bin 7 files changed, 4 insertions(+), 4 deletions(-) rename app/src/main/kotlin/info/appdev/chartexample/{LineChartActivityColored.kt => LineChartColoredActivity.kt} (98%) rename screenshotsToCompare9/{StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-1SampleClick.png => StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-1SampleClick.png} (100%) rename screenshotsToCompare9/{StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click.png => StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click.png} (100%) rename screenshotsToCompare9/{StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click2020.png => StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click2020.png} (100%) rename screenshotsToCompare9/{StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click7070.png => StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click7070.png} (100%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 648410d1e..104d62c8d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -46,7 +46,7 @@ - + diff --git a/app/src/main/kotlin/info/appdev/chartexample/LineChartActivityColored.kt b/app/src/main/kotlin/info/appdev/chartexample/LineChartColoredActivity.kt similarity index 98% rename from app/src/main/kotlin/info/appdev/chartexample/LineChartActivityColored.kt rename to app/src/main/kotlin/info/appdev/chartexample/LineChartColoredActivity.kt index aae6647b5..71b02cab9 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/LineChartActivityColored.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/LineChartColoredActivity.kt @@ -15,7 +15,7 @@ import info.appdev.charting.data.EntryFloat import info.appdev.charting.data.LineData import info.appdev.charting.data.LineDataSet -class LineChartActivityColored : DemoBase() { +class LineChartColoredActivity : DemoBase() { private val charts = mutableListOf() private lateinit var binding: ActivityColoredLinesBinding diff --git a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt index 35572d42a..9c50a3bf2 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt @@ -57,7 +57,7 @@ import info.appdev.chartexample.HalfPieChartActivity import info.appdev.chartexample.HorizontalBarChartActivity import info.appdev.chartexample.InvertedLineChartActivity import info.appdev.chartexample.LineChartActivity -import info.appdev.chartexample.LineChartActivityColored +import info.appdev.chartexample.LineChartColoredActivity import info.appdev.chartexample.LineChartDualAxisActivity import info.appdev.chartexample.LineChartTimeActivity import info.appdev.chartexample.ListViewBarChartActivity @@ -165,7 +165,7 @@ class MainActivity : ComponentActivity() { add(ContentItem("Dual Axis", "Line chart with dual y-axes.", LineChartDualAxisActivity::class.java)) add(ContentItem("Inverted Axis", "Inverted y-axis.", InvertedLineChartActivity::class.java)) add(ContentItem("Cubic", "Line chart with a cubic line shape.", CubicLineChartActivity::class.java)) - add(ContentItem("Colorful", "Colorful line chart.", LineChartActivityColored::class.java)) + add(ContentItem("Colorful", "Colorful line chart.", LineChartColoredActivity::class.java)) add(ContentItem("Performance", "Render 30.000 data points smoothly.", PerformanceLineChart::class.java)) add(ContentItem("Filled", "Colored area between two lines.", FilledLineActivity::class.java)) diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-1SampleClick.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-1SampleClick.png rename to screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-1SampleClick.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click.png b/screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click.png rename to screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click2020.png b/screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click2020.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click2020.png rename to screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click2020.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click7070.png b/screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click7070.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartActivityColored-Colorful-click7070.png rename to screenshotsToCompare9/StartTest_smokeTestStart-6-LineChartColoredActivity-Colorful-click7070.png From 08584f0f2454c992847f02501a62a10bdd7ad90c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 Apr 2026 14:43:19 +0000 Subject: [PATCH 27/36] Bump androidx.annotation:annotation from 1.9.1 to 1.10.0 Bumps androidx.annotation:annotation from 1.9.1 to 1.10.0. --- updated-dependencies: - dependency-name: androidx.annotation:annotation dependency-version: 1.10.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- chartLib/build.gradle.kts | 2 +- chartLibCompose/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chartLib/build.gradle.kts b/chartLib/build.gradle.kts index 68b3f1923..a855574e6 100644 --- a/chartLib/build.gradle.kts +++ b/chartLib/build.gradle.kts @@ -43,7 +43,7 @@ android { } dependencies { - implementation("androidx.annotation:annotation:1.9.1") + implementation("androidx.annotation:annotation:1.10.0") implementation("androidx.core:core:1.18.0") implementation("androidx.activity:activity-ktx:1.13.0") implementation("com.github.AppDevNext.Logcat:LogcatCoreLib:3.4") diff --git a/chartLibCompose/build.gradle.kts b/chartLibCompose/build.gradle.kts index 68f44b787..1170ace2e 100644 --- a/chartLibCompose/build.gradle.kts +++ b/chartLibCompose/build.gradle.kts @@ -45,7 +45,7 @@ android { } dependencies { - implementation("androidx.annotation:annotation:1.9.1") + implementation("androidx.annotation:annotation:1.10.0") implementation("androidx.core:core:1.18.0") implementation("androidx.activity:activity-ktx:1.13.0") implementation("com.github.AppDevNext.Logcat:LogcatCoreLib:3.4") From 1b956ab33b60199d36fab38d4179b7d0299c3acf Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 12 Apr 2026 10:06:04 +0200 Subject: [PATCH 28/36] Cosmetic --- .../renderer/HorizontalBarChartRenderer.kt | 24 ++++++++++++------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/HorizontalBarChartRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/HorizontalBarChartRenderer.kt index 67fa0d74c..870796ffb 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/HorizontalBarChartRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/HorizontalBarChartRenderer.kt @@ -140,24 +140,30 @@ open class HorizontalBarChartRenderer( if (isCustomFill) { dataSet.getFill(pos)?.fillRect( canvas, paintRender, + left = buffer.buffer[j], + top = buffer.buffer[j + 1], + right = buffer.buffer[j + 2], + bottom = buffer.buffer[j + 3], + gradientDirection = if (isInverted) Fill.Direction.LEFT else Fill.Direction.RIGHT, + mRoundedBarRadius = 0f + ) + } else { + canvas.drawRect( buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], buffer.buffer[j + 3], - if (isInverted) Fill.Direction.LEFT else Fill.Direction.RIGHT, - 0f - ) - } else { - canvas.drawRect( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3], paintRender + paintRender ) } if (drawBorder) { canvas.drawRect( - buffer.buffer[j], buffer.buffer[j + 1], buffer.buffer[j + 2], - buffer.buffer[j + 3], barBorderPaint + buffer.buffer[j], + buffer.buffer[j + 1], + buffer.buffer[j + 2], + buffer.buffer[j + 3], + barBorderPaint ) } j += 4 From bb454c1c68e02eb4bdae32f2858aeca83d36b2fc Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 12 Apr 2026 10:34:42 +0200 Subject: [PATCH 29/36] Java compability in buildSrc --- buildSrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc b/buildSrc index abb98b144..17e584459 160000 --- a/buildSrc +++ b/buildSrc @@ -1 +1 @@ -Subproject commit abb98b1449cfa86f169cc6d6eb2f4ed9769545df +Subproject commit 17e584459c97966f8fdcb57dfac277b35476d4bf From 096957b78fcd075c8895afdf2a14bfa0057ca7c3 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 12 Apr 2026 10:33:27 +0200 Subject: [PATCH 30/36] Strip native libs Avoid: Unable to strip the following libraries, packaging them as they are: libandroidx.graphics.path.so --- app/build.gradle.kts | 7 +++++++ chartLib/build.gradle.kts | 7 +++++++ chartLibCompose/build.gradle.kts | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cb249759b..c5864c808 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -46,6 +46,13 @@ android { testOptions { animationsDisabled = true } + packaging { + jniLibs { + // androidx.graphics:graphics-path ships a .so that llvm-strip cannot process; + // keep it as-is to silence "Unable to strip" warnings. + keepDebugSymbols += "**/libandroidx.graphics.path.so" + } + } } dependencies { diff --git a/chartLib/build.gradle.kts b/chartLib/build.gradle.kts index a855574e6..d8464bb59 100644 --- a/chartLib/build.gradle.kts +++ b/chartLib/build.gradle.kts @@ -40,6 +40,13 @@ android { testOptions { unitTests.isReturnDefaultValues = true // this prevents "not mocked" error } + packaging { + jniLibs { + // androidx.graphics:graphics-path ships a .so that llvm-strip cannot process; + // keep it as-is to silence "Unable to strip" warnings. + keepDebugSymbols += "**/libandroidx.graphics.path.so" + } + } } dependencies { diff --git a/chartLibCompose/build.gradle.kts b/chartLibCompose/build.gradle.kts index 1170ace2e..4d7692a6e 100644 --- a/chartLibCompose/build.gradle.kts +++ b/chartLibCompose/build.gradle.kts @@ -42,6 +42,13 @@ android { testOptions { unitTests.isReturnDefaultValues = true // this prevents "not mocked" error } + packaging { + jniLibs { + // androidx.graphics:graphics-path ships a .so that llvm-strip cannot process; + // keep it as-is to silence "Unable to strip" warnings. + keepDebugSymbols += "**/libandroidx.graphics.path.so" + } + } } dependencies { From 95a5773e193245289909605be162642e9ed5214e Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 12 Apr 2026 14:09:40 +0200 Subject: [PATCH 31/36] Cosmetic --- .../info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt index dd7d48530..2ab02341d 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeValueFormatter.kt @@ -5,7 +5,6 @@ import info.appdev.charting.data.BarEntryFloat import info.appdev.charting.data.EntryFloat import info.appdev.charting.formatter.IValueFormatter import info.appdev.charting.utils.ViewPortHandler -import timber.log.Timber import java.text.SimpleDateFormat import java.util.Locale From 2b4288ee187e76f0abf730789abd8fcd8a893763 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sat, 28 Mar 2026 08:24:02 +0100 Subject: [PATCH 32/36] Grantt chart Inspired by https://github.com/PhilJay/MPAndroidChart/pull/5519 Fix Gantt chart label clipping, x-axis label overlap, and seekbar overlay - GanttChart.kt: dynamically compute chartLeft from max label width so long names like 'Development' are never clipped by the view edge - GanttChart.kt: compute grid-line count from available width / label width so x-axis time labels never overlap each other - GanttChart.kt: extract labelTextSize, gridLinesMin/Max as constants so measurement paint and drawing paint are always in sync - activity_time_interval_chart.xml: remove unused SeekBar widgets and TextViews that were rendering on top of the chart - TimeIntervalChartActivity.kt: remove OnSeekBarChangeListener interface and its unused override methods --- .../info/appdev/chartexample/StartTest.kt | 23 ++- app/src/main/AndroidManifest.xml | 1 + .../chartexample/TimeIntervalChartActivity.kt | 45 +++++ .../chartexample/notimportant/MainActivity.kt | 2 + .../layout/activity_time_interval_chart.xml | 60 ++++++ .../info/appdev/charting/charts/GanttChart.kt | 185 ++++++++++++++++++ .../appdev/charting/data/GanttChartData.kt | 90 +++++++++ .../info/appdev/charting/data/GanttTask.kt | 15 ++ 8 files changed, 419 insertions(+), 2 deletions(-) create mode 100644 app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt create mode 100644 app/src/main/res/layout/activity_time_interval_chart.xml create mode 100644 chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt create mode 100644 chartLib/src/main/kotlin/info/appdev/charting/data/GanttChartData.kt create mode 100644 chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt diff --git a/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt b/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt index 5bbdbaf80..2c127e96d 100644 --- a/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt +++ b/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt @@ -75,7 +75,10 @@ class StartTest { @After fun cleanUp() { - Intents.release() + // release() may have already been called by the last loop iteration + try { + Intents.release() + } catch (_: IllegalStateException) { /* not initialised, nothing to do */ } // Clean up test timber tree Timber.uprootAll() } @@ -249,12 +252,17 @@ class StartTest { if (!compose) doClickTest(index, contentClass, contentItem) - //Thread.sleep(100) Espresso.pressBack() // Wait for MainActivity to be visible again composeTestRule.waitForIdle() Thread.sleep(200) // Small delay for back navigation + + // Reset intent recording for next iteration; otherwise intents accumulate + // across the loop and Intents.intended() can no longer find a fresh + // unverified match for the current activity. + Intents.release() + Intents.init() } catch (e: Exception) { Timber.e("#$index/'${contentClass.simpleName}'->'$optionMenu' ${e.message}", e) onView(ViewMatchers.isRoot()) @@ -264,6 +272,16 @@ class StartTest { .replace(" ", "") ) }) + // Navigate back so subsequent iterations start from MainActivity + try { + Espresso.pressBack() + composeTestRule.waitForIdle() + } catch (_: Exception) { /* already on MainActivity */ } + // Reset intents so the next iteration starts clean + try { + Intents.release() + Intents.init() + } catch (_: Exception) { /* ignore if already released */ } } } } @@ -278,6 +296,7 @@ class StartTest { contentItem.clazz == HorizontalBarFullComposeActivity::class.java || contentItem.clazz == MultiLineComposeActivity::class.java || contentItem.clazz == GradientActivity::class.java || +// contentItem.clazz == TimeIntervalChartActivity::class.java || contentItem.clazz == TimeLineActivity::class.java ) { // These charts have less clickable area, so skip further clicks diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 104d62c8d..a8f2a241b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -60,6 +60,7 @@ + diff --git a/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt new file mode 100644 index 000000000..02a0b43b7 --- /dev/null +++ b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt @@ -0,0 +1,45 @@ +package info.appdev.chartexample + +import android.graphics.Color +import android.os.Bundle +import info.appdev.chartexample.databinding.ActivityTimeIntervalChartBinding +import info.appdev.chartexample.notimportant.DemoBase +import info.appdev.charting.data.EntryFloat +import info.appdev.charting.data.GanttChartData +import info.appdev.charting.data.GanttTask +import info.appdev.charting.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * Demo activity showing Gantt-style timeline visualization. + * Each horizontal bar represents a task with start time and duration. + */ +class TimeIntervalChartActivity : DemoBase(), OnChartValueSelectedListener { + + private lateinit var binding: ActivityTimeIntervalChartBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityTimeIntervalChartBinding.inflate(layoutInflater) + setContentView(binding.root) + + // Create Gantt chart data + val ganttData = GanttChartData() + + // Add sample project tasks + ganttData.addTask(GanttTask("Design", 0f, 50f, Color.rgb(255, 107, 107))) // Red: 0-50 + ganttData.addTask(GanttTask("Dev", 40f, 100f, Color.rgb(66, 165, 245))) // Blue: 40-140 + ganttData.addTask(GanttTask("Testing", 120f, 40f, Color.rgb(76, 175, 80))) // Green: 120-160 + ganttData.addTask(GanttTask("Launch", 150f, 20f, Color.rgb(255, 193, 7))) // Yellow: 150-170 + + // Set data and render + binding.chart1.setData(ganttData) + } + + override fun saveToGallery() = Unit + + override fun onValueSelected(entryFloat: EntryFloat, highlight: Highlight) = Unit + + override fun onNothingSelected() = Unit + +} diff --git a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt index 9c50a3bf2..ccae0d21c 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt @@ -75,6 +75,7 @@ import info.appdev.chartexample.ScrollViewActivity import info.appdev.chartexample.SpecificPositionsLineChartActivity import info.appdev.chartexample.StackedBarActivity import info.appdev.chartexample.StackedBarActivityNegative +import info.appdev.chartexample.TimeIntervalChartActivity import info.appdev.chartexample.TimeLineActivity import info.appdev.chartexample.compose.HorizontalBarComposeActivity import info.appdev.chartexample.compose.HorizontalBarFullComposeActivity @@ -219,6 +220,7 @@ class MainActivity : ComponentActivity() { add(ContentItem("Demonstrate and fix issues")) add(ContentItem("Gradient", "Show a gradient edge case", GradientActivity::class.java)) add(ContentItem("Timeline", "Show a time line with Unix timestamp", TimeLineActivity::class.java)) + add(ContentItem("Timeinterval", "Grantt chart", TimeIntervalChartActivity::class.java)) } } } diff --git a/app/src/main/res/layout/activity_time_interval_chart.xml b/app/src/main/res/layout/activity_time_interval_chart.xml new file mode 100644 index 000000000..b41d55b6c --- /dev/null +++ b/app/src/main/res/layout/activity_time_interval_chart.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt new file mode 100644 index 000000000..1afe35bcf --- /dev/null +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt @@ -0,0 +1,185 @@ +package info.appdev.charting.charts + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import info.appdev.charting.data.GanttChartData +import java.util.Locale + +class GanttChart : View { + private var data: GanttChartData? = null + private var taskPaint: Paint? = null + private var gridPaint: Paint? = null + private var textPaint: Paint? = null + + private var chartLeft = 0f + private var chartTop = 0f + private var chartRight = 0f + private var chartBottom = 0f + private val padding = 16f + private val labelTextSize = 24f + private val gridLinesMin = 2 + private val gridLinesMax = 10 + + constructor(context: Context?) : super(context) { + init() + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init() + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init() + } + + private fun init() { + taskPaint = Paint().apply { + isAntiAlias = true + } + gridPaint = Paint().apply { + color = -0x333334 + strokeWidth = 1f + } + textPaint = Paint().apply { + color = -0x99999a + textSize = 28f + isAntiAlias = true + } + } + + fun setData(data: GanttChartData?) { + this.data = data + invalidate() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (data == null || data!!.taskCount == 0) { + return + } + + calculateDimensions() + drawGrid(canvas) + drawTasks(canvas) + } + + private fun calculateDimensions() { + val labelMeasurePaint = Paint().apply { + textSize = labelTextSize + isAntiAlias = true + } + var maxLabelWidth = 0f + if (data != null) { + for (i in 0.. maxLabelWidth) maxLabelWidth = w + } + } + chartLeft = maxLabelWidth + padding * 3 + chartTop = padding + 30 + chartRight = width - padding + chartBottom = height - padding - 30 + } + + private val taskHeight: Float + // Dynamically calculate task height based on available space + get() { + if (data == null || data!!.taskCount == 0) { + return 40f + } + val availableHeight = chartBottom - chartTop + val taskCount = data!!.taskCount + // 50% of slot for bar, 50% for gap + return (availableHeight / taskCount) * 0.5f + } + + private val taskSpacing: Float + get() { + if (data == null || data!!.taskCount == 0) { + return 12f + } + val availableHeight = chartBottom - chartTop + val taskCount = data!!.taskCount + return (availableHeight / taskCount) * 0.5f + } + + private fun drawGrid(canvas: Canvas) { + val minTime = data!!.minTime + val maxTime = data!!.maxTime + var timeRange = maxTime - minTime + if (timeRange == 0f) { + timeRange = 100f + } + + val timeLabelPaint = Paint().apply { + color = -0x99999a + textSize = 22f + isAntiAlias = true + textAlign = Paint.Align.CENTER + } + + // Calculate how many grid lines fit without overlapping labels + val sampleLabel = String.format(Locale.getDefault(), "%.0f", maxTime) + val labelWidth = timeLabelPaint.measureText(sampleLabel) + 8f + val chartWidth = chartRight - chartLeft + val maxGridLines = (chartWidth / labelWidth).toInt().coerceIn(gridLinesMin, gridLinesMax) + + for (i in 0..maxGridLines) { + val x = chartLeft + (i / maxGridLines.toFloat()) * chartWidth + canvas.drawLine(x, chartTop, x, chartBottom, gridPaint!!) + + val time = minTime + (i / maxGridLines.toFloat()) * timeRange + canvas.drawText(String.format(Locale.getDefault(), "%.0f", time), x, chartBottom + 30, timeLabelPaint) + } + } + + private fun drawTasks(canvas: Canvas) { + val minTime = data!!.minTime + val maxTime = data!!.maxTime + var timeRange = maxTime - minTime + if (timeRange == 0f) { + timeRange = 100f + } + + val taskHeight = this.taskHeight + val taskSpacing = this.taskSpacing + val slotHeight = taskHeight + taskSpacing + + val labelPaint = Paint() + labelPaint.color = -0xcccccd + labelPaint.textSize = labelTextSize + labelPaint.isAntiAlias = true + labelPaint.textAlign = Paint.Align.RIGHT + + val borderPaint = Paint() + borderPaint.color = -0x666667 + borderPaint.strokeWidth = 2f + borderPaint.style = Paint.Style.STROKE + + for (i in 0.. = mutableListOf() + + /** + * Add a task to the Gantt chart. + * + * @param task The task to add + */ + fun addTask(task: GanttTask?) { + tasks.add(task!!) + } + + /** + * Add multiple tasks to the Gantt chart. + * + * @param taskList List of tasks to add + */ + fun addTasks(taskList: MutableList) { + tasks.addAll(taskList) + } + + /** + * Get a specific task by index. + * + * @param index Task index + * @return The task at the given index + */ + fun getTask(index: Int): GanttTask { + return tasks[index] + } + + /** + * Get the number of tasks. + * + * @return Number of tasks in the chart + */ + val taskCount: Int + get() = tasks.size + + /** + * Get the earliest start time across all tasks. + * + * @return Minimum start time + */ + val minTime: Float + get() { + if (tasks.isEmpty()) return 0f + var min = Float.MAX_VALUE + for (task in tasks) { + min = min(min, task.startTime) + } + return min + } + + /** + * Get the latest end time across all tasks. + * + * @return Maximum end time + */ + val maxTime: Float + get() { + if (tasks.isEmpty()) return 100f + var max = 0f + for (task in tasks) { + max = max(max, task.endTime) + } + return max + } + + /** + * Clear all tasks. + */ + fun clearTasks() { + tasks.clear() + } +} diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt new file mode 100644 index 000000000..9290551b7 --- /dev/null +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt @@ -0,0 +1,15 @@ +package info.appdev.charting.data + +/** + * Represents a single task in a Gantt chart. + * Each task has a name, start time, duration, and display color. + * + * @param name Task name/label + * @param startTime When the task starts + * @param duration How long the task lasts + * @param color Display color (Android color int) + */ +class GanttTask(val name: String?, val startTime: Float, val duration: Float, val color: Int) { + val endTime: Float + get() = startTime + duration +} From 73a3c7825dbfe0b7d2ad78b564395e12f243e6f2 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 12 Apr 2026 14:17:38 +0200 Subject: [PATCH 33/36] Optional hatch fill --- .../chartexample/TimeIntervalChartActivity.kt | 2 +- .../info/appdev/charting/charts/GanttChart.kt | 20 +++++++++++++++++++ .../info/appdev/charting/data/GanttTask.kt | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt index 02a0b43b7..1e2c1cd32 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt @@ -29,7 +29,7 @@ class TimeIntervalChartActivity : DemoBase(), OnChartValueSelectedListener { // Add sample project tasks ganttData.addTask(GanttTask("Design", 0f, 50f, Color.rgb(255, 107, 107))) // Red: 0-50 ganttData.addTask(GanttTask("Dev", 40f, 100f, Color.rgb(66, 165, 245))) // Blue: 40-140 - ganttData.addTask(GanttTask("Testing", 120f, 40f, Color.rgb(76, 175, 80))) // Green: 120-160 + ganttData.addTask(GanttTask("Testing", 120f, 40f, Color.rgb(76, 175, 80), hatched = true)) // Green: 120-160 ganttData.addTask(GanttTask("Launch", 150f, 20f, Color.rgb(255, 193, 7))) // Yellow: 150-170 // Set data and render diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt index 1afe35bcf..01029f82b 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt @@ -8,10 +8,12 @@ import android.util.AttributeSet import android.view.View import info.appdev.charting.data.GanttChartData import java.util.Locale +import androidx.core.graphics.withClip class GanttChart : View { private var data: GanttChartData? = null private var taskPaint: Paint? = null + private var hatchPaint: Paint? = null private var gridPaint: Paint? = null private var textPaint: Paint? = null @@ -40,6 +42,12 @@ class GanttChart : View { taskPaint = Paint().apply { isAntiAlias = true } + hatchPaint = Paint().apply { + color = 0x7DFFFFFF // more transparent (0x7D ≈ 66 % opacity vs previous 0x55 ≈ 33 %) + strokeWidth = 20f // thicker lines (was 2f) + isAntiAlias = true + style = Paint.Style.STROKE + } gridPaint = Paint().apply { color = -0x333334 strokeWidth = 1f @@ -179,6 +187,18 @@ class GanttChart : View { val rect = RectF(startX, taskY, endX, taskY + taskHeight) taskPaint!!.color = task.color canvas.drawRect(rect, taskPaint!!) + + if (task.hatched) { + // Hatch lines (45° diagonal, bottom-left → top-right) + canvas.withClip(rect) { + val hatchSpacing = 10f + var hx = startX - taskHeight + while (hx < endX + taskHeight) { + drawLine(hx, taskY + taskHeight, hx + taskHeight, taskY, hatchPaint!!) + hx += hatchSpacing + } + } + } canvas.drawRect(rect, borderPaint) } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt index 9290551b7..9c96eb613 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/GanttTask.kt @@ -9,7 +9,7 @@ package info.appdev.charting.data * @param duration How long the task lasts * @param color Display color (Android color int) */ -class GanttTask(val name: String?, val startTime: Float, val duration: Float, val color: Int) { +class GanttTask(val name: String?, val startTime: Float, val duration: Float, val color: Int, val hatched: Boolean = false) { val endTime: Float get() = startTime + duration } From 1609f151db09d3f4b74c0260d21f61ed7d13e0b9 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Sun, 12 Apr 2026 18:53:59 +0200 Subject: [PATCH 34/36] Thicker hatching --- .../info/appdev/charting/charts/GanttChart.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt index 01029f82b..8f8681906 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/GanttChart.kt @@ -43,10 +43,10 @@ class GanttChart : View { isAntiAlias = true } hatchPaint = Paint().apply { - color = 0x7DFFFFFF // more transparent (0x7D ≈ 66 % opacity vs previous 0x55 ≈ 33 %) - strokeWidth = 20f // thicker lines (was 2f) + color = 0xAAFFFFFF.toInt() // ~67 % opaque white – clearly visible over any bar colour isAntiAlias = true style = Paint.Style.STROKE + // strokeWidth is set dynamically in drawTasks to stay proportional to bar height } gridPaint = Paint().apply { color = -0x333334 @@ -189,9 +189,16 @@ class GanttChart : View { canvas.drawRect(rect, taskPaint!!) if (task.hatched) { + // Scale both stroke width and spacing to the bar height so the + // pattern is always clearly visible regardless of how many tasks + // are shown. Rule: spacing must be > strokeWidth or lines overlap. + // strokeWidth ≈ 20 % of bar height + // hatchSpacing ≈ 40 % of bar height (2× the stroke → clear gap) + hatchPaint!!.strokeWidth = taskHeight * 0.20f + val hatchSpacing = taskHeight * 0.40f + // Hatch lines (45° diagonal, bottom-left → top-right) canvas.withClip(rect) { - val hatchSpacing = 10f var hx = startX - taskHeight while (hx < endX + taskHeight) { drawLine(hx, taskY + taskHeight, hx + taskHeight, taskY, hatchPaint!!) From 3d2dbca847267daebdad3c2110ea327f9dc02041 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Mon, 13 Apr 2026 07:13:51 +0200 Subject: [PATCH 35/36] Set min/max --- .../chartexample/TimeIntervalChartActivity.kt | 3 +- .../appdev/charting/data/GanttChartData.kt | 52 +++++++++++++------ 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt index 1e2c1cd32..ad79bb404 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/TimeIntervalChartActivity.kt @@ -31,7 +31,8 @@ class TimeIntervalChartActivity : DemoBase(), OnChartValueSelectedListener { ganttData.addTask(GanttTask("Dev", 40f, 100f, Color.rgb(66, 165, 245))) // Blue: 40-140 ganttData.addTask(GanttTask("Testing", 120f, 40f, Color.rgb(76, 175, 80), hatched = true)) // Green: 120-160 ganttData.addTask(GanttTask("Launch", 150f, 20f, Color.rgb(255, 193, 7))) // Yellow: 150-170 - + ganttData.minTime = 10f + ganttData.maxTime = 200f // Set data and render binding.chart1.setData(ganttData) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/data/GanttChartData.kt b/chartLib/src/main/kotlin/info/appdev/charting/data/GanttChartData.kt index 97fb7c0d5..e2b8c41ad 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/GanttChartData.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/GanttChartData.kt @@ -8,6 +8,9 @@ import kotlin.math.min * Manages a list of tasks and provides convenient access methods. */ class GanttChartData { + private var mMaxTime = 0f + private var mMinTime = 0f + /** * Get all tasks. * @@ -22,6 +25,27 @@ class GanttChartData { */ fun addTask(task: GanttTask?) { tasks.add(task!!) + mMinTime = calcMinTime() + mMaxTime = calcMaxTime() + } + + private fun calcMaxTime(): Float { + if (tasks.isEmpty()) + return 100f + var max = 0f + for (task in tasks) { + max = max(max, task.endTime) + } + return max + } + + private fun calcMinTime(): Float { + if (tasks.isEmpty()) return 0f + var min = Float.MAX_VALUE + for (task in tasks) { + min = min(min, task.startTime) + } + return min } /** @@ -31,6 +55,8 @@ class GanttChartData { */ fun addTasks(taskList: MutableList) { tasks.addAll(taskList) + mMinTime = calcMinTime() + mMaxTime = calcMaxTime() } /** @@ -56,14 +82,10 @@ class GanttChartData { * * @return Minimum start time */ - val minTime: Float - get() { - if (tasks.isEmpty()) return 0f - var min = Float.MAX_VALUE - for (task in tasks) { - min = min(min, task.startTime) - } - return min + var minTime: Float + get() = mMinTime + set(value) { + mMinTime = value } /** @@ -71,14 +93,10 @@ class GanttChartData { * * @return Maximum end time */ - val maxTime: Float - get() { - if (tasks.isEmpty()) return 100f - var max = 0f - for (task in tasks) { - max = max(max, task.endTime) - } - return max + var maxTime: Float + get() = mMaxTime + set(value) { + mMaxTime = value } /** @@ -86,5 +104,7 @@ class GanttChartData { */ fun clearTasks() { tasks.clear() + mMinTime = calcMinTime() + mMaxTime = calcMaxTime() } } From e34cf3e4239737325a0237821e3c3fa1b81a9869 Mon Sep 17 00:00:00 2001 From: Hannes Achleitner Date: Fri, 3 Apr 2026 07:35:55 +0200 Subject: [PATCH 36/36] Adapt screenshots --- ...lChartActivity-Timeinterval-1SampleClick.png | Bin 0 -> 15621 bytes ...IntervalChartActivity-Timeinterval-click.png | Bin 0 -> 15621 bytes ...rvalChartActivity-Timeinterval-click2020.png | Bin 0 -> 15621 bytes ...rvalChartActivity-Timeinterval-click7070.png | Bin 0 -> 15621 bytes 4 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png create mode 100644 screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click.png create mode 100644 screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click2020.png create mode 100644 screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-click7070.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-46-TimeIntervalChartActivity-Timeinterval-1SampleClick.png new file mode 100644 index 0000000000000000000000000000000000000000..3e61b1da061ab51d47978fbb34e08e2c94c58158 GIT binary patch literal 15621 zcmdseXH-<_wrv4o1QC&pprC*Rkt8`NARr2g5=1frlC$IjMI}d-Afb>TN)7@N3&|M; zk(`s{T;$@d-F?m-qkp`6@9A^ipXV5iZnVPQ-}kLG=bCeF|9i^vq$lZ4qEIMOMFm+^ z6pBC^g(CDmeiZ&@G3PvPnmCz3+YQRkF$Y6|g

{ODJs+E;g^oql9^|l<9(Io{c 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

{ODJs+E;g^oql9^|l<9(Io{c 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

{ODJs+E;g^oql9^|l<9(Io{c zb)u67JQkWH7KXhyud@yuu&CTJJllBbP4L#1DiCb*(Y{{(gS?Ro(=fR8@_Q3VV9} z7iC_Dy?;Nq;=aAJLrz6?wCbDBu@cAGhzAXtObIUICm7M&2IUnME#(Q3k@Xn-23zI+ zT8kUqBY%3kSfzu{b|@8$WZMTB-vz0%va+sh9Wj)0yr|r>XGNa`al1P4T4nu4AvLUAD)3pGS-?4iuB|@bHv6&EE>7 z;*J(`zS`H#2Mm>SY!Df#u{47e1*fpHT>3!c>pIOiGI>;F0v#H!+ zg&&i<8r;eP`c)|O!Mq;PW#6Ju$=y!}VZ{zJ?Oa-4{HVCKt~pG%x{)UO@plV*cds_G zgnrL@ETkMK{0(!sZ;DFr!nG6oPIXhPv((Ars&08BT<-Son&Ci^72M)+ z6cZB@dT(IlEF+_`zW#-o_V|eb8?5n}sKhNzO&SSz+!J`0aU_$V`8f(UvKImRmE0Q>Q z>Qu&;Ho5e4UBc}^z5QzR*-8O#=Rvz;=f$2){(5Y4`O>A7)_tF&q)Bi_?!*a`3keDB z%@+?nqmLJJo{lk1$iw2`W#p8UY9>4AgI!*aZQGA`Vyaez3F@U ze6A+C^q%CC#xuGuW5khMkE(T?ht#IU`>cwGj#kmzl#i`w-j>?yrhY3fA)%bKdbkpn+@0Ar>{A#Gz+ks~?uSqWQ@kJ=UVYQoTes^2SSyuMQ zJ^KV4?BUc$uk|*eBS()Ceos++!`FozS}3-~0S z%DttUhK6J8eFZ@Q0ZCuKMqgHnyym&`sU!^V6*m9IuZLzsF;`-S$TP)6}XGDXV2ceb?Ycp2s_KIApLJIh*2BAfB5Jf z%xUxtEum&-XDO8rJG}5Ur+CV%;71xRt;CM!aZ!*wqiZcX;k#kpHP^K#d*a=+BOS#J zn?)sl$#ceJD_YmB2OBwa$_d{tI5{p4$KOxBWl+%P0{0;7xhsIm&d!Ebbj;b=xzu^N zJ(BzJf?fYtJC+WKEd~^Hg_kd1UNde!2d(bgLSI3Kc*vsylRmH0up|q;d0))Bj(Ts- z_e8}=UA~;!dSYiTXlnGE&!E@gf$>#_#l8aH1POQhy~W~zjx^lz2$MII&fW*}*@S`i zcrpLrVB*{D9Z)=xP>iL|7Twx9=Euees_Fu%GxA-Vd*Lczi@qGpsGhRo-HoeH8(c07i`_v z)+UjL4tAWBlzUwgUc6J?S)=0YyfuII@lYOv*t5(*J>1KeN8n&7VkA9not!RTyB20W zR7%h*L)hCvKKyLJN__Ws^7h_dSBfIV-q_q`y^gkaP+*`^j0`?~cc+NsfWOQwzMEE0 zZa}6shE5xmQBzl!+S=ATQ6FqH_|=ZGot}X~I~DcoC4WP{Wgj0N4$J8Baz|r0i`j6w zhbIy>u_!#i6`Lm7h8gM-@0ok&Yd7e-^7VEx%YmZc zmMESAkDWC>-=udUPIKqtMV*q=GSn%RnS^aG9B%3z5?8$-KGj?9y6!ZxZZ3EpqhR&b zfG|2D;zgTDO3i zDzc2JnO9I&{)iyy{I6hG&ODouO1>_rx3;j0GVB~2g*QeV^m?Rn4 zy@+@Bch-@$hs9dW&^rjgAN+BSfbr;)Da~fxeAkEdlJeSV(Q{cUnehWgusPSAlA1cU zxEPGZVm&1QWGQ4Ou7CTA&$#4}k1`bF1wn-~7tqLG!Idu8#4_3oTI2{ZF%b+_RUr!(Zd zZ!)0LJwtEErszK1Qu$l7^RM6k?-1C(@ku4WwotVtR9Ce%M`S*Qn{|Ec-a1ozp`YmO zEdyvYn-WKS1&ervidJv7HDpRu(f_oe@8enD_w(Cjd_kk&{`bhXx(}M>rn0hf37^#_T(Ft&O!@~y$)Ab*GsIdYd}a=;X5NR zn2w*HAIy7bXK58Ov$DQsz5D(@h;_lv+ zEKgP+OfNtGD~r=(cfW*DIY)PLyf}b)dM{EZTBVjCMPE&?RCglrNH z4CSq@uk!NpN-LsrA|oQIVSc)8V#10dARxGO>Czc$>IQ&6|G>axn7eSO1Tp83=xCbu zIFTCoif!D0&Fb13vy>DD2yd=M&($KUf%;gR%D%+x7l8sM!FU^0xcxj$F1T9d1o1~E z{f})b>R`N~5qNs$$B!ReK#F>MHBDNhpSPJzAl#jYr$h1(nin$1z+vmrbq^M__>~K2 zte&l{7|JLijNTV!@>u!i)bJLLf)Y=KEL^S6F)~&FAi&5x>mktu^sX6@G!C8XA|f#W zY^y-V-hBNk{3~0hB~HWv1;EJ&A^6 z<=f+d0ye`8C=Cq_);rPC$X5dmQ&d#M8RHfQ#`udAHLxbL21Ki^m zbMxA1jlrLO96OVXp)TmN#Pz`3K;Rb?1ZN^`kk5I@i5fLfY*UO?cSA34uTE%}yX3X* z12-^S>@TdXs~ekhTjW3XxY)Wdr)&ckpHT=v-f1ibO?>QbcWTSNhc>gSXE2`O!v02Z zcm~gzl#~=ySVBVk48}{>ItGp!(dCgz_Uns1R{=x3Bfq_ByjxZ&MMDZg{&EH?P&hhFblj-Z~CLDaP~4I5?`ix(Zr*#&457Fl~*#QEWZLp@W}scN!iZz7sF{?iizsHynU` zIDQC&hRISBW_y?q4T1UgFg%z|_}zhR;bJCLf@^ei6hPIyCgogr`Vtm)CWKKUAR~iw zY;5c_HmBSCGK?xP>_9Iw#7a!VfXtbEE4XyJtpf^cVX)*4j0TFb2DaSA0?iYQq7E?pm`t<2I!kQ&FjbUxKx3|NfsskuF zo9`&WfK#zG?6A!r=(jEyATz3=>q@MmqJnD2NU3OP(cQDpUi_Svz|6;&Kgb94w0w6i zqcu+C4e=?eT(d6Lqa-w_DwqfQDm}$uL5wR;3mH@&L5d1SgRh79Jz7Z*VLToWEvVl{ z7rpUAKF3|Kx2GoxCJ|nDoaL~~1nKz11kwVf@Y^h~exKI5pOAmn31;?AEqj&G!-Vv{S_H^l#&f|kycg^j3Mx%0h^@P5vMLJS8Oq!qT+&$ zcx~Jg*_Q@qJ^@j1p!U?wT46j(grN^%)v%a8Fk5=o ziQ=X@6)R+uy}Adh9$w-w6Y=hy9L#tya{-*Gl~Pa7%rrbuk*H%j+?H=okO%~x-P6;9 zw+hK9v>w`hZ^QtE9+~1z+v6@mHw3+8baqw|I?28KG);5Rj(R+DpsW3AVZq8;KLKQH zpI%(l87t=FFe=ot*!VIFlpPc!Xr>6!H~#2(c(8?Xob62QUj{tvVdr@v;8;>pg6sw@ zzez7PobWQ4qGtY+6B!yg^@e%9*bp}6&6;S5s?y2X*`}4TniwfBNixap3!n})!1-a3 z4+(qs?iJJQ(Jjp(M>tsYU zQg?4vQ~csV2JpB0tR~#q%NGS)`v0}n{V#QOji;>c9*sDd}Zs7|@L!K*h43?<;lU1O^0nN=`-=+JA75 z`Sm@N#V5}_9P}j1>yJw+N&)Hv@5Wx|efs@EQgSkk&9r%Wd7zgOfgISvgS8xqwE)2u zB*06YoPnU!MLzD>-JB<&5wU-==*3Ph!|pBkq)_X`@#Co5rynRGf(y_dC^VPVI%-5) zK-8T((bt|cO1Rkq{R6psp=Ho>YY3J3xH!92G{2w#A*QgiFwQ4NN`?E0oRek)!hVe1 z;doqh^l|AYPcBJHN=`+V%x9OOdMiE44hliOJRewh)5F6fX6v@Ha=l4`-Augm_|%jd zC+0@2Y$<8|iQ8CSRl1wrZ{MCnb#`^_?5}sUa69a8+o4n+JXp-x+1uj>y&tF)u;F9C zIf?W^DM1C%@FuC(^B8gv=e}NBTU+q0)L5QPO-m!nHSgvC*jJlf0|nX`T?yNwsGy(^ zZ`_#g2`w)dhff0MA`$pR;mQfwR!~~L{{`T+b(F3z2GR2W27;Q}a=0I04fFFV4##@+ zY6x3`%L`c8h}hT`;68a;S%vEtWl7*8?)?+eDL(xL(bdpWFbUiO9RtozU!IZg!HP2w zt@iU@{eCkm+_mh@eGW_(CIpxyIry`HETJ;BOCo@ZpN#5gZcbm>?DV~s`KaLa_O?rZ zk(Cmt1#!ampS-fZJ^{@ak<5Ya@pVy9 zP#|yv(?91HzIf376yRp2R$%~~V-OJ-B`j~GQ2jph^`x%r6eWnT1j1f;aWgYB=_a+2 zv;?<+#>Phb7VsEW=Q48m9H#GpG>dox3tKCTz(%08Q^24Ovq=lLMV=-Q#V7dv9TW*I zUrMB8j9(vAHhYC}onhV?8X5&q%zlqxr4SO?_Fgc!5x9UhncQb5p zjHGAj0qY!w9>5Hgt~3yAC~v9Cnky4zz69g|sYDQs0j%e9UeboK1Fo*ByZc>YV&Xg+ zQw-Ij{0Qs!5MImz#0=+~9-vT@7gD#pE3TA0_WY2GaEns6F{UtJ9T}$ zo3ZM?FkBJq=Xabf=;M+=J#Xx-{~vM0uTcfj1ajt8l$Em`VdCzZv%Tab^T(|&5(EfH zN4c98EY-fYXz%*j|JFa=OVPG#)S`XM$XfJJa+|nsr>`e&xk#Oe_MGUa!TG()VfO<9 zAyohxGwG>F#Az9I3H4* z^U$F~pV_9gtlI2jJ=rHV%g=he6dC-U#NS2H|MCMpp9 zyfPFI6S2d zta}0T33qTKFEsqGB_%)djazmZ185XhGh*DxMqw_9mT)hUX(6E#@Dm#GD2TlE&`@si?FXxPJ2FN$5`W zm6esoABQ~F+nfZ&EAhJ)2nz3I}wXXBF`J|5V#&eLd7Mmh77@ z(f&Gnn5Y*x?e50xgQd-~<;qx)@m9d|3I|QmjpmU*3>oW-#Uq`drrLLD6`IT4xkGaI zy}$=hgf_2<^4`~}?=ZK^zzNg)xRL1_%}h)@7?C;S!;Wt6{!OH*so4m)1H_pWl+c=i zfmi^PYu1Cp;CAYa@c{-SI_)92W+4ATH?FRFPE<0J;J&iP;reuPZyDPGn!bqbC<+yO z-D(`vdRQ2fw)ezUK#8~aMh;I`0b&Q%soLK^0MU_4r}XWglD}q!o8ao|YMi9!`>HDG zqhyS&(4*~~oq2?cfY?eN?mHCOi~wXa!n6wGMFdO{>-fE83I>MOjoGgF%JZpX5BB47 z?jO4b@QDbufVBi?pt+fwbAW(g)R%7z(__xym;*HX94ub+UjEkf#6%6?1OncA=$*^Z zomjwe1LFd)3uZ>bQRryY;;Zf^ZV(Xp{gu#Wf6R;hwV#{%fjZ`z(~+(K1(fO`xk zA!1sBRKL()7zirt>U6A4fAR@raQg4$kduke&tdz2d|D>*n9nNi1qB9tJ7!p57)8kG z>01EFa{DaH!TSXPsBcCJlzi}0p&_sITjb<{e~G9x&F?s8e9$ycw>;S>6hkhO z1?VerNb?-t-}lgUo4w4(&yUDRptK;U<23(k6%@EPVPT^$xmkn5$2HvPtWaD33CY8@ znMOiF!WA5(S>Se{>j85DFSs7S@OgyHs_Nq+%Q4WAKqx~d!Mz#4Z&^9HRj?q$jPG=Q zeaMKt5rCl(=tr@#vJ#%5rF}H^%CBsx^prP%+<+dZ1lAc?3HZjECU~&Qk$VTJ1uUTU znCAdU>MlNxa7+lAgA4!-rJH7eGh{o`@-`Gs2z?*hda&PY@nr_}K=n5MLf1cN&Rr?c zk?yVC4ORs&;3I%LgRrf4(;GH^lU5qH%{e(27h$MzC*0y$q2dU=B47pf6QtnXzTs7< z`MBL3e2cznVPHRvSka&Q9uffoSGiS>aS z8@H$GWNqZ;{{-i;1c*M6Y|j!`Xqv%whG1ma;rCtl=3%#$8JwnI)rhO+x-+i8??MN# z08V_T%`~kw_RZCjNk{)ejvCQ5_MaiM0n%y%D259;PGEO}XoNpl_KFe_I*urbAawx0 zo&S#uDiktZ-P|<(mHDVOK_XUY*u@73qNwvyYp4{y5zaha>pEGg zwZPkLPY3Z3cbdf&VYHrxUlZ-?!FF@i9G#p zgocKOnqbO#YQcGSg@6YwOb{_n8%sm6$Cy0Az-E05LT2JZ&v2YstsglA5?+EP>&s%PG) zo}s(9{!};pI+9cQeZ4;KlzSm9*1b~+hf2E}JNJ<8Qr%5 zV$#L2IXN~Z#|lIrgZ>1x5gNn~P!0LID#7$afexvfVH8#`?~d;7dCK!*@G0o-F(MAE zP^GzEEyTBR52GN~0EZ4FW?as;zxTMt1EE$%fW`l{3PLKYQ@flHTQBW)pIB*-=3Ld$ zCqS|g*A#H(J!tg_i0psv9IQ4AjTGABRp=Bof4ACxso;FQB<|PKOwt&_-YSr4Kp&ZW zKgWj_5(SOKO!l1A49xioEP3Tu$DJK64H1_8dKwq(k3-4PJ)iQ`u+?035CslkdT+{AH+57cd(eznj7Sa&})2T-zD{Zm-IdrY<~m4&KM2+I?Jo>f6y& za&fzzmj$nTe*fmkRUmq}OHz3px|ze?8P@evP2Dsp%pcn=j0UyNtIh5FY|s(r7XEz{-hWODW-8gbjHbs)%`yZoJ_ixPc_{hjxK!t!3JbChDbbh`it9ZB> z^a6P(s^-=*=Ve`poGJAeS~TRKH#6*)DVdl$oJTz6vFHOt+e3l9ZV2k)%U7=;NmK^~ zH!)pIRep} zcRei3-CduEz5+i+q!&;tkfHt3rI(-~B8noClG4MkQ3K~S!bnL-#KYjdyS1!m0GO=m z=kNak2pH#JS77Q2E}}4SDzJ%=!T-EiJ$M$Hl^!Bs=A8szhFj=0gbxri z=(^3YcIu{|!lqz9R+0Lu_9$>(7O>u-wIL0S4dZbWq*~Acs!a-zSzGnqz0tRum)Y1H z=6+tm?{s*b1se<&6JCG%p8cs7K+MH;DoV=LP9+{YcnKmhK(?X{@@hA3-XsL;At^D@ z8%eK$$^}vosL{eOzQ6m$0^a6RBA4wIgZ^Th4$$ITKywYvuxM(}EGrYk?hP&HYGpyF zARN+#Z#P}t+}6Nh>#A^f*4Ea})#M_fza9i9gD4l)bgvvZl>tOizJTOyit(fZ+(Nw< zFslaIyEDb(r3jk?;&Ap4H`x>uy2XpOAf?QLee|kpaA?SEs8kRjkA&pCmZypQx z<07stH-tldby-b1C`9N75TOnU)R~k_;$Fc;I?&+e}x^evIYNM)(yXX4lV?hrc)bNw5z_uFGb(MiVFq zAWs0#mzGA|v%g$e`F6?4&DFJgfJSz57r1FCY%0j^#8qIVc0dDOSy{}c~)mhR&%ck(A7Yd})(n%%f8_^d6pIee+bp+9j3b|AhGAglkXD=;t+1_M6a zH!@~YIp8D!*vF+|<|{D+kC{?;A35JBT3TAl${Fyf;`xd?1Njk`!gd$N#hSz$J}!%e zJ#qPxN2YQA^c=rM@czXZDW&J_!F1GeSt+dNX( zIS8U&x9pXMt&A~)keCM;Bq++^Q@O3KkF%7>73C0UA zf{du+wf<0?zQF1cTm+kXb!%&8ye?3?)Um135=I|d2swfxxsTt%nr^R*fo5d4GI|5F zexI~7HZTqZ@gyJ*BCMeGyY@ezq7r)0#hR+Sg6CETRLeTAvb)E^4biG<8j5<@V9SeRaMWTxV65F`m-fm;NycT zqfX0Il1*=@Gb!j^qI@nI79M^B78l3~WdcPeLYiUWkkKA&vXeHN&mv)VwuL)@BMo-E z*_n~4p$c~)=uw*zK^zvA6R+xdr=hbGt_)la?b)uFW@7JijRqAycqAj;yfm18kWt?W zF?d8gQW5tBriN7Vca&$&=wqes%hHAHj!=@5e`fo4BF)x*4F>)SZEq!8It2bu_W?a> z^);j_N_fS;_4oaM2d95p5_BBQi05Nl({(-DQ*fyMDk$^4ibmw*W)J`F9V79W6x`&@ zOaXu!dVA~`fG9`&GNd=343#=DNTK6iydZopWTOmyZhpax6(TDhyR&IMai(jNO~8|D z@#|n4_CZ!rYJY`@n3x#${RikUFt)sc#?aNENk5P;iW%rL%5JBD3}gok=fdtdE)=xJ z6g?~&(s5Z@6NAMzOyICjLB#SL6;)^EBYq@~2w%y$U*Nd0wW+l}Z+1;65lKUUxByuT z5Z+IN=qtW8=s?--K3YY9Bx%Di7!@em)s2lw$e^5~q~t}W!#mOU<|ir~XU}#>9qtRx zAp9D}WYi@#wslB(-P6?c!(f;a5)uF(AS>C5AifNl#LK$zIG@=5!N?HwQa?>WvJs3A zv@p`t)z!U%N%VoX_8l-X;WyBQUxLCj@*z+601fF5kaj@xhiQ!!TyI#KDF2+~fo->S z3$m4?_B(6FFpx;VtWE448rmT^Q`~2XgmjS(Yt=8{wzWixXhP9KHjL0A6XE*??OV7D zMVFw{6P#gWY=;azM_fa9;iQHj5T3(5>|t_lZWv6{Wr+8CWAI$ihgu+8(E{OCf`(!{wdo#_DtZM-xXg55Our}I0L#V)oP_cB>&xK5 zfK>$BXLdM+dipCf<{<3Kl`Egs8%V-pVopdueaa3z@be0$QwGS+X~;WM!jNYfsyCY- z9v+SY>A9@_FV_qgciQ{YCwBM!#K|)N@nhab189gE{E>2G#CGPr#Xnl=D`>!%CJ%h& z_+y#R($F+Q*wI8v8$5jM^gePjQGguWLRqkmD9F6FHYIcAIP%nvj2xD(o0ysseTU9x z-*sEBrK#!jYV@4pW6`yejOriAq77HLQ&3Ueg88=PE<~7U@8wg27X$qgo-z1SOf0r0 zl}17ug7V1HXrOE^gfLts00xPYI#|)mf)@U0M&p<`sczt^^c+ho#7oikK=e>gLn}h z76!*wC8q0aw;GfR2qq{RVcWz5r+?fysrqhxPEnA#n_ma-hBsU(LLwW0se4 zmZ6gYpA%eITqDG*VWjWlqvlVa0FBlI3`R&q^P6?1&{b%tv3noQn4`R9I`lvz2C?e4 zsZGHXvm7dY2Tau-cqub8^H!^AbF>O7Z+Mq*K3Te3AbO*a|a&q#T+s1D`4VHt0gGH|^*xfnKfWUbi9&Kvf5+LcU z|5xwsU#dpgcYBfCM6+(n!`X2TVp%M{7v#e z=Z_8@dWQ2gPzW5sNSyaMCi|!u1eSfbM!XPT`lE8Z)+}SnKhHR4MNfK?`@x@DG?M8k zTpQ|FWmAqDeMPN1IuRzs+FxH ziB0+HQJ#v8h=cw4?%T^9jvG>uu+94*l(BJh2cMu|eOqF%GxN_I?FD%e0%c=hD@=fT zkLv+sL(Bk#ucdeMLh$3Xjg1YUn~J%)d5jrs+BkGd7Z;aaGH0bjAzt3RLA6TYs`>?e zNDSW@|EWl%l>7X_VBxnPypD4}Pb5~?3>I4S5K@jfYR4ocF-z|LW`S^1v&7b54VV__ zww?3Hnq!*)AobGM{8H~jg(GoA$RR!4ZkZYzy8(fOW2YJPVF^JMw}Z!Vz)(jl2jGGF zV8s-=37h0;u54|Y!hqrdF_yT_o` zAb1oJ7A6h31n^77KKL@GuT1y6x5X7XZaDPRN1s101K@%91-@iV)1XWPd-jK5%o)gZ z!s8F%OzVT*swQd5KtUk`%oL6YTy-u0)XBpuTwIWbtR1t`ez)U9*=q~2xNj{jf!!Gz z1(4)QoaDKcqgxSu&EQ3i3iIa>@K%uo1Q;1+hiLY3IE?=C=f+;H!=6gLlst@K%9XJGiz-r;5kKS&l#K9bZ z1nnJ}^pVKcnp1J-GOb1}#H=7%NongruV;gnIIZLzQ4<~%M6$NNZU#zamhSe~Na%79 zYkCFj9E9f{@k7Kcf)PjoItTm#<*j>6fm0*?4;1DO2?Bk$kH-%pkmbsF8CL`{jMq64 z4MOJ#`M>vc-`|yL{wwaHZ833oy;)RS$`4)+ ztIZI})t-P{8zgYw9L&IzPQ3P$jU3$bUmU!xZCal_#UD&C+Si-Q1=+*%Q9WB{=;8p| z!((GD4ojd*ZSU@?oAqgxoaACw)!5(PhXg^!uYBcG`m(ky9Pvb4@fwGhDZ+QBzXViL}f8@DTb$l{K_f{!XE1oq8HHUwz?;}3BD+gtoUJybfSMQ^if zZGZN#zw8ZcAnG?cL-j<(u@!xnoPN!CxaqIGmPLl96@7J_u0h&s;SwHFJuJd6zbcYV zEH8~^ss;ExjD0}%T%Kzuw{x3d0d~6aGLwkIJ7(tY=`X0t*tpuIje?$d8;xCBSh<=< z8IG;fyF9xGm80n;i zf=yJPWze53-{pz%`ze&BbHr{kZW!)T6i(zH*C{#degSqgM@&!FR!Ik*$UXY)^Ax(PnSyhBBn?yc5|L_YTN)7@N3&|M; zk(`s{T;$@d-F?m-qkp`6@9A^ipXV5iZnVPQ-}kLG=bCeF|9i^vq$lZ4qEIMOMFm+^ z6pBC^g(CDmeiZ&@G3PvPnmCz3+YQRkF$Y6|g

{ODJs+E;g^oql9^|l<9(Io{c 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