diff --git a/.github/workflows/Android-CI.yml b/.github/workflows/pullrequest.yml
similarity index 94%
rename from .github/workflows/Android-CI.yml
rename to .github/workflows/pullrequest.yml
index c1bd2eb65..350a77f3c 100644
--- a/.github/workflows/Android-CI.yml
+++ b/.github/workflows/pullrequest.yml
@@ -24,10 +24,6 @@ jobs:
emulatorApi: [ 9 ]
java_version: [ 17 ]
steps:
- - uses: actions/checkout@v6
- with:
- fetch-depth: 0
- submodules: true
- name: kvm support
run: |
egrep -c '(vmx|svm)' /proc/cpuinfo
@@ -39,6 +35,10 @@ jobs:
run: |
sudo apt-get update && sudo apt-get install -y exiftool imagemagick xdg-utils libimage-exiftool-perl zsh jq xorg
# brew install exiftool imagemagick
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ submodules: true
- name: Install JDK ${{ matrix.java_version }}
uses: actions/setup-java@v5
with:
@@ -57,7 +57,7 @@ jobs:
cmdOptions: -noaudio -no-boot-anim -no-window -metrics-collection
disableAnimations: true
- name: Archive Espresso results
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
if: ${{ always() }}
with:
name: Chart-Espresso-report-${{ matrix.api }}-${{ matrix.tag }}-${{ matrix.abi }}-${{ matrix.emulatorApi }}
@@ -65,7 +65,7 @@ jobs:
if-no-files-found: error
- name: Archive screenshots
if: ${{ always() }}
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: Chart-Screenshots-${{ matrix.api }}-${{ matrix.tag }}-${{ matrix.abi }}-${{ matrix.emulatorApi }}
path: |
@@ -78,12 +78,12 @@ jobs:
SCREENSHOT_USER: ${{ secrets.SCREENSHOT_USER }}
SCREENSHOT_PASSWORD: ${{ secrets.SCREENSHOT_PASSWORD }}
run: |
- ls -ls app/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected
+ ls -la app/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected
cp app/build/outputs/connected_android_test_additional_output/debugAndroidTest/connected/emulator-5554\ -\ ${{ matrix.emulatorApi }}/* screenshotsToCompare${{ matrix.emulatorApi }}
./screenShotScript/screenShotCompare.sh
- name: Archive screenshots diffs
if: ${{ always() }}
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
with:
name: Chart-Screenshots-diffs-${{ matrix.api }}-${{ matrix.tag }}-${{ matrix.abi }}-${{ matrix.emulatorApi }}
path: |
@@ -120,7 +120,7 @@ jobs:
- name: Code checks
run: ./gradlew check
- name: Archive Lint report
- uses: actions/upload-artifact@v6
+ uses: actions/upload-artifact@v7
if: ${{ always() }}
with:
name: Chart-Lint-report
diff --git a/README.md b/README.md
index cf71f5ecb..f0ce0f1b6 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,10 @@
[](https://jitpack.io/#AppDevNext/AndroidChart)
-[](https://github.com/AppDevNext/AndroidChart/actions/workflows/Android-CI.yml)
+[](https://github.com/AppDevNext/AndroidChart/actions/workflows/pullrequest.yml)
:zap: A powerful & easy to use 100% Kotlin chart library for Android :zap:
+It comes with library view based `chartLib` and composeable `chartLibCompose`
+
### Packagename changed ! 🛑
During the full Kotlin conversion,
@@ -26,12 +28,12 @@ dependencies {
}
```
-### Gradle Setup with Sonartype [snapshot]
+### Gradle Setup with Maven Central [snapshot]
-Currently only from Sonartype snapshot staging is provided
+Currently only from Maven Central snapshot staging is provided
```gradle
repositories {
- maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' }
+ maven { url 'https://central.sonatype.com/api/v1/publisher/deployments/download/' }
}
dependencies {
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 3ddcf9bd0..c78733f72 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,8 +4,7 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("com.android.application")
- id("kotlin-android")
- id("org.jetbrains.kotlin.plugin.compose") version "2.3.0"
+ id("org.jetbrains.kotlin.plugin.compose") version "2.3.10"
}
android {
@@ -53,10 +52,12 @@ dependencies {
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("com.google.android.material:material:1.13.0")
implementation(project(":chartLib"))
+ implementation(project(":chartLibCompose"))
implementation("androidx.window:window:1.5.1")
+ implementation("androidx.viewpager2:viewpager2:1.1.0")
// Compose BOM
- val composeBom = platform("androidx.compose:compose-bom:2025.12.01")
+ val composeBom = platform("androidx.compose:compose-bom:2026.03.00")
implementation(composeBom)
androidTestImplementation(composeBom)
@@ -64,7 +65,7 @@ dependencies {
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-tooling-preview")
- implementation("androidx.activity:activity-compose:1.12.2")
+ implementation("androidx.activity:activity-compose:1.13.0")
implementation("androidx.compose.material:material-icons-extended")
implementation("com.github.AppDevNext.Logcat:LogcatCoreLib:3.4")
debugImplementation("androidx.compose.ui:ui-tooling")
diff --git a/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt b/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt
index 24bdb4eef..5bbdbaf80 100644
--- a/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt
+++ b/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt
@@ -1,6 +1,7 @@
package info.appdev.chartexample
import android.graphics.Bitmap
+import android.util.Log
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.compose.ui.test.onNodeWithTag
@@ -25,6 +26,8 @@ import androidx.test.ext.junit.rules.activityScenarioRule
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import info.appdev.chartexample.compose.HorizontalBarComposeActivity
+import info.appdev.chartexample.compose.HorizontalBarFullComposeActivity
+import info.appdev.chartexample.compose.MultiLineComposeActivity
import info.appdev.chartexample.fragments.ViewPagerSimpleChartDemo
import info.appdev.chartexample.notimportant.ContentItem
import info.appdev.chartexample.notimportant.DemoBase
@@ -56,12 +59,25 @@ class StartTest {
@Before
fun setUp() {
Intents.init()
+ Log.d("StartTest", "Timber tree count before setUp: ${Timber.treeCount}")
+
+ // Remove all existing trees to prevent double logging
+ // Store them so we can restore after test if needed
+ if (Timber.treeCount > 0) {
+ // Uproot all existing trees to prevent double logging
+ Timber.uprootAll()
+ }
+
+ // Plant a single test tree
Timber.plant(DebugFormatTree())
+ Log.d("StartTest", "Timber tree count after setUp: ${Timber.treeCount}")
}
@After
fun cleanUp() {
Intents.release()
+ // Clean up test timber tree
+ Timber.uprootAll()
}
@Test
@@ -170,6 +186,16 @@ class StartTest {
optionMenu = "$index->$menuTitle"
Timber.d("Testing Compose menu item: $optionMenu")
+ // Check if menu item exists first
+ try {
+ composeTestRule
+ .onNodeWithTag("menuItem_$menuTitle")
+ .assertExists()
+ } catch (_: AssertionError) {
+ Timber.e("Menu item '$menuTitle' not found for ${contentClass.simpleName}, skipping")
+ return@forEach
+ }
+
// Click the menu item
composeTestRule
.onNodeWithTag("menuItem_$menuTitle")
@@ -249,6 +275,8 @@ class StartTest {
contentItem.clazz == RealtimeLineChartActivity::class.java ||
contentItem.clazz == LineChartTimeActivity::class.java ||
contentItem.clazz == HorizontalBarComposeActivity::class.java ||
+ contentItem.clazz == HorizontalBarFullComposeActivity::class.java ||
+ contentItem.clazz == MultiLineComposeActivity::class.java ||
contentItem.clazz == GradientActivity::class.java ||
contentItem.clazz == TimeLineActivity::class.java
) {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 4eb165a0f..648410d1e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -26,6 +26,8 @@
+
+
diff --git a/app/src/main/kotlin/info/appdev/chartexample/BarChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/BarChartActivity.kt
index bf660256e..88070b885 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/BarChartActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/BarChartActivity.kt
@@ -50,7 +50,7 @@ class BarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelect
binding.chart1.setOnChartValueSelectedListener(this)
binding.chart1.setRoundedBarRadius(50f)
- binding.chart1.setDrawMarkerViews(true)
+ binding.chart1.isDrawMarkersEnabled = true
binding.chart1.isDrawBarShadow = false
binding.chart1.isDrawValueAboveBar = true
diff --git a/app/src/main/kotlin/info/appdev/chartexample/BarChartActivityMultiDataset.kt b/app/src/main/kotlin/info/appdev/chartexample/BarChartActivityMultiDataset.kt
index efad124f7..522a876de 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/BarChartActivityMultiDataset.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/BarChartActivityMultiDataset.kt
@@ -80,7 +80,7 @@ class BarChartActivityMultiDataset : DemoBase(), OnSeekBarChangeListener, OnChar
val xAxis = binding.chart1.xAxis
xAxis.typeface = tfLight
xAxis.granularity = 1f
- xAxis.setCenterAxisLabels(true)
+ xAxis.centerAxisLabels = true
xAxis.valueFormatter = object : IAxisValueFormatter {
override fun getFormattedValue(value: Float, axis: AxisBase?): String {
return value.toInt().toString()
diff --git a/app/src/main/kotlin/info/appdev/chartexample/BarChartPositiveNegative.kt b/app/src/main/kotlin/info/appdev/chartexample/BarChartPositiveNegative.kt
index 04e6e370d..58fc64310 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/BarChartPositiveNegative.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/BarChartPositiveNegative.kt
@@ -54,7 +54,7 @@ class BarChartPositiveNegative : DemoBase() {
xAxis.textColor = Color.LTGRAY
xAxis.textSize = 13f
xAxis.labelCount = 5
- xAxis.setCenterAxisLabels(true)
+ xAxis.centerAxisLabels = true
xAxis.granularity = 1f
val left = binding.chart1.axisLeft
diff --git a/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt b/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt
index 84c7deb04..a3f09c098 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt
@@ -158,6 +158,10 @@ class DataTools {
return result
}
+ fun getSawtoothValues(size: Int): Array {
+ return Array(size) { i -> if (i % 2 == 0) -1.0 else 1.0 }
+ }
+
fun setData(context: Context, lineChart: LineChart, count: Int = VAL_COUNT, range: Float = VAL_RANGE) {
Timber.d("count=$count range=$range")
val values = ArrayList()
diff --git a/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt
index 6fd38ad15..fc483fae6 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/HorizontalBarChartActivity.kt
@@ -108,8 +108,9 @@ class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartV
val yValue = sampleValues[i]!!.toFloat() * range
values.add(
BarEntry(
- i * spaceForBar, yValue,
- ResourcesCompat.getDrawable(resources, R.drawable.star, null)
+ x = i * spaceForBar,
+ y = yValue,
+ icon = ResourcesCompat.getDrawable(resources, R.drawable.star, null)
)
)
}
@@ -247,5 +248,5 @@ class HorizontalBarChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartV
PointF.recycleInstance(position)
}
- override fun onNothingSelected() {}
+ override fun onNothingSelected() = Unit
}
diff --git a/app/src/main/kotlin/info/appdev/chartexample/LineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/LineChartActivity.kt
index a828b8b35..a4ee76d09 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/LineChartActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/LineChartActivity.kt
@@ -56,11 +56,11 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec
binding.chart1.setDrawGridBackground(false)
// create marker to display box when values are selected
- val mv = MyMarkerView(this, R.layout.custom_marker_view)
+ val markerView = MyMarkerView(this, R.layout.custom_marker_view)
// Set the marker to the chart
- mv.chartView = binding.chart1
- binding.chart1.marker.add(mv)
+ markerView.chartView = binding.chart1
+ binding.chart1.marker.add(markerView)
// enable scaling and dragging
binding.chart1.isDragEnabled = true
@@ -254,8 +254,8 @@ class LineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartValueSelec
saveToGallery(binding.chart1, "LineChartActivity1")
}
- override fun onStartTrackingTouch(seekBar: SeekBar) {}
- override fun onStopTrackingTouch(seekBar: SeekBar) {}
+ override fun onStartTrackingTouch(seekBar: SeekBar) = Unit
+ override fun onStopTrackingTouch(seekBar: SeekBar) = Unit
override fun onValueSelected(entry: Entry, highlight: Highlight) {
Timber.i(entry.toString())
Timber.i("LOW HIGH low:${binding.chart1.lowestVisibleX}, high:${binding.chart1.highestVisibleX}")
diff --git a/app/src/main/kotlin/info/appdev/chartexample/LineChartTimeActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/LineChartTimeActivity.kt
index fd1a1bb72..fdd99c287 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/LineChartTimeActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/LineChartTimeActivity.kt
@@ -74,7 +74,7 @@ class LineChartTimeActivity : DemoBase(), OnSeekBarChangeListener {
xAxis.isDrawAxisLine = false
xAxis.isDrawGridLines = true
xAxis.textColor = Color.rgb(255, 192, 56)
- xAxis.setCenterAxisLabels(true)
+ xAxis.centerAxisLabels = true
xAxis.granularity = 1f // one hour
xAxis.valueFormatter = object : IAxisValueFormatter {
private val simpleDateFormat = SimpleDateFormat("dd MMM HH:mm", Locale.ENGLISH)
diff --git a/app/src/main/kotlin/info/appdev/chartexample/MultiLineChartActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/MultiLineChartActivity.kt
index 38b89d9fd..8338952ab 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/MultiLineChartActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/MultiLineChartActivity.kt
@@ -11,6 +11,7 @@ import android.widget.SeekBar
import android.widget.SeekBar.OnSeekBarChangeListener
import androidx.core.content.ContextCompat
import androidx.core.net.toUri
+import info.appdev.chartexample.DataTools.Companion.generateSineWaves
import info.appdev.chartexample.DataTools.Companion.getValues
import info.appdev.chartexample.databinding.ActivityLinechartBinding
import info.appdev.chartexample.notimportant.DemoBase
@@ -70,9 +71,9 @@ class MultiLineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartGestu
}
private val colors = intArrayOf(
- ColorTemplate.VORDIPLOM_COLORS[0],
- ColorTemplate.VORDIPLOM_COLORS[1],
- ColorTemplate.VORDIPLOM_COLORS[2]
+ ColorTemplate.VORDIPLOM_COLORS[2],
+ ColorTemplate.VORDIPLOM_COLORS[3],
+ ColorTemplate.VORDIPLOM_COLORS[0]
)
override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {
@@ -87,27 +88,30 @@ class MultiLineChartActivity : DemoBase(), OnSeekBarChangeListener, OnChartGestu
for (datasetNumber in 0..2) {
val values = ArrayList()
- val sampleValues = getValues(100)
+ val sampleValues = when (datasetNumber) {
+ 1 -> getValues(100).reversedArray()
+ 2 -> generateSineWaves(3, 30).toTypedArray()
+ else -> getValues(100)
+ }
for (i in 0..