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/v/AppDevNext/AndroidChart.svg)](https://jitpack.io/#AppDevNext/AndroidChart) -[![PullRequest](https://github.com/AppDevNext/AndroidChart/actions/workflows/Android-CI.yml/badge.svg)](https://github.com/AppDevNext/AndroidChart/actions/workflows/Android-CI.yml) +[![PullRequest](https://github.com/AppDevNext/AndroidChart/actions/workflows/pullrequest.yml/badge.svg)](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.. + val sampleEntries = if (sinus) + generateSineWaves(3, 30).mapIndexed { index, data -> val valueY = (data.toFloat() * range) + 50 Entry(timeOffset + index.toFloat() * 1000, valueY) - }.toMutableList() + } + else { + var previousEntry: Entry? = null + getSawtoothValues(14).mapIndexed { index, data -> + val valueY = data.toFloat() * 20 + val entry = previousEntry?.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) + } else + Entry(it.x + 1000, valueY) + } ?: run { + Entry(timeOffset + index.toFloat() * 1000, valueY) + } + previousEntry = entry + // Now you can use 'prev' which holds the previous Entry + entry + } + } + + 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}" + Timber.d(entryText) + } val set1: LineDataSet if (binding.chart1.lineData.dataSetCount > 0) { set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet - set1.entries = sampleEntries + set1.entries = sampleEntries.toMutableList() + if (sinus) + set1.lineMode = LineDataSet.Mode.LINEAR + else + set1.lineMode = LineDataSet.Mode.STEPPED binding.chart1.lineData.notifyDataChanged() binding.chart1.notifyDataSetChanged() } else { // create a dataset and give it a type - set1 = LineDataSet(sampleEntries, "DataSet 1") - + set1 = LineDataSet(sampleEntries.toMutableList(), "DataSet 1") + if (sinus) + set1.lineMode = LineDataSet.Mode.LINEAR + else + set1.lineMode = LineDataSet.Mode.STEPPED set1.axisDependency = YAxis.AxisDependency.LEFT set1.color = Color.rgb(255, 241, 46) set1.isDrawCircles = false @@ -133,11 +173,31 @@ class TimeLineActivity : DemoBase() { override fun onCreateOptionsMenu(menu: Menu?): Boolean { menuInflater.inflate(R.menu.only_github, menu) - menuItemMove = menu?.add("Move chart")?.apply { + menuItemsMove = menu?.add("Move chart")?.apply { + setOnMenuItemClickListener { menuItem -> + lifecycleScope.launch { + menuItem.isChecked = !menuItem.isChecked + moveChart(menuItem) + } + true + }.isCheckable = true + } + menuItemsMove = menu?.add("Move X-Axis")?.apply { + setOnMenuItemClickListener { menuItem -> + menuItem.isChecked = !menuItem.isChecked + lifecycleScope.launch { + moveXAxis(menuItem) + } + true + }.isCheckable = true + } + menuItemsMove = menu?.add("Show sinus data")?.apply { + this.isChecked = true setOnMenuItemClickListener { menuItem -> menuItem.isChecked = !menuItem.isChecked lifecycleScope.launch { - moveChart() + setData(60f, TIME_OFFSET, menuItem.isChecked) + binding.chart1.invalidate() } true }.isCheckable = true @@ -155,13 +215,32 @@ class TimeLineActivity : DemoBase() { add(first) } - private suspend fun moveChart() { + private suspend fun moveXAxis(menuItem: MenuItem) { + Timber.d("${menuItem.isChecked}") + withContext(Dispatchers.Default) { + while (menuItem.isChecked) { + withContext(Dispatchers.Main) { + binding.chart1.xAxis.apply { + this.axisMinimum += 1000f + this.axisMaximum += 1000f + binding.chart1.notifyDataSetChanged() + binding.chart1.invalidate() + } + Timber.d("X min=${binding.chart1.xAxis.axisMinimum}, max=${binding.chart1.xAxis.axisMaximum}") + } + delay(100) + } + } + } + + private suspend fun moveChart(menuItem: MenuItem) { + Timber.d("${menuItem.isChecked}") withContext(Dispatchers.Default) { - while (menuItemMove!!.isChecked) { + while (menuItem.isChecked) { withContext(Dispatchers.Main) { - binding.chart1.lineData.dataSets.get(0)?.let { set -> - (set as LineDataSet).entries.moveFirstToLast() - set.notifyDataChanged() + binding.chart1.lineData.dataSets[0].apply { + (this as LineDataSet).entries.moveFirstToLast() + this.notifyDataChanged() binding.chart1.lineData.notifyDataChanged() binding.chart1.notifyDataSetChanged() binding.chart1.invalidate() diff --git a/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarFullComposeActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarFullComposeActivity.kt new file mode 100644 index 000000000..414c1342a --- /dev/null +++ b/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarFullComposeActivity.kt @@ -0,0 +1,361 @@ +package info.appdev.chartexample.compose + +import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.material3.DropdownMenu +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Slider +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.dp +import androidx.core.content.res.ResourcesCompat +import info.appdev.chartexample.DataTools.Companion.getValues +import info.appdev.chartexample.R +import info.appdev.chartexample.notimportant.DemoBaseCompose +import info.appdev.charting.components.Legend +import info.appdev.charting.components.XAxis.XAxisPosition +import info.appdev.charting.compose.HorizontalBarChart +import info.appdev.charting.data.BarData +import info.appdev.charting.data.BarDataSet +import info.appdev.charting.data.BarEntry +import timber.log.Timber + +class HorizontalBarFullComposeActivity : DemoBaseCompose() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + MaterialTheme { + HorizontalBarChartScreen( + onSaveToGallery = { /* saveToGallery functionality needs chart bitmap */ }, + onViewGithub = { viewGithub() } + ) + } + } + } + + @OptIn(ExperimentalMaterial3Api::class) + @Composable + fun HorizontalBarChartScreen( + onSaveToGallery: () -> Unit, + onViewGithub: () -> Unit + ) { + // State management + var showValues by remember { mutableStateOf(true) } + var showIcons by remember { mutableStateOf(false) } + var highlightEnabled by remember { mutableStateOf(true) } + var pinchZoomEnabled by remember { mutableStateOf(true) } + var autoScaleMinMaxEnabled by remember { mutableStateOf(false) } + var barBordersEnabled by remember { mutableStateOf(false) } + var animationTrigger by remember { mutableStateOf(0) } + + var showMenu by remember { mutableStateOf(false) } + var seekBarXValue by remember { mutableFloatStateOf(12f) } + var seekBarYValue by remember { mutableFloatStateOf(50f) } + + Scaffold( + topBar = { + TopAppBar( + title = { Text("HorizontalBarFullCompose") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary + ), + actions = { + Box { + IconButton( + onClick = { showMenu = true }, + modifier = Modifier.testTag("menuButton") + ) { + Icon( + Icons.Default.MoreVert, + contentDescription = "Menu", + tint = MaterialTheme.colorScheme.onPrimary + ) + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + modifier = Modifier.testTag("dropdownMenu") + ) { + DropdownMenuItem( + text = { Text("View on GitHub") }, + onClick = { + showMenu = false + onViewGithub() + }, + modifier = Modifier.testTag("menuItem_View on GitHub") + ) + DropdownMenuItem( + text = { Text("Toggle Values") }, + onClick = { + showMenu = false + showValues = !showValues + }, + modifier = Modifier.testTag("menuItem_Toggle Values") + ) + DropdownMenuItem( + text = { Text("Toggle Icons") }, + onClick = { + showMenu = false + showIcons = !showIcons + }, + modifier = Modifier.testTag("menuItem_Toggle Icons") + ) + DropdownMenuItem( + text = { Text("Toggle Highlight") }, + onClick = { + showMenu = false + highlightEnabled = !highlightEnabled + }, + modifier = Modifier.testTag("menuItem_Toggle Highlight") + ) + DropdownMenuItem( + text = { Text("Toggle Pinch Zoom") }, + onClick = { + showMenu = false + pinchZoomEnabled = !pinchZoomEnabled + }, + modifier = Modifier.testTag("menuItem_Toggle Pinch Zoom") + ) + DropdownMenuItem( + text = { Text("Toggle Auto Scale MinMax") }, + onClick = { + showMenu = false + autoScaleMinMaxEnabled = !autoScaleMinMaxEnabled + }, + modifier = Modifier.testTag("menuItem_Toggle Auto Scale MinMax") + ) + DropdownMenuItem( + text = { Text("Toggle Bar Borders") }, + onClick = { + showMenu = false + barBordersEnabled = !barBordersEnabled + }, + modifier = Modifier.testTag("menuItem_Toggle Bar Borders") + ) + DropdownMenuItem( + text = { Text("Animate X") }, + onClick = { + showMenu = false + animationTrigger++ + }, + modifier = Modifier.testTag("menuItem_Animate X") + ) + DropdownMenuItem( + text = { Text("Animate Y") }, + onClick = { + showMenu = false + animationTrigger++ + }, + modifier = Modifier.testTag("menuItem_Animate Y") + ) + DropdownMenuItem( + text = { Text("Animate XY") }, + onClick = { + showMenu = false + animationTrigger++ + }, + modifier = Modifier.testTag("menuItem_Animate XY") + ) + DropdownMenuItem( + text = { Text("Save to Gallery") }, + onClick = { + showMenu = false + onSaveToGallery() + }, + modifier = Modifier.testTag("menuItem_Save to Gallery") + ) + } + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .background(Color.White) + ) { + // Chart - Using Compose HorizontalBarChart + val barData = remember( + seekBarXValue, + seekBarYValue, + showIcons, + barBordersEnabled, + showValues + ) { + createBarData( + seekBarXValue.toInt(), + seekBarYValue, + showIcons, + barBordersEnabled, + showValues + ) + } + + key(showIcons) { + HorizontalBarChart( + data = barData, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .testTag("horizontalBarChart_$showIcons"), + drawValueAboveBar = true, + drawBarShadow = false, + scaleEnabled = pinchZoomEnabled, + touchEnabled = true, + dragEnabled = true, + highlightFullBarEnabled = highlightEnabled, + animationDuration = if (animationTrigger > 0) 2500 else 0, + onValueSelected = { entry, _ -> + entry?.let { + Timber.d("Selected: x=${it.x}, y=${it.y}") + } + }, + xAxisConfig = { xAxis -> + xAxis.position = XAxisPosition.BOTTOM + xAxis.typeface = tfLight + xAxis.isDrawAxisLine = true + xAxis.isDrawGridLines = false + xAxis.granularity = 10f + }, + leftAxisConfig = { leftAxis -> + leftAxis.typeface = tfLight + leftAxis.isDrawAxisLine = true + leftAxis.isDrawGridLines = true + if (!autoScaleMinMaxEnabled) { + leftAxis.axisMinimum = 0f + } + }, + rightAxisConfig = { rightAxis -> + rightAxis.typeface = tfLight + rightAxis.isDrawAxisLine = true + rightAxis.isDrawGridLines = false + if (!autoScaleMinMaxEnabled) { + rightAxis.axisMinimum = 0f + } + }, + legend = { legend -> + legend.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + legend.horizontalAlignment = Legend.LegendHorizontalAlignment.LEFT + legend.orientation = Legend.LegendOrientation.HORIZONTAL + legend.setDrawInside(false) + legend.formSize = 8f + legend.xEntrySpace = 4f + } + ) + } + + // SeekBar X with label + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Slider( + value = seekBarXValue, + onValueChange = { newValue -> + seekBarXValue = newValue + }, + valueRange = 0f..100f, + modifier = Modifier.weight(1f) + ) + Text( + text = seekBarXValue.toInt().toString(), + modifier = Modifier.padding(start = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + } + + // SeekBar Y with label + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Slider( + value = seekBarYValue, + onValueChange = { newValue -> + seekBarYValue = newValue + }, + valueRange = 0f..100f, + modifier = Modifier.weight(1f) + ) + Text( + text = seekBarYValue.toInt().toString(), + modifier = Modifier.padding(start = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } + + private fun createBarData(count: Int, range: Float, showIcons: Boolean, barBordersEnabled: Boolean, showValues: Boolean): BarData { + val barWidth = 9f + val spaceForBar = 10f + val values = ArrayList() + val sampleValues = getValues(100) + + for (i in 0.. Unit, + onViewGithub: () -> Unit + ) { + var showMenu by remember { mutableStateOf(false) } + var seekBarXValue by remember { mutableFloatStateOf(20f) } + var seekBarYValue by remember { mutableFloatStateOf(100f) } + + // State for toggles + var showValues by remember { mutableStateOf(false) } + var pinchZoom by remember { mutableStateOf(false) } + var autoScaleMinMax by remember { mutableStateOf(false) } + var showFilled by remember { mutableStateOf(false) } + var showCircles by remember { mutableStateOf(true) } + var lineMode by remember { mutableStateOf(LineDataSet.Mode.LINEAR) } + + Scaffold( + topBar = { + TopAppBar( + title = { Text("MultiLineChartCompose") }, + colors = TopAppBarDefaults.topAppBarColors( + containerColor = MaterialTheme.colorScheme.primary, + titleContentColor = MaterialTheme.colorScheme.onPrimary + ), + actions = { + Box { + IconButton( + onClick = { showMenu = true }, + modifier = Modifier.testTag("menuButton") + ) { + Icon( + Icons.Default.MoreVert, + contentDescription = "Menu", + tint = MaterialTheme.colorScheme.onPrimary + ) + } + DropdownMenu( + expanded = showMenu, + onDismissRequest = { showMenu = false }, + modifier = Modifier.testTag("dropdownMenu") + ) { + DropdownMenuItem( + text = { Text("View on GitHub") }, + onClick = { + showMenu = false + onViewGithub() + }, + modifier = Modifier.testTag("menuItem_View on GitHub") + ) + DropdownMenuItem( + text = { Text("Toggle Values") }, + onClick = { + showMenu = false + showValues = !showValues + }, + modifier = Modifier.testTag("menuItem_Toggle Values") + ) + DropdownMenuItem( + text = { Text("Toggle Pinch Zoom") }, + onClick = { + showMenu = false + pinchZoom = !pinchZoom + }, + modifier = Modifier.testTag("menuItem_Toggle Pinch Zoom") + ) + DropdownMenuItem( + text = { Text("Toggle Auto Scale MinMax") }, + onClick = { + showMenu = false + autoScaleMinMax = !autoScaleMinMax + }, + modifier = Modifier.testTag("menuItem_Toggle Auto Scale MinMax") + ) + DropdownMenuItem( + text = { Text("Toggle Filled") }, + onClick = { + showMenu = false + showFilled = !showFilled + }, + modifier = Modifier.testTag("menuItem_Toggle Filled") + ) + DropdownMenuItem( + text = { Text("Toggle Circles") }, + onClick = { + showMenu = false + showCircles = !showCircles + }, + modifier = Modifier.testTag("menuItem_Toggle Circles") + ) + DropdownMenuItem( + text = { Text("Toggle Cubic") }, + onClick = { + showMenu = false + lineMode = when (lineMode) { + LineDataSet.Mode.CUBIC_BEZIER -> LineDataSet.Mode.LINEAR + else -> LineDataSet.Mode.CUBIC_BEZIER + } + }, + modifier = Modifier.testTag("menuItem_Toggle Cubic") + ) + DropdownMenuItem( + text = { Text("Toggle Stepped") }, + onClick = { + showMenu = false + lineMode = when (lineMode) { + LineDataSet.Mode.STEPPED -> LineDataSet.Mode.LINEAR + else -> LineDataSet.Mode.STEPPED + } + }, + modifier = Modifier.testTag("menuItem_Toggle Stepped") + ) + DropdownMenuItem( + text = { Text("Toggle Horizontal Cubic") }, + onClick = { + showMenu = false + lineMode = when (lineMode) { + LineDataSet.Mode.HORIZONTAL_BEZIER -> LineDataSet.Mode.LINEAR + else -> LineDataSet.Mode.HORIZONTAL_BEZIER + } + }, + modifier = Modifier.testTag("menuItem_Toggle Horizontal Cubic") + ) + DropdownMenuItem( + text = { Text("Save to Gallery") }, + onClick = { + showMenu = false + onSaveToGallery() + }, + modifier = Modifier.testTag("menuItem_Save to Gallery") + ) + } + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + .background(Color.White) + ) { + // Chart - Using Compose LineChart + val lineData = remember( + seekBarXValue, + seekBarYValue, + showValues, + showFilled, + showCircles, + lineMode + ) { + createLineData( + seekBarXValue.toInt(), + seekBarYValue, + showValues, + showFilled, + showCircles, + lineMode + ) + } + + LineChart( + data = lineData, + modifier = Modifier + .fillMaxWidth() + .weight(1f), + drawGridBackground = false, + pinchZoomEnabled = pinchZoom, + autoScaleMinMax = autoScaleMinMax, + animationDuration = 1500, + onValueSelected = { entry, highlight -> + entry?.let { + Timber.i("Value: ${it.y}, xIndex: ${it.x}, DataSet index: ${highlight?.dataSetIndex}") + } + }, + xAxisConfig = { xAxis -> + xAxis.isDrawAxisLine = false + xAxis.isDrawGridLines = false + }, + leftAxisConfig = { leftAxis -> + leftAxis.isEnabled = false + }, + rightAxisConfig = { rightAxis -> + rightAxis.isDrawAxisLine = false + rightAxis.isDrawGridLines = false + }, + legend = { legend -> + legend.verticalAlignment = Legend.LegendVerticalAlignment.TOP + legend.horizontalAlignment = Legend.LegendHorizontalAlignment.RIGHT + legend.orientation = Legend.LegendOrientation.VERTICAL + legend.setDrawInside(false) + } + ) + + // SeekBar X with label + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "X:", + modifier = Modifier.padding(end = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + Slider( + value = seekBarXValue, + onValueChange = { newValue -> + seekBarXValue = newValue + }, + valueRange = 0f..100f, + modifier = Modifier.weight(1f) + ) + Text( + text = seekBarXValue.toInt().toString(), + modifier = Modifier.padding(start = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + } + + // SeekBar Y with label + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = "Y:", + modifier = Modifier.padding(end = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + Slider( + value = seekBarYValue, + onValueChange = { newValue -> + seekBarYValue = newValue + }, + valueRange = 0f..200f, + modifier = Modifier.weight(1f) + ) + Text( + text = seekBarYValue.toInt().toString(), + modifier = Modifier.padding(start = 8.dp), + style = MaterialTheme.typography.bodyMedium + ) + } + } + } + } + + private fun createLineData( + progress: Int, + range: Float, + showValues: Boolean, + showFilled: Boolean, + showCircles: Boolean, + lineMode: LineDataSet.Mode + ): LineData { + val dataSets = ArrayList() + + for (datasetNumber in 0..2) { + val values = ArrayList() + val sampleValues = when (datasetNumber) { + 1 -> getValues(100).reversedArray() + 2 -> generateSineWaves(3, 30).toTypedArray() + else -> getValues(100) + } + + for (i in 0.. + val myIndex = dataset?.getEntryIndex(entry) + val nextEntry = myIndex?.let { + if (it < dataset.entryCount - 1) + dataset.getEntryForIndex(myIndex + 1) + else + 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) + } + + override var offset: PointF = PointF() + get() = PointF(-(width / 2).toFloat(), -height.toFloat()) +} diff --git a/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeAxisValueFormatter.kt b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeAxisValueFormatter.kt index d123887dd..761b17dd2 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeAxisValueFormatter.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeAxisValueFormatter.kt @@ -10,7 +10,7 @@ class UnixTimeAxisValueFormatter(val format: String = "yyyy-MM-dd'T'HH:mm:ss'Z'" val simpleDateFormat = SimpleDateFormat(format, Locale.getDefault()) override fun getFormattedValue(value: Float, axis: AxisBase?): String { - return simpleDateFormat.format(value * 1000L) + return simpleDateFormat.format(value.toLong()) } } diff --git a/app/src/main/kotlin/info/appdev/chartexample/fragments/ViewPagerSimpleChartDemo.kt b/app/src/main/kotlin/info/appdev/chartexample/fragments/ViewPagerSimpleChartDemo.kt index 954bdbbf5..aac310086 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/fragments/ViewPagerSimpleChartDemo.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/fragments/ViewPagerSimpleChartDemo.kt @@ -11,8 +11,7 @@ import android.widget.Toast import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.net.toUri import androidx.fragment.app.Fragment -import androidx.fragment.app.FragmentManager -import androidx.fragment.app.FragmentPagerAdapter +import androidx.viewpager2.adapter.FragmentStateAdapter import com.google.android.material.snackbar.Snackbar import info.appdev.chartexample.R import info.appdev.chartexample.databinding.ActivityAwesomedesignBinding @@ -31,25 +30,24 @@ class ViewPagerSimpleChartDemo : DemoBase() { setContentView(binding.root) binding.pager.offscreenPageLimit = 3 - binding.pager.adapter = PageAdapter(supportFragmentManager) + binding.pager.adapter = PageAdapter(this) showSnackbar("Swipe left and right for more awesome design examples!") } - private class PageAdapter(fm: FragmentManager?) : FragmentPagerAdapter(fm!!) { - override fun getItem(pos: Int): Fragment { - var f: Fragment? = null - when (pos) { - 0 -> f = SineCosineFragment.newInstance() - 1 -> f = ComplexityFragment.newInstance() - 2 -> f = BarChartFrag.newInstance() - 3 -> f = ScatterChartFrag.newInstance() - 4 -> f = PieChartFrag.newInstance() + private class PageAdapter(activity: ViewPagerSimpleChartDemo) : FragmentStateAdapter(activity) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> SineCosineFragment.newInstance() + 1 -> ComplexityFragment.newInstance() + 2 -> BarChartFrag.newInstance() + 3 -> ScatterChartFrag.newInstance() + 4 -> PieChartFrag.newInstance() + else -> throw IllegalArgumentException("Invalid position: $position") } - return f!! } - override fun getCount(): Int { + override fun getItemCount(): Int { return 5 } } 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 930721fa9..35572d42a 100644 --- a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt +++ b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt @@ -77,9 +77,12 @@ import info.appdev.chartexample.StackedBarActivity import info.appdev.chartexample.StackedBarActivityNegative import info.appdev.chartexample.TimeLineActivity 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.charting.utils.Utils import info.appdev.charting.utils.initUtils +import kotlin.jvm.java class MainActivity : ComponentActivity() { @@ -208,8 +211,10 @@ class MainActivity : ComponentActivity() { add(ContentItem("Hourly", "Uses the current time to add a data point for each hour.", LineChartTimeActivity::class.java)) //add(new ContentItem("Realm.io Examples", "See more examples that use Realm.io mobile database.")); - add(ContentItem("Compose Horizontal")) - add(ComposeItem("Horizontal", "Render bar chart horizontally.", HorizontalBarComposeActivity::class.java).toDemoBase()) + add(ContentItem("Compose")) + add(ComposeItem("HorizontalCompose", "Render bar chart horizontally compose", HorizontalBarComposeActivity::class.java).toDemoBase()) + add(ComposeItem("HorizontalFullCompose", "Render bar chart horizontally full compose.", HorizontalBarFullComposeActivity::class.java).toDemoBase()) + add(ComposeItem("MultiLineCompose", "Show multiple data sets in compose.", MultiLineComposeActivity::class.java).toDemoBase()) add(ContentItem("Demonstrate and fix issues")) add(ContentItem("Gradient", "Show a gradient edge case", GradientActivity::class.java)) diff --git a/app/src/main/res/layout/activity_awesomedesign.xml b/app/src/main/res/layout/activity_awesomedesign.xml index 3add0e955..f6d69aee7 100644 --- a/app/src/main/res/layout/activity_awesomedesign.xml +++ b/app/src/main/res/layout/activity_awesomedesign.xml @@ -4,7 +4,7 @@ android:layout_height="match_parent" android:orientation="vertical"> - diff --git a/build.gradle.kts b/build.gradle.kts index 68fc188dd..2453050cf 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,9 +4,9 @@ buildscript { mavenCentral() } dependencies { - classpath("com.android.tools.build:gradle:8.13.2") + 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.0") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.10") } } diff --git a/buildSrc b/buildSrc index d748fbfe6..abb98b144 160000 --- a/buildSrc +++ b/buildSrc @@ -1 +1 @@ -Subproject commit d748fbfe6b6a4c1ea0abaaad9e5a0644049a84ab +Subproject commit abb98b1449cfa86f169cc6d6eb2f4ed9769545df diff --git a/chartLib/build.gradle.kts b/chartLib/build.gradle.kts index 9b71039ae..68b3f1923 100644 --- a/chartLib/build.gradle.kts +++ b/chartLib/build.gradle.kts @@ -1,11 +1,11 @@ import info.git.versionHelper.getVersionText +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import java.net.URI plugins { id("com.android.library") id("maven-publish") - id("kotlin-android") - id("com.vanniktech.maven.publish") version "0.34.0" + id("com.vanniktech.maven.publish") version "0.36.0" } android { @@ -24,6 +24,11 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } + kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_17 + } + } buildTypes { release { isMinifyEnabled = false @@ -39,11 +44,11 @@ android { dependencies { implementation("androidx.annotation:annotation:1.9.1") - implementation("androidx.core:core:1.17.0") - implementation("androidx.activity:activity-ktx:1.12.2") + implementation("androidx.core:core:1.18.0") + implementation("androidx.activity:activity-ktx:1.13.0") implementation("com.github.AppDevNext.Logcat:LogcatCoreLib:3.4") testImplementation("junit:junit:4.13.2") - testImplementation("org.mockito:mockito-core:5.21.0") + testImplementation("org.mockito:mockito-core:5.23.0") testImplementation("org.mockito:mockito-inline:5.2.0") } diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/BarChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/BarChart.kt deleted file mode 100644 index c534e929b..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/BarChart.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.Log - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -open class BarChart : info.appdev.charting.charts.BarChart { - @Deprecated("Use same class from package info.appdev.charting.charts instead") - constructor(context: Context?) : super(context) - - @Deprecated("Use same class from package info.appdev.charting.charts instead") - constructor(context: Context?, attrs: android.util.AttributeSet?) : super(context, attrs) - - @Deprecated("Use same class from package info.appdev.charting.charts instead") - constructor(context: Context?, attrs: android.util.AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - - override fun init() { - super.init() - Log.e("BarChart", "This class is deprecated. Please use info.appdev.charting.charts.BarChart instead.") - } -} diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/BubbleChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/BubbleChart.kt deleted file mode 100644 index 8eb60baff..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/BubbleChart.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.AttributeSet -import android.util.Log -import info.appdev.charting.charts.BubbleChart - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -class BubbleChart : BubbleChart { - constructor(context: Context?) : super(context) - - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - Log.e("BubbleChart", "This class is deprecated. Please use info.appdev.charting.charts.BubbleChart instead.") - } -} diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/CandleStickChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/CandleStickChart.kt deleted file mode 100644 index 3ea2d8891..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/CandleStickChart.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.AttributeSet -import android.util.Log -import info.appdev.charting.charts.CandleStickChart - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -class CandleStickChart : CandleStickChart { - constructor(context: Context?) : super(context) - - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - Log.e("CandleStickChart", "This class is deprecated. Please use info.appdev.charting.charts.CandleStickChart instead.") - } -} diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/CombinedChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/CombinedChart.kt deleted file mode 100644 index ca31dc98c..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/CombinedChart.kt +++ /dev/null @@ -1,23 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.AttributeSet -import android.util.Log -import info.appdev.charting.charts.CombinedChart - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -open class CombinedChart : CombinedChart { - @Deprecated("Use same class from package info.appdev.charting.charts instead") - constructor(context: Context?) : super(context) - - @Deprecated("Use same class from package info.appdev.charting.charts instead") - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - - @Deprecated("Use same class from package info.appdev.charting.charts instead") - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - Log.e("CombinedChart", "This class is deprecated. Please use info.appdev.charting.charts.CombinedChart instead.") - } -} diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/HorizontalBarChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/HorizontalBarChart.kt deleted file mode 100644 index 983432f4a..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/HorizontalBarChart.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.AttributeSet -import android.util.Log -import info.appdev.charting.charts.HorizontalBarChart - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -open class HorizontalBarChart : HorizontalBarChart { - constructor(context: Context?) : super(context) - - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - Log.e("HorizontalBarChart", "This class is deprecated. Please use info.appdev.charting.charts.HorizontalBarChart instead.") - } -} diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/LineChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/LineChart.kt deleted file mode 100644 index 76037a156..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/LineChart.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.AttributeSet -import android.util.Log -import info.appdev.charting.charts.LineChart - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -open class LineChart : LineChart { - constructor(context: Context?) : super(context) - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - Log.e("LineChart", "This class is deprecated. Please use info.appdev.charting.charts.LineChart instead.") - } -} diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/PieChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/PieChart.kt deleted file mode 100644 index 119941bec..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/PieChart.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.AttributeSet -import android.util.Log -import info.appdev.charting.charts.PieChart - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -class PieChart : PieChart { - constructor(context: Context?) : super(context) - - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - Log.e("PieChart", "This class is deprecated. Please use info.appdev.charting.charts.PieChart instead.") - } -} diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/RadarChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/RadarChart.kt deleted file mode 100644 index 133020416..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/RadarChart.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.AttributeSet -import android.util.Log -import info.appdev.charting.charts.RadarChart - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -open class RadarChart : RadarChart { - constructor(context: Context?) : super(context) - - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - Log.e("RadarChart", "This class is deprecated. Please use info.appdev.charting.charts.RadarChart instead.") - } -} - diff --git a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/ScatterChart.kt b/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/ScatterChart.kt deleted file mode 100644 index 8a8247ede..000000000 --- a/chartLib/src/main/kotlin/com/github/mikephil/charting/charts/ScatterChart.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.github.mikephil.charting.charts - -import android.content.Context -import android.util.AttributeSet -import android.util.Log -import info.appdev.charting.charts.ScatterChart - -@Deprecated("Use same class from package info.appdev.charting.charts instead") -class ScatterChart : ScatterChart { - constructor(context: Context?) : super(context) - - constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) - - constructor(context: Context?, attrs: AttributeSet?, defStyle: Int) : super(context, attrs, defStyle) - - override fun init() { - super.init() - Log.e("ScatterChart", "This class is deprecated. Please use info.appdev.charting.charts.ScatterChart instead.") - } -} 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 155c975ee..a4eab0f84 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/BarLineChartBase.kt @@ -491,7 +491,7 @@ abstract class BarLineChartBase? { - val h = getHighlightByTouchPoint(x, y) - if (h != null) { - return mData!!.getDataSetByIndex(h.dataSetIndex) + val highlight = getHighlightByTouchPoint(x, y) + if (highlight != null) { + return mData!!.getDataSetByIndex(highlight.dataSetIndex) } return null } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/Chart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/Chart.kt index 88c08fc3b..eddb09ec6 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/Chart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/Chart.kt @@ -549,15 +549,14 @@ abstract class Chart>> : ViewGroup, IBaseP setLastHighlighted(this.highlighted) - if (callListener && mSelectionListener != null) { + mSelectionListener?.let { listener -> if (!valuesToHighlight()) { - mSelectionListener!!.onNothingSelected() + listener.onNothingSelected() } else { // notify the listener - mSelectionListener!!.onValueSelected(entry!!, high!!) + listener.onValueSelected(entry!!, high!!) } } - // redraw the chart invalidate() } @@ -618,14 +617,14 @@ abstract class Chart>> : ViewGroup, IBaseP continue } - val e = mData!!.getEntryForHighlight(highlight) ?: continue + val entry = mData!!.getEntryForHighlight(highlight) ?: continue // make sure entry not null before using it // Cast to non-star-projected type to allow calling getEntryIndex @Suppress("UNCHECKED_CAST") val set = dataset as IDataSet - val entryIndex = set.getEntryIndex(e) + val entryIndex = set.getEntryIndex(entry) if (entryIndex > set.entryCount * mAnimator.phaseX) { continue @@ -642,7 +641,7 @@ abstract class Chart>> : ViewGroup, IBaseP if (!marker.isEmpty()) { val markerIndex = i % marker.size val markerItem = marker[markerIndex] - markerItem.refreshContent(e, highlight) + markerItem.refreshContent(entry, highlight) // draw the marker markerItem.draw(canvas, pos[0], pos[1]) @@ -651,8 +650,7 @@ abstract class Chart>> : ViewGroup, IBaseP } /** - * Returns the actual position in pixels of the MarkerView for the given - * Highlight object. + * Returns the actual position in pixels of the MarkerView for the given Highlight object. */ protected open fun getMarkerPosition(high: Highlight): FloatArray { return floatArrayOf(high.drawX, high.drawY) @@ -807,14 +805,12 @@ abstract class Chart>> : ViewGroup, IBaseP get() = mXAxis.axisRange /** - * Returns a recyclable PointF instance. * Returns the center point of the chart (the whole View) in pixels. */ val center: PointF get() = getInstance(width / 2f, height / 2f) /** - * Returns a recyclable PointF instance. * Returns the center of the chart taking offsets under consideration. * (returns the center of the content rectangle) */ @@ -822,8 +818,7 @@ abstract class Chart>> : ViewGroup, IBaseP get() = viewPortHandler.contentCenter /** - * Sets extra offsets (around the chart view) to be appended to the - * auto-calculated offsets. + * Sets extra offsets (around the chart view) to be appended to the auto-calculated offsets. */ fun setExtraOffsets(left: Float, top: Float, right: Float, bottom: Float) { this.extraLeftOffset = left @@ -869,8 +864,7 @@ abstract class Chart>> : ViewGroup, IBaseP } /** - * Sets the text that informs the user that there is no data available with - * which to draw the chart. + * Sets the text that informs the user that there is no data available with which to draw the chart. */ fun setNoDataText(text: String) { mNoDataText = text 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 369fad84f..2ed51233d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/HorizontalBarChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/HorizontalBarChart.kt @@ -143,7 +143,7 @@ open class HorizontalBarChart : BarChart { offsetBottom += mAxisRight.getRequiredHeightSpace(axisRendererRight.paintAxisLabels) } - val xLabelWidth = mXAxis.mLabelWidth.toFloat() + val xLabelWidth = mXAxis.labelWidth.toFloat() if (mXAxis.isEnabled) { // offsets for x-labels diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/LineChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/LineChart.kt index 86e53df5a..db7e4a218 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/LineChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/LineChart.kt @@ -5,7 +5,6 @@ import android.util.AttributeSet import info.appdev.charting.data.LineData import info.appdev.charting.interfaces.dataprovider.LineDataProvider import info.appdev.charting.renderer.LineChartRenderer -import java.util.Locale open class LineChart : BarLineChartBase, LineDataProvider { @@ -39,9 +38,6 @@ open class LineChart : BarLineChartBase, LineDataProvider { override val accessibilityDescription: String get() { - val lineData = lineData - val numberOfPoints = lineData.entryCount - // Min and max values... val yAxisValueFormatter = axisLeft.valueFormatter val minVal = yAxisValueFormatter?.getFormattedValue(lineData.yMin, null) @@ -51,13 +47,10 @@ open class LineChart : BarLineChartBase, LineDataProvider { val xAxisValueFormatter = xAxis.valueFormatter val minRange = xAxisValueFormatter?.getFormattedValue(lineData.xMin, null) val maxRange = xAxisValueFormatter?.getFormattedValue(lineData.xMax, null) - val entries = if (numberOfPoints == 1) "entry" else "entries" - return String.format( - Locale.getDefault(), "The line chart has %d %s. " + - "The minimum value is %s and maximum value is %s." + - "Data ranges from %s to %s.", - numberOfPoints, entries, minVal, maxVal, minRange, maxRange - ) + val pluralOrSingular = if (lineData.entryCount == 1) "entry" else "entries" + return "The line chart has ${lineData.entryCount} $pluralOrSingular. " + + "The minimum value is $minVal and maximum value is $maxVal." + + "Data ranges from $minRange to $maxRange." } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/PieChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/PieChart.kt index 2fa64bb5f..ae8a3fe0c 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/PieChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/PieChart.kt @@ -339,7 +339,6 @@ open class PieChart : PieRadarChartBase { override fun getIndexForAngle(angle: Float): Int { // take the current angle of the chart into consideration - val a = (angle - rotationAngle).getNormalizedAngle() for (i in absoluteAngles.indices) { 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 0ba8785fd..76b043d60 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/PieRadarChartBase.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/PieRadarChartBase.kt @@ -263,7 +263,7 @@ abstract class PieRadarChartBase>> val x = this.xAxis if (x.isEnabled && x.isDrawLabelsEnabled) { - minOffset = max(minOffset, x.mLabelWidth.toFloat()) + minOffset = max(minOffset, x.labelWidth.toFloat()) } } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/charts/RadarChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/charts/RadarChart.kt index c8335441a..aa8e60cee 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/charts/RadarChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/charts/RadarChart.kt @@ -274,7 +274,7 @@ open class RadarChart : PieRadarChartBase { } override val requiredBaseOffset: Float - get() = if (mXAxis.isEnabled && mXAxis.isDrawLabelsEnabled) mXAxis.mLabelWidth.toFloat() else 10f.convertDpToPixel() + get() = if (mXAxis.isEnabled && mXAxis.isDrawLabelsEnabled) mXAxis.labelWidth.toFloat() else 10f.convertDpToPixel() override val radius: Float get() { diff --git a/chartLib/src/main/kotlin/info/appdev/charting/components/AxisBase.kt b/chartLib/src/main/kotlin/info/appdev/charting/components/AxisBase.kt index d2f8fbe1e..19e7f2b1d 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/components/AxisBase.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/components/AxisBase.kt @@ -17,7 +17,7 @@ abstract class AxisBase : ComponentBase() { /** * custom formatter that is used instead of the auto-formatter if set */ - protected var mAxisValueFormatter: IAxisValueFormatter? = null + protected var axisValueFormatter: IAxisValueFormatter? = null /** * The color of the grid lines for this axis (the horizontal lines @@ -96,7 +96,11 @@ abstract class AxisBase : ComponentBase() { var isDrawLabelsEnabled = true protected set - protected var mCenterAxisLabels: Boolean = false + /** + * Centers the axis labels instead of drawing them at their original position. + * This is useful especially for grouped BarChart. + */ + var centerAxisLabels: Boolean = false /** * the path effect of the axis line that makes dashed lines possible @@ -212,16 +216,8 @@ abstract class AxisBase : ComponentBase() { this.limitRanges = ArrayList() } - /** - * Centers the axis labels instead of drawing them at their original position. - * This is useful especially for grouped BarChart. - */ - fun setCenterAxisLabels(enabled: Boolean) { - mCenterAxisLabels = enabled - } - val isCenterAxisLabelsEnabled: Boolean - get() = mCenterAxisLabels && entryCount > 0 + get() = centerAxisLabels && entryCount > 0 /** * The width of the border surrounding the chart in dp. @@ -401,16 +397,18 @@ abstract class AxisBase : ComponentBase() { */ var valueFormatter: IAxisValueFormatter? get() { - if (mAxisValueFormatter == null || (mAxisValueFormatter is DefaultAxisValueFormatter && - (mAxisValueFormatter as DefaultAxisValueFormatter).decimalDigits != decimals) + if (axisValueFormatter == null || + (axisValueFormatter is DefaultAxisValueFormatter && (axisValueFormatter as DefaultAxisValueFormatter).decimalDigits != decimals) ) - mAxisValueFormatter = DefaultAxisValueFormatter(decimals) + axisValueFormatter = DefaultAxisValueFormatter(decimals) - return mAxisValueFormatter + return axisValueFormatter } set(f) { - if (f == null) mAxisValueFormatter = DefaultAxisValueFormatter(decimals) - else mAxisValueFormatter = f + if (f == null) + axisValueFormatter = DefaultAxisValueFormatter(decimals) + else + axisValueFormatter = f } /** @@ -518,8 +516,7 @@ abstract class AxisBase : ComponentBase() { /** * By calling this method, any custom maximum value that has been previously set is reseted, - * and the calculation is - * done automatically. + * and the calculation is done automatically. */ fun resetAxisMaximum() { this.isAxisMaxCustom = false @@ -527,8 +524,7 @@ abstract class AxisBase : ComponentBase() { /** * By calling this method, any custom minimum value that has been previously set is reseted, - * and the calculation is - * done automatically. + * and the calculation is done automatically. */ fun resetAxisMinimum() { this.isAxisMinCustom = false 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 4960c5d4c..7592d71e4 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerView.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/components/MarkerView.kt @@ -19,12 +19,6 @@ open class MarkerView(context: Context?, layoutResource: Int) : RelativeLayout(c private val mOffset2 = PointF() private var mWeakChart: WeakReference?>? = null - /** - * Constructor. Sets up the MarkerView with a custom layout resource. - * - * @param context - * @param layoutResource the layout resource to use for the MarkerView - */ init { setupLayoutResource(layoutResource) } @@ -40,7 +34,6 @@ open class MarkerView(context: Context?, layoutResource: Int) : RelativeLayout(c inflated.layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT) inflated.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)) - // measure(getWidth(), getHeight()); inflated.layout(0, 0, inflated.measuredWidth, inflated.measuredHeight) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/components/XAxis.kt b/chartLib/src/main/kotlin/info/appdev/charting/components/XAxis.kt index cb93037a3..73e5d23d2 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/components/XAxis.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/components/XAxis.kt @@ -12,20 +12,19 @@ class XAxis : AxisBase() { * width of the x-axis labels in pixels - this is automatically * calculated by the computeSize() methods in the renderers */ - var mLabelWidth: Int = 1 + var labelWidth: Int = 1 /** * height of the x-axis labels in pixels - this is automatically * calculated by the computeSize() methods in the renderers */ - var mLabelHeight: Int = 1 + var labelHeight: Int = 1 /** * This is the angle for drawing the X axis labels (in degrees) */ var labelRotationAngle: Float = 0f - /** * if set to true, the chart will avoid that the first and last label entry * in the chart "clip" off the edge of the chart 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 b285d89dc..4706bc2de 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/data/Entry.kt @@ -126,7 +126,12 @@ open class Entry : BaseEntry, Parcelable, Serializable { this._x = `in`.readFloat() this.yBase = `in`.readFloat() if (`in`.readInt() == 1) { - this.data = `in`.readParcelable(Any::class.java.classLoader) + this.data = if (android.os.Build.VERSION.SDK_INT >= 33) { + `in`.readParcelable(Any::class.java.classLoader, Any::class.java) + } else { + @Suppress("DEPRECATION") + `in`.readParcelable(Any::class.java.classLoader) + } } } 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 52684d5e8..786af91c6 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/listener/BarLineChartTouchListener.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/listener/BarLineChartTouchListener.kt @@ -323,9 +323,8 @@ class BarLineChartTouchListener( if (totalDist > minScalePointerDistance) { // get the translation - - val t = getTrans(touchPointCenter.x, touchPointCenter.y) - val h = chart.viewPortHandler + val pointF = getTrans(touchPointCenter.x, touchPointCenter.y) + val viewPortHandler = chart.viewPortHandler // take actions depending on the activated touch mode if (touchMode == PINCH_ZOOM) { @@ -335,16 +334,16 @@ class BarLineChartTouchListener( val isZoomingOut = (scale < 1) - val canZoomMoreX = if (isZoomingOut) h.canZoomOutMoreX() else h.canZoomInMoreX() + val canZoomMoreX = if (isZoomingOut) viewPortHandler.canZoomOutMoreX() else viewPortHandler.canZoomInMoreX() - val canZoomMoreY = if (isZoomingOut) h.canZoomOutMoreY() else h.canZoomInMoreY() + val canZoomMoreY = if (isZoomingOut) viewPortHandler.canZoomOutMoreY() else viewPortHandler.canZoomInMoreY() val scaleX = if (chart.isScaleXEnabled) scale else 1f val scaleY = if (chart.isScaleYEnabled) scale else 1f if (canZoomMoreY || canZoomMoreX) { matrix.set(savedMatrix) - matrix.postScale(getLimitedScaleX(scaleX, t), getLimitedScaleY(scaleY, t), t.x, t.y) + matrix.postScale(getLimitedScaleX(scaleX, pointF), getLimitedScaleY(scaleY, pointF), pointF.x, pointF.y) l?.onChartScale(event, scaleX, scaleY) } @@ -355,11 +354,11 @@ class BarLineChartTouchListener( val scaleX = xDist / savedXDist // x-axis scale val isZoomingOut = (scaleX < 1) - val canZoomMoreX = if (isZoomingOut) h.canZoomOutMoreX() else h.canZoomInMoreX() + val canZoomMoreX = if (isZoomingOut) viewPortHandler.canZoomOutMoreX() else viewPortHandler.canZoomInMoreX() if (canZoomMoreX) { matrix.set(savedMatrix) - matrix.postScale(getLimitedScaleX(scaleX, t), 1f, t.x, t.y) + matrix.postScale(getLimitedScaleX(scaleX, pointF), 1f, pointF.x, pointF.y) l?.onChartScale(event, scaleX, 1f) } @@ -370,17 +369,17 @@ class BarLineChartTouchListener( val scaleY = yDist / savedYDist // y-axis scale val isZoomingOut = (scaleY < 1) - val canZoomMoreY = if (isZoomingOut) h.canZoomOutMoreY() else h.canZoomInMoreY() + val canZoomMoreY = if (isZoomingOut) viewPortHandler.canZoomOutMoreY() else viewPortHandler.canZoomInMoreY() if (canZoomMoreY) { matrix.set(savedMatrix) - matrix.postScale(1f, getLimitedScaleY(scaleY, t), t.x, t.y) + matrix.postScale(1f, getLimitedScaleY(scaleY, pointF), pointF.x, pointF.y) l?.onChartScale(event, 1f, scaleY) } } - PointF.recycleInstance(t) + PointF.recycleInstance(pointF) } } } @@ -389,7 +388,7 @@ class BarLineChartTouchListener( * limit scaleX range */ private fun getLimitedScaleX(scaleX: Float, t: PointF): Float { - val h = chart.viewPortHandler + val viewPortHandler = chart.viewPortHandler tempMatrix.set(savedMatrix) tempMatrix.postScale(scaleX, 1f, t.x, t.y) @@ -401,10 +400,10 @@ class BarLineChartTouchListener( var resultScaleX = scaleX - if (calScaleX < h.minScaleX) { - resultScaleX = h.minScaleX / lastScaleX - } else if (calScaleX > h.maxScaleX) { - resultScaleX = h.maxScaleX / lastScaleX + if (calScaleX < viewPortHandler.minScaleX) { + resultScaleX = viewPortHandler.minScaleX / lastScaleX + } else if (calScaleX > viewPortHandler.maxScaleX) { + resultScaleX = viewPortHandler.maxScaleX / lastScaleX } return resultScaleX } @@ -413,7 +412,7 @@ class BarLineChartTouchListener( * limit scaleY range */ private fun getLimitedScaleY(scaleY: Float, t: PointF): Float { - val h = chart.viewPortHandler + val viewPortHandler = chart.viewPortHandler tempMatrix.set(savedMatrix) tempMatrix.postScale(1f, scaleY, t.x, t.y) @@ -425,10 +424,10 @@ class BarLineChartTouchListener( var resultScaleY = scaleY - if (calScaleY < h.minScaleY) { - resultScaleY = h.minScaleY / lastScaleY - } else if (calScaleY > h.maxScaleY) { - resultScaleY = h.maxScaleY / lastScaleY + if (calScaleY < viewPortHandler.minScaleY) { + resultScaleY = viewPortHandler.minScaleY / lastScaleY + } else if (calScaleY > viewPortHandler.maxScaleY) { + resultScaleY = viewPortHandler.maxScaleY / lastScaleY } return resultScaleY } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/PieChartRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/PieChartRenderer.kt index 870936b23..c64c60894 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/PieChartRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/PieChartRenderer.kt @@ -551,7 +551,14 @@ open class PieChartRenderer( // draw everything, depending on settings if (drawXInside && drawYInside) { drawValue( - this, formatter, value, entry, 0, x, y, dataSet.getValueTextColor(j) + canvas = this, + formatter = formatter, + value = value, + entry = entry, + dataSetIndex = 0, + x = x, + y = y, + color = dataSet.getValueTextColor(j) ) if (j < data.entryCount && entryLabel != null) { @@ -561,7 +568,7 @@ open class PieChartRenderer( if (j < data.entryCount && entryLabel != null) { drawEntryLabel(this, entryLabel, x, y + lineHeight / 2f) } - } else if (drawYInside) { + } else { drawValue(this, formatter, value, entry, 0, x, y + lineHeight / 2f, dataSet.getValueTextColor(j)) } } @@ -681,9 +688,27 @@ open class PieChartRenderer( val width = centerTextLastBounds.width() // If width is 0, it will crash. Always have a minimum of 1 - centerTextLayout = StaticLayout( - centerText, 0, centerText.length, paintCenterText, max(ceil(width.toDouble()), 1.0).toInt(), Layout.Alignment.ALIGN_CENTER, 1f, 0f, false - ) +// centerTextLayout = StaticLayout( +// centerText, +// 0, +// centerText.length, +// paintCenterText, +// max(ceil(width.toDouble()), 1.0).toInt(), +// Layout.Alignment.ALIGN_CENTER, +// 1f, +// 0f, +// false +// ) + centerTextLayout = StaticLayout.Builder.obtain( + centerText, + 0, + centerText.length, + paintCenterText, + max(ceil(width.toDouble()), 1.0).toInt() + ).setAlignment(Layout.Alignment.ALIGN_CENTER) + .setLineSpacing(0f, 1f) + .setIncludePad(false) + .build() } val layoutHeight = centerTextLayout!!.height.toFloat() @@ -695,9 +720,7 @@ open class PieChartRenderer( translate(boundingRect.left, boundingRect.top + (boundingRect.height() - layoutHeight) / 2f) centerTextLayout!!.draw(this) - } - PointF.recycleInstance(center) PointF.recycleInstance(offset) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRenderer.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRenderer.kt index 0f4e10662..dd93481c9 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRenderer.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRenderer.kt @@ -84,8 +84,8 @@ open class XAxisRenderer( xAxis.labelRotationAngle ) - xAxis.mLabelWidth = labelRotatedSize.width.roundToInt() - xAxis.mLabelHeight = labelRotatedSize.height.roundToInt() + xAxis.labelWidth = labelRotatedSize.width.roundToInt() + xAxis.labelHeight = labelRotatedSize.height.roundToInt() FSize.recycleInstance(labelRotatedSize) FSize.recycleInstance(labelSize) @@ -110,7 +110,7 @@ open class XAxisRenderer( XAxisPosition.TOP_INSIDE -> { pointF.x = 0.5f pointF.y = 1.0f - drawLabels(canvas, viewPortHandler.contentTop() + yOffset + xAxis.mLabelHeight, pointF) + drawLabels(canvas, viewPortHandler.contentTop() + yOffset + xAxis.labelHeight, pointF) } XAxisPosition.BOTTOM -> { @@ -122,7 +122,7 @@ open class XAxisRenderer( XAxisPosition.BOTTOM_INSIDE -> { pointF.x = 0.5f pointF.y = 0.0f - drawLabels(canvas, viewPortHandler.contentBottom() - yOffset - xAxis.mLabelHeight, pointF) + drawLabels(canvas, viewPortHandler.contentBottom() - yOffset - xAxis.labelHeight, pointF) } else -> { // BOTH SIDED diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRendererHorizontalBarChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRendererHorizontalBarChart.kt index 781dd86cf..02edf3666 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRendererHorizontalBarChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRendererHorizontalBarChart.kt @@ -71,8 +71,8 @@ open class XAxisRendererHorizontalBarChart( xAxis.labelRotationAngle ) - xAxis.mLabelWidth = labelRotatedSize.width.roundToInt() - xAxis.mLabelHeight = labelRotatedSize.height.roundToInt() + xAxis.labelWidth = labelRotatedSize.width.roundToInt() + xAxis.labelHeight = labelRotatedSize.height.roundToInt() FSize.recycleInstance(labelRotatedSize) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRendererRadarChart.kt b/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRendererRadarChart.kt index 9f0acfc18..853a0f296 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRendererRadarChart.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/renderer/XAxisRendererRadarChart.kt @@ -37,10 +37,10 @@ class XAxisRendererRadarChart( val angle = (sliceAngle * i + chart.rotationAngle) % 360f - pOut = center.getPosition(chart.yRange * factor + xAxis.mLabelWidth / 2f, angle) + pOut = center.getPosition(chart.yRange * factor + xAxis.labelWidth / 2f, angle) drawLabel( - canvas, label, pOut.x, pOut.y - xAxis.mLabelHeight / 2f, + canvas, label, pOut.x, pOut.y - xAxis.labelHeight / 2f, drawLabelAnchor, labelRotationAngleDegrees ) } diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/CanvasUtils.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/CanvasUtils.kt index 8c4dd8011..02a888efb 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/CanvasUtils.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/CanvasUtils.kt @@ -15,8 +15,7 @@ private val drawableBoundsCache = Rect() /** * Utilities class that has some helper methods. Needs to be initialized by * calling Utils.init(...) before usage. Inside the Chart.init() method, this is - * done, if the Utils are used before that, Utils.init(...) needs to be called - * manually. + * done, if the Utils are used before that, Utils.init(...) needs to be called manually. */ fun Canvas.drawImage( drawable: Drawable, @@ -81,9 +80,9 @@ fun Canvas.drawXAxisValue( // Move the "outer" rect relative to the anchor, assuming its centered if (anchor.x != 0.5f || anchor.y != 0.5f) { val rotatedSize = getSizeOfRotatedRectangleByDegrees( - mDrawTextRectBuffer.width().toFloat(), - lineHeight, - angleDegrees + rectangleWidth = mDrawTextRectBuffer.width().toFloat(), + rectangleHeight = lineHeight, + degrees = angleDegrees ) translateX -= rotatedSize.width * (anchor.x - 0.5f) diff --git a/chartLib/src/main/kotlin/info/appdev/charting/utils/ColorTemplate.kt b/chartLib/src/main/kotlin/info/appdev/charting/utils/ColorTemplate.kt index 1027faff2..43c2b6dd6 100644 --- a/chartLib/src/main/kotlin/info/appdev/charting/utils/ColorTemplate.kt +++ b/chartLib/src/main/kotlin/info/appdev/charting/utils/ColorTemplate.kt @@ -3,9 +3,8 @@ package info.appdev.charting.utils import android.graphics.Color /** - * Class that holds predefined color integer arrays (e.g. - * ColorTemplate.VORDIPLOM_COLORS) and convenience methods for loading colors - * from resources. + * Class that holds predefined color integer arrays (e.g. ColorTemplate.VORDIPLOM_COLORS) + * and convenience methods for loading colors from resources. */ object ColorTemplate { /** @@ -24,27 +23,45 @@ object ColorTemplate { * FREE TO CREATE YOUR OWN WITH AS MANY DIFFERENT COLORS AS YOU WANT */ val LIBERTY_COLORS: IntArray = intArrayOf( - Color.rgb(207, 248, 246), Color.rgb(148, 212, 212), Color.rgb(136, 180, 187), - Color.rgb(118, 174, 175), Color.rgb(42, 109, 130) + Color.rgb(207, 248, 246), + Color.rgb(148, 212, 212), + Color.rgb(136, 180, 187), + Color.rgb(118, 174, 175), + Color.rgb(42, 109, 130) ) val JOYFUL_COLORS: IntArray = intArrayOf( - Color.rgb(217, 80, 138), Color.rgb(254, 149, 7), Color.rgb(254, 247, 120), - Color.rgb(106, 167, 134), Color.rgb(53, 194, 209) + Color.rgb(217, 80, 138), + Color.rgb(254, 149, 7), + Color.rgb(254, 247, 120), + Color.rgb(106, 167, 134), + Color.rgb(53, 194, 209) ) val PASTEL_COLORS: IntArray = intArrayOf( - Color.rgb(64, 89, 128), Color.rgb(149, 165, 124), Color.rgb(217, 184, 162), - Color.rgb(191, 134, 134), Color.rgb(179, 48, 80) + Color.rgb(64, 89, 128), + Color.rgb(149, 165, 124), + Color.rgb(217, 184, 162), + Color.rgb(191, 134, 134), + Color.rgb(179, 48, 80) ) val COLORFUL_COLORS: IntArray = intArrayOf( - Color.rgb(193, 37, 82), Color.rgb(255, 102, 0), Color.rgb(245, 199, 0), - Color.rgb(106, 150, 31), Color.rgb(179, 100, 53) + Color.rgb(193, 37, 82), + Color.rgb(255, 102, 0), + Color.rgb(245, 199, 0), + Color.rgb(106, 150, 31), + Color.rgb(179, 100, 53) ) val VORDIPLOM_COLORS: IntArray = intArrayOf( - Color.rgb(192, 255, 140), Color.rgb(255, 247, 140), Color.rgb(255, 208, 140), - Color.rgb(140, 234, 255), Color.rgb(255, 140, 157) + Color.rgb(192, 255, 140), + Color.rgb(255, 247, 140), + Color.rgb(255, 208, 140), + Color.rgb(140, 234, 255), + Color.rgb(255, 140, 157) ) val MATERIAL_COLORS: IntArray = intArrayOf( - rgb("#2ecc71"), rgb("#f1c40f"), rgb("#e74c3c"), rgb("#3498db") + rgb("#2ecc71"), + rgb("#f1c40f"), + rgb("#e74c3c"), + rgb("#3498db") ) /** @@ -58,10 +75,10 @@ object ColorTemplate { return Color.rgb(r, g, b) } + /** + * Returns the Android ICS holo blue light color. + */ val holoBlue: Int - /** - * Returns the Android ICS holo blue light color. - */ get() = Color.rgb(51, 181, 229) /** @@ -77,11 +94,9 @@ object ColorTemplate { */ fun createColors(colors: IntArray): MutableList { val result: MutableList = ArrayList() - for (i in colors) { result.add(i) } - return result } } diff --git a/chartLibCompose/build.gradle.kts b/chartLibCompose/build.gradle.kts new file mode 100644 index 000000000..a540a0210 --- /dev/null +++ b/chartLibCompose/build.gradle.kts @@ -0,0 +1,117 @@ +import info.git.versionHelper.getVersionText +import org.gradle.kotlin.dsl.implementation +import java.net.URI + +plugins { + id("com.android.library") + id("maven-publish") + id("org.jetbrains.kotlin.plugin.compose") version "2.3.10" + id("com.vanniktech.maven.publish") version "0.36.0" +} + +android { + namespace = "info.appdev.charting.compose" + defaultConfig { + minSdk = 23 + compileSdk = 36 + + // VERSION_NAME no longer available as of 4.1 + // https://issuetracker.google.com/issues/158695880 + buildConfigField("String", "VERSION_NAME", "\"${getVersionText()}\"") + + consumerProguardFiles.add(File("proguard-lib.pro")) + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + buildTypes { + release { + isMinifyEnabled = false + } + } + buildFeatures { + buildConfig = true + compose = true + } + kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } + } + testOptions { + unitTests.isReturnDefaultValues = true // this prevents "not mocked" error + } +} + +dependencies { + implementation("androidx.annotation:annotation:1.9.1") + implementation("androidx.core:core:1.18.0") + implementation("androidx.activity:activity-ktx:1.13.0") + implementation("com.github.AppDevNext.Logcat:LogcatCoreLib:3.4") + api(project(":chartLib")) + + // Compose dependencies + val composeBom = platform("androidx.compose:compose-bom:2026.03.00") + implementation(composeBom) + implementation("androidx.compose.material3:material3") + implementation("androidx.compose.ui:ui") + implementation("androidx.compose.ui:ui-graphics") + implementation("androidx.compose.foundation:foundation") + implementation("androidx.compose.runtime:runtime") + implementation("androidx.compose.runtime:runtime-saveable") + implementation("androidx.lifecycle:lifecycle-runtime-compose:2.10.0") + + testImplementation("junit:junit:4.13.2") +} + +tasks.register("androidSourcesJar") { + archiveClassifier.set("sources") + from(android.sourceSets["main"].java.srcDirs) +} + +group = "info.mxtracks" +var versionVersion = getVersionText() +println("Build version $versionVersion") + +mavenPublishing { + pom { + name = "Android Chart" + description = + "A powerful Android chart view/graph view library, supporting line- bar- pie- radar- bubble- and candlestick charts as well as scaling, dragging and animations" + inceptionYear = "2022" + url = "https://github.com/AppDevNext/AndroidChart/" + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + distribution = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = "AppDevNext" + name = "AppDevNext" + url = "https://github.com/AppDevNext/" + } + } + scm { + url = "https://github.com/AppDevNext/AndroidChart/" + connection = "scm:git:git://github.com/AppDevNext/AndroidChart.git" + developerConnection = "scm:git:ssh://git@github.com/AppDevNext/AndroidChart.git" + } + } + + // Github packages + repositories { + maven { + version = "$versionVersion-SNAPSHOT" + name = "GitHubPackages" + url = URI("https://maven.pkg.github.com/AppDevNext/AndroidChart") + credentials { + username = System.getenv("GITHUBACTOR") + password = System.getenv("GITHUBTOKEN") + } + } + } +} diff --git a/chartLibCompose/ic_launcher-web.png b/chartLibCompose/ic_launcher-web.png new file mode 100644 index 000000000..ef5592200 Binary files /dev/null and b/chartLibCompose/ic_launcher-web.png differ diff --git a/chartLibCompose/proguard-lib.pro b/chartLibCompose/proguard-lib.pro new file mode 100644 index 000000000..9a306f98d --- /dev/null +++ b/chartLibCompose/proguard-lib.pro @@ -0,0 +1,7 @@ +# Whitelist AndroidChart +# Preserve all public classes and methods + +-keep class info.appdev.charting.** { *; } +-keep public class info.appdev.charting.animation.* { + public protected *; +} diff --git a/chartLibCompose/src/main/AndroidManifest.xml b/chartLibCompose/src/main/AndroidManifest.xml new file mode 100644 index 000000000..7dc9b158e --- /dev/null +++ b/chartLibCompose/src/main/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BarChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BarChartComposable.kt new file mode 100644 index 000000000..66541d4e8 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BarChartComposable.kt @@ -0,0 +1,169 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.animation.Easing +import info.appdev.charting.charts.BarChart +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.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [BarChart]. + * + * @param data The bar chart data to display + * @param modifier The modifier to be applied to the chart + * @param state The state holder for the chart + * @param onValueSelected Callback when a value is selected + * @param description Description text for the chart + * @param legend Configuration for the legend + * @param xAxisConfig Configuration for the X axis + * @param leftAxisConfig Configuration for the left Y axis + * @param rightAxisConfig Configuration for the right Y axis + * @param backgroundColor Background color for the chart + * @param gridBackgroundColor Grid background color + * @param drawGridBackground Whether to draw the grid background + * @param touchEnabled Whether touch gestures are enabled + * @param dragEnabled Whether drag gestures are enabled + * @param scaleEnabled Whether scale/zoom gestures are enabled + * @param scaleXEnabled Whether horizontal scale/zoom is enabled + * @param scaleYEnabled Whether vertical scale/zoom is enabled + * @param pinchZoomEnabled Whether pinch zoom is enabled + * @param doubleTapToZoomEnabled Whether double tap to zoom is enabled + * @param highlightPerTapEnabled Whether highlighting on tap is enabled + * @param highlightFullBarEnabled Whether to highlight full bar or single stack entry + * @param drawValueAboveBar Whether to draw values above bars + * @param drawBarShadow Whether to draw bar shadows + * @param fitBars Whether to fit bars within the chart viewport + * @param animationDuration Animation duration in milliseconds + * @param animationEasing Animation easing function + */ +@Composable +fun BarChart( + data: BarData?, + modifier: Modifier = Modifier, + state: BarChartState = rememberBarChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + xAxisConfig: ((XAxis) -> Unit)? = null, + leftAxisConfig: ((YAxis) -> Unit)? = null, + rightAxisConfig: ((YAxis) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + gridBackgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.LightGray, + drawGridBackground: Boolean = false, + touchEnabled: Boolean = true, + dragEnabled: Boolean = true, + scaleEnabled: Boolean = true, + scaleXEnabled: Boolean = true, + scaleYEnabled: Boolean = true, + pinchZoomEnabled: Boolean = true, + doubleTapToZoomEnabled: Boolean = true, + highlightPerTapEnabled: Boolean = true, + highlightFullBarEnabled: Boolean = false, + drawValueAboveBar: Boolean = true, + drawBarShadow: Boolean = false, + fitBars: Boolean = false, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + BarChart(context).apply { + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { + chart.clear() + } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { barChart -> + // Update data + barChart.data = data + + // Update configuration + barChart.setBackgroundColor(backgroundColor.toArgb()) + barChart.setDrawGridBackground(drawGridBackground) + barChart.setGridBackgroundColor(gridBackgroundColor.toArgb()) + + // Touch settings + barChart.setTouchEnabled(touchEnabled) + barChart.isDragEnabled = dragEnabled + barChart.setScaleEnabled(scaleEnabled) + barChart.isScaleXEnabled = scaleXEnabled + barChart.isScaleYEnabled = scaleYEnabled + barChart.isPinchZoom = pinchZoomEnabled + barChart.isDoubleTapToZoomEnabled = doubleTapToZoomEnabled + barChart.isHighlightPerTap = highlightPerTapEnabled + + // Bar-specific settings + barChart.isHighlightFullBar = highlightFullBarEnabled + barChart.isDrawValueAboveBar = drawValueAboveBar + barChart.isDrawBarShadow = drawBarShadow + barChart.setFitBars(fitBars) + + // Description + barChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + // Legend + legend?.invoke(barChart.legend) + + // Axes + xAxisConfig?.invoke(barChart.xAxis) + leftAxisConfig?.invoke(barChart.axisLeft) + rightAxisConfig?.invoke(barChart.axisRight) + + // Selection listener + if (onValueSelected != null) { + barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) { + onValueSelected(entry, highlight) + } + + override fun onNothingSelected() { + onValueSelected(null, null) + } + }) + } else { + barChart.setOnChartValueSelectedListener(null) + } + + // Animation + if (animationDuration > 0) { + if (animationEasing != null) { + barChart.animateY(animationDuration, animationEasing) + } else { + barChart.animateY(animationDuration) + } + } + + // Refresh + barChart.invalidate() + } + ) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BubbleChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BubbleChartComposable.kt new file mode 100644 index 000000000..42134afa1 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/BubbleChartComposable.kt @@ -0,0 +1,112 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.animation.Easing +import info.appdev.charting.charts.BubbleChart +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.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [BubbleChart]. + */ +@Composable +fun BubbleChart( + data: BubbleData?, + modifier: Modifier = Modifier, + state: BubbleChartState = rememberBubbleChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + xAxisConfig: ((XAxis) -> Unit)? = null, + leftAxisConfig: ((YAxis) -> Unit)? = null, + rightAxisConfig: ((YAxis) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + gridBackgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.LightGray, + drawGridBackground: Boolean = false, + touchEnabled: Boolean = true, + dragEnabled: Boolean = true, + scaleEnabled: Boolean = true, + scaleXEnabled: Boolean = true, + scaleYEnabled: Boolean = true, + pinchZoomEnabled: Boolean = true, + doubleTapToZoomEnabled: Boolean = true, + highlightPerTapEnabled: Boolean = true, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + BubbleChart(context).apply { + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { chart.clear() } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { bubbleChart -> + bubbleChart.data = data + + bubbleChart.setBackgroundColor(backgroundColor.toArgb()) + bubbleChart.setDrawGridBackground(drawGridBackground) + bubbleChart.setGridBackgroundColor(gridBackgroundColor.toArgb()) + bubbleChart.setTouchEnabled(touchEnabled) + bubbleChart.isDragEnabled = dragEnabled + bubbleChart.setScaleEnabled(scaleEnabled) + bubbleChart.isScaleXEnabled = scaleXEnabled + bubbleChart.isScaleYEnabled = scaleYEnabled + bubbleChart.isPinchZoom = pinchZoomEnabled + bubbleChart.isDoubleTapToZoomEnabled = doubleTapToZoomEnabled + bubbleChart.isHighlightPerTap = highlightPerTapEnabled + + bubbleChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + legend?.invoke(bubbleChart.legend) + xAxisConfig?.invoke(bubbleChart.xAxis) + leftAxisConfig?.invoke(bubbleChart.axisLeft) + rightAxisConfig?.invoke(bubbleChart.axisRight) + + if (onValueSelected != null) { + bubbleChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onNothingSelected() = onValueSelected(null, null) + }) + } + + if (animationDuration > 0) { + if (animationEasing != null) { + bubbleChart.animateXY(animationDuration, animationDuration, animationEasing, animationEasing) + } else { + bubbleChart.animateXY(animationDuration, animationDuration) + } + } + + bubbleChart.invalidate() + } + ) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CandleStickChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CandleStickChartComposable.kt new file mode 100644 index 000000000..892d2fbf6 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CandleStickChartComposable.kt @@ -0,0 +1,112 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.animation.Easing +import info.appdev.charting.charts.CandleStickChart +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.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [CandleStickChart]. + */ +@Composable +fun CandleStickChart( + data: CandleData?, + modifier: Modifier = Modifier, + state: CandleStickChartState = rememberCandleStickChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + xAxisConfig: ((XAxis) -> Unit)? = null, + leftAxisConfig: ((YAxis) -> Unit)? = null, + rightAxisConfig: ((YAxis) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + gridBackgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.LightGray, + drawGridBackground: Boolean = false, + touchEnabled: Boolean = true, + dragEnabled: Boolean = true, + scaleEnabled: Boolean = true, + scaleXEnabled: Boolean = true, + scaleYEnabled: Boolean = true, + pinchZoomEnabled: Boolean = true, + doubleTapToZoomEnabled: Boolean = true, + highlightPerTapEnabled: Boolean = true, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + CandleStickChart(context).apply { + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { chart.clear() } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { candleChart -> + candleChart.data = data + + candleChart.setBackgroundColor(backgroundColor.toArgb()) + candleChart.setDrawGridBackground(drawGridBackground) + candleChart.setGridBackgroundColor(gridBackgroundColor.toArgb()) + candleChart.setTouchEnabled(touchEnabled) + candleChart.isDragEnabled = dragEnabled + candleChart.setScaleEnabled(scaleEnabled) + candleChart.isScaleXEnabled = scaleXEnabled + candleChart.isScaleYEnabled = scaleYEnabled + candleChart.isPinchZoom = pinchZoomEnabled + candleChart.isDoubleTapToZoomEnabled = doubleTapToZoomEnabled + candleChart.isHighlightPerTap = highlightPerTapEnabled + + candleChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + legend?.invoke(candleChart.legend) + xAxisConfig?.invoke(candleChart.xAxis) + leftAxisConfig?.invoke(candleChart.axisLeft) + rightAxisConfig?.invoke(candleChart.axisRight) + + if (onValueSelected != null) { + candleChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onNothingSelected() = onValueSelected(null, null) + }) + } + + if (animationDuration > 0) { + if (animationEasing != null) { + candleChart.animateY(animationDuration, animationEasing) + } else { + candleChart.animateY(animationDuration) + } + } + + candleChart.invalidate() + } + ) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ChartState.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ChartState.kt new file mode 100644 index 000000000..89c93d0c1 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ChartState.kt @@ -0,0 +1,200 @@ +package info.appdev.charting.compose + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.listSaver +import androidx.compose.runtime.setValue +import info.appdev.charting.charts.CombinedChart +import info.appdev.charting.charts.ScatterChart +import info.appdev.charting.data.* +import info.appdev.charting.highlight.Highlight + +/** + * Base state holder for all chart types in Compose. + * Manages chart data, highlighting, and configuration in a Compose-friendly way. + */ +@Stable +sealed class ChartState> { + var data by mutableStateOf(null) + var highlights by mutableStateOf>(emptyList()) + var isLogEnabled by mutableStateOf(false) + var description by mutableStateOf(null) + var touchEnabled by mutableStateOf(true) + var dragDecelerationEnabled by mutableStateOf(true) + var dragDecelerationFrictionCoef by mutableStateOf(0.9f) +} + +/** + * State holder for LineChart composable. + */ +@Stable +class LineChartState : ChartState() { + companion object { + val Saver: Saver = listSaver( + save = { state -> + listOf( + state.isLogEnabled, + state.description, + state.touchEnabled, + state.dragDecelerationEnabled, + state.dragDecelerationFrictionCoef + ) + }, + restore = { list -> + LineChartState().apply { + isLogEnabled = list[0] as Boolean + description = list[1] as String? + touchEnabled = list[2] as Boolean + dragDecelerationEnabled = list[3] as Boolean + dragDecelerationFrictionCoef = list[4] as Float + } + } + ) + } +} + +/** + * State holder for BarChart composable. + */ +@Stable +class BarChartState : ChartState() { + var isHighlightFullBar by mutableStateOf(false) + var isDrawValueAboveBar by mutableStateOf(true) + var isDrawBarShadow by mutableStateOf(false) + + companion object { + val Saver: Saver = listSaver( + save = { state -> + listOf( + state.isLogEnabled, + state.description, + state.touchEnabled, + state.isHighlightFullBar, + state.isDrawValueAboveBar, + state.isDrawBarShadow + ) + }, + restore = { list -> + BarChartState().apply { + isLogEnabled = list[0] as Boolean + description = list[1] as String? + touchEnabled = list[2] as Boolean + isHighlightFullBar = list[3] as Boolean + isDrawValueAboveBar = list[4] as Boolean + isDrawBarShadow = list[5] as Boolean + } + } + ) + } +} + +/** + * State holder for HorizontalBarChart composable. + */ +@Stable +class HorizontalBarChartState : ChartState() { + var isHighlightFullBarEnabled by mutableStateOf(false) + var isDrawValueAboveBarEnabled by mutableStateOf(true) + var isDrawBarShadowEnabled by mutableStateOf(false) +} + +/** + * State holder for PieChart composable. + */ +@Stable +class PieChartState : ChartState() { + var isDrawEntryLabelsEnabled by mutableStateOf(true) + var isDrawHoleEnabled by mutableStateOf(true) + var isDrawSlicesUnderHoleEnabled by mutableStateOf(false) + var isUsePercentValuesEnabled by mutableStateOf(false) + var isDrawRoundedSlicesEnabled by mutableStateOf(false) + var holeRadius by mutableStateOf(50f) + var transparentCircleRadius by mutableStateOf(55f) + var centerText by mutableStateOf("") + var rotationAngle by mutableStateOf(270f) + var isRotationEnabled by mutableStateOf(true) + + companion object { + val Saver: Saver = listSaver( + save = { state -> + listOf( + state.isLogEnabled, + state.description, + state.isDrawEntryLabelsEnabled, + state.isDrawHoleEnabled, + state.holeRadius, + state.transparentCircleRadius, + state.centerText.toString(), + state.rotationAngle, + state.isRotationEnabled + ) + }, + restore = { list -> + PieChartState().apply { + isLogEnabled = list[0] as Boolean + description = list[1] as String? + isDrawEntryLabelsEnabled = list[2] as Boolean + isDrawHoleEnabled = list[3] as Boolean + holeRadius = list[4] as Float + transparentCircleRadius = list[5] as Float + centerText = list[6] as String + rotationAngle = list[7] as Float + isRotationEnabled = list[8] as Boolean + } + } + ) + } +} + +/** + * State holder for RadarChart composable. + */ +@Stable +class RadarChartState : ChartState() { + var webLineWidth by mutableStateOf(2.5f) + var webLineWidthInner by mutableStateOf(1.5f) + var webAlpha by mutableStateOf(150) + var skipWebLineCount by mutableStateOf(0) +} + +/** + * State holder for ScatterChart composable. + */ +@Stable +class ScatterChartState : ChartState() { + var scaleType by mutableStateOf(ScatterChart.ScatterShape.CIRCLE) +} + +/** + * State holder for BubbleChart composable. + */ +@Stable +class BubbleChartState : ChartState() + +/** + * State holder for CandleStickChart composable. + */ +@Stable +class CandleStickChartState : ChartState() + +/** + * State holder for CombinedChart composable. + */ +@Stable +class CombinedChartState : ChartState() { + var drawOrder by mutableStateOf( + arrayOf( + CombinedChart.DrawOrder.BAR, + CombinedChart.DrawOrder.BUBBLE, + CombinedChart.DrawOrder.LINE, + CombinedChart.DrawOrder.CANDLE, + CombinedChart.DrawOrder.SCATTER + ) + ) + var isDrawBarShadowEnabled by mutableStateOf(false) + var isHighlightFullBarEnabled by mutableStateOf(false) + var isDrawValueAboveBarEnabled by mutableStateOf(true) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ChartStateRemember.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ChartStateRemember.kt new file mode 100644 index 000000000..3e21d9e64 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ChartStateRemember.kt @@ -0,0 +1,99 @@ +package info.appdev.charting.compose + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable + +/** + * Remember a [LineChartState] across recompositions. + * The state will be saved and restored across configuration changes. + */ +@Composable +fun rememberLineChartState(): LineChartState { + return rememberSaveable(saver = LineChartState.Saver) { + LineChartState() + } +} + +/** + * Remember a [BarChartState] across recompositions. + * The state will be saved and restored across configuration changes. + */ +@Composable +fun rememberBarChartState(): BarChartState { + return rememberSaveable(saver = BarChartState.Saver) { + BarChartState() + } +} + +/** + * Remember a [HorizontalBarChartState] across recompositions. + */ +@Composable +fun rememberHorizontalBarChartState(): HorizontalBarChartState { + return remember { + HorizontalBarChartState() + } +} + +/** + * Remember a [PieChartState] across recompositions. + * The state will be saved and restored across configuration changes. + */ +@Composable +fun rememberPieChartState(): PieChartState { + return rememberSaveable(saver = PieChartState.Saver) { + PieChartState() + } +} + +/** + * Remember a [RadarChartState] across recompositions. + */ +@Composable +fun rememberRadarChartState(): RadarChartState { + return remember { + RadarChartState() + } +} + +/** + * Remember a [ScatterChartState] across recompositions. + */ +@Composable +fun rememberScatterChartState(): ScatterChartState { + return remember { + ScatterChartState() + } +} + +/** + * Remember a [BubbleChartState] across recompositions. + */ +@Composable +fun rememberBubbleChartState(): BubbleChartState { + return remember { + BubbleChartState() + } +} + +/** + * Remember a [CandleStickChartState] across recompositions. + */ +@Composable +fun rememberCandleStickChartState(): CandleStickChartState { + return remember { + CandleStickChartState() + } +} + +/** + * Remember a [CombinedChartState] across recompositions. + */ +@Composable +fun rememberCombinedChartState(): CombinedChartState { + return remember { + CombinedChartState() + } +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CombinedChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CombinedChartComposable.kt new file mode 100644 index 000000000..3c196f471 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/CombinedChartComposable.kt @@ -0,0 +1,128 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.animation.Easing +import info.appdev.charting.charts.CombinedChart +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.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [CombinedChart]. + */ +@Composable +fun CombinedChart( + data: CombinedData?, + modifier: Modifier = Modifier, + state: CombinedChartState = rememberCombinedChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + xAxisConfig: ((XAxis) -> Unit)? = null, + leftAxisConfig: ((YAxis) -> Unit)? = null, + rightAxisConfig: ((YAxis) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + gridBackgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.LightGray, + drawGridBackground: Boolean = false, + touchEnabled: Boolean = true, + dragEnabled: Boolean = true, + scaleEnabled: Boolean = true, + scaleXEnabled: Boolean = true, + scaleYEnabled: Boolean = true, + pinchZoomEnabled: Boolean = true, + doubleTapToZoomEnabled: Boolean = true, + highlightPerTapEnabled: Boolean = true, + drawOrder: Array = arrayOf( + CombinedChart.DrawOrder.BAR, + CombinedChart.DrawOrder.BUBBLE, + CombinedChart.DrawOrder.LINE, + CombinedChart.DrawOrder.CANDLE, + CombinedChart.DrawOrder.SCATTER + ), + drawBarShadow: Boolean = false, + highlightFullBarEnabled: Boolean = false, + drawValueAboveBar: Boolean = true, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + CombinedChart(context).apply { + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { chart.clear() } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { combinedChart -> + combinedChart.data = data + + combinedChart.setBackgroundColor(backgroundColor.toArgb()) + combinedChart.setDrawGridBackground(drawGridBackground) + combinedChart.setGridBackgroundColor(gridBackgroundColor.toArgb()) + combinedChart.setTouchEnabled(touchEnabled) + combinedChart.isDragEnabled = dragEnabled + combinedChart.setScaleEnabled(scaleEnabled) + combinedChart.isScaleXEnabled = scaleXEnabled + combinedChart.isScaleYEnabled = scaleYEnabled + combinedChart.isPinchZoom = pinchZoomEnabled + combinedChart.isDoubleTapToZoomEnabled = doubleTapToZoomEnabled + combinedChart.isHighlightPerTap = highlightPerTapEnabled + + // Combined-specific settings + combinedChart.drawOrder = drawOrder.toMutableList() + combinedChart.isDrawBarShadow = drawBarShadow + combinedChart.isHighlightFullBar = highlightFullBarEnabled + combinedChart.isDrawValueAboveBar = drawValueAboveBar + + combinedChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + legend?.invoke(combinedChart.legend) + xAxisConfig?.invoke(combinedChart.xAxis) + leftAxisConfig?.invoke(combinedChart.axisLeft) + rightAxisConfig?.invoke(combinedChart.axisRight) + + if (onValueSelected != null) { + combinedChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onNothingSelected() = onValueSelected(null, null) + }) + } + + if (animationDuration > 0) { + if (animationEasing != null) { + combinedChart.animateXY(animationDuration, animationDuration, animationEasing, animationEasing) + } else { + combinedChart.animateXY(animationDuration, animationDuration) + } + } + + combinedChart.invalidate() + } + ) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/HorizontalBarChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/HorizontalBarChartComposable.kt new file mode 100644 index 000000000..d76bae0ff --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/HorizontalBarChartComposable.kt @@ -0,0 +1,108 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.animation.Easing +import info.appdev.charting.charts.HorizontalBarChart +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.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [HorizontalBarChart]. + */ +@Composable +fun HorizontalBarChart( + data: BarData?, + modifier: Modifier = Modifier, + state: HorizontalBarChartState = rememberHorizontalBarChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + xAxisConfig: ((XAxis) -> Unit)? = null, + leftAxisConfig: ((YAxis) -> Unit)? = null, + rightAxisConfig: ((YAxis) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + gridBackgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.LightGray, + drawGridBackground: Boolean = false, + touchEnabled: Boolean = true, + dragEnabled: Boolean = true, + scaleEnabled: Boolean = true, + highlightFullBarEnabled: Boolean = false, + drawValueAboveBar: Boolean = true, + drawBarShadow: Boolean = false, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + HorizontalBarChart(context).apply { + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { chart.clear() } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { horizontalBarChart -> + horizontalBarChart.data = data + + horizontalBarChart.setBackgroundColor(backgroundColor.toArgb()) + horizontalBarChart.setDrawGridBackground(drawGridBackground) + horizontalBarChart.setGridBackgroundColor(gridBackgroundColor.toArgb()) + horizontalBarChart.setTouchEnabled(touchEnabled) + horizontalBarChart.isDragEnabled = dragEnabled + horizontalBarChart.setScaleEnabled(scaleEnabled) + horizontalBarChart.isHighlightFullBar = highlightFullBarEnabled + horizontalBarChart.isDrawValueAboveBar = drawValueAboveBar + horizontalBarChart.isDrawBarShadow = drawBarShadow + + horizontalBarChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + legend?.invoke(horizontalBarChart.legend) + xAxisConfig?.invoke(horizontalBarChart.xAxis) + leftAxisConfig?.invoke(horizontalBarChart.axisLeft) + rightAxisConfig?.invoke(horizontalBarChart.axisRight) + + if (onValueSelected != null) { + horizontalBarChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onNothingSelected() = onValueSelected(null, null) + }) + } + + if (animationDuration > 0) { + if (animationEasing != null) { + horizontalBarChart.animateY(animationDuration, animationEasing) + } else { + horizontalBarChart.animateY(animationDuration) + } + } + + horizontalBarChart.invalidate() + } + ) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/LineChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/LineChartComposable.kt new file mode 100644 index 000000000..97c511ac9 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/LineChartComposable.kt @@ -0,0 +1,174 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.animation.Easing +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.LineData +import info.appdev.charting.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [LineChart]. + * + * @param data The line chart data to display + * @param modifier The modifier to be applied to the chart + * @param state The state holder for the chart + * @param onValueSelected Callback when a value is selected + * @param description Description text for the chart + * @param legend Configuration for the legend + * @param xAxisConfig Configuration for the X axis + * @param leftAxisConfig Configuration for the left Y axis + * @param rightAxisConfig Configuration for the right Y axis + * @param backgroundColor Background color for the chart + * @param gridBackgroundColor Grid background color + * @param drawGridBackground Whether to draw the grid background + * @param touchEnabled Whether touch gestures are enabled + * @param dragEnabled Whether drag gestures are enabled + * @param scaleEnabled Whether scale/zoom gestures are enabled + * @param scaleXEnabled Whether horizontal scale/zoom is enabled + * @param scaleYEnabled Whether vertical scale/zoom is enabled + * @param pinchZoomEnabled Whether pinch zoom is enabled + * @param doubleTapToZoomEnabled Whether double tap to zoom is enabled + * @param highlightPerTapEnabled Whether highlighting on tap is enabled + * @param dragDecelerationEnabled Whether drag deceleration is enabled + * @param dragDecelerationFrictionCoef Drag deceleration friction coefficient + * @param maxVisibleValueCount Maximum number of values to display + * @param autoScaleMinMax Whether to auto-scale min/max values + * @param keepPositionOnRotation Whether to keep position on rotation + * @param animationDuration Animation duration in milliseconds + * @param animationEasing Animation easing function + */ +@Composable +fun LineChart( + data: LineData?, + modifier: Modifier = Modifier, + state: LineChartState = rememberLineChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + xAxisConfig: ((XAxis) -> Unit)? = null, + leftAxisConfig: ((YAxis) -> Unit)? = null, + rightAxisConfig: ((YAxis) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + gridBackgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.LightGray, + drawGridBackground: Boolean = false, + touchEnabled: Boolean = true, + dragEnabled: Boolean = true, + scaleEnabled: Boolean = true, + scaleXEnabled: Boolean = true, + scaleYEnabled: Boolean = true, + pinchZoomEnabled: Boolean = true, + doubleTapToZoomEnabled: Boolean = true, + highlightPerTapEnabled: Boolean = true, + dragDecelerationEnabled: Boolean = true, + dragDecelerationFrictionCoef: Float = 0.9f, + maxVisibleValueCount: Int = 100, + autoScaleMinMax: Boolean = false, + keepPositionOnRotation: Boolean = false, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + LineChart(context).apply { + // Initial setup + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { + // Clean up chart resources + chart.clear() + } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { lineChart -> + // Update data + lineChart.data = data + + // Update configuration + lineChart.setBackgroundColor(backgroundColor.toArgb()) + lineChart.setDrawGridBackground(drawGridBackground) + lineChart.setGridBackgroundColor(gridBackgroundColor.toArgb()) + + // Touch settings + lineChart.setTouchEnabled(touchEnabled) + lineChart.isDragEnabled = dragEnabled + lineChart.setScaleEnabled(scaleEnabled) + lineChart.isScaleXEnabled = scaleXEnabled + lineChart.isScaleYEnabled = scaleYEnabled + lineChart.isPinchZoom = pinchZoomEnabled + lineChart.isDoubleTapToZoomEnabled = doubleTapToZoomEnabled + lineChart.isHighlightPerTap = highlightPerTapEnabled + lineChart.isDragDeceleration = dragDecelerationEnabled + lineChart.dragDecelerationFrictionCoef = dragDecelerationFrictionCoef + + // Display settings + lineChart.setMaxVisibleValueCount(maxVisibleValueCount) + lineChart.isAutoScaleMinMax = autoScaleMinMax + lineChart.isKeepPositionOnRotation = keepPositionOnRotation + + // Description + lineChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + // Legend + legend?.invoke(lineChart.legend) + + // Axes + xAxisConfig?.invoke(lineChart.xAxis) + leftAxisConfig?.invoke(lineChart.axisLeft) + rightAxisConfig?.invoke(lineChart.axisRight) + + // Selection listener + if (onValueSelected != null) { + lineChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) { + onValueSelected(entry, highlight) + } + + override fun onNothingSelected() { + onValueSelected(null, null) + } + }) + } else { + lineChart.setOnChartValueSelectedListener(null) + } + + // Animation + if (animationDuration > 0) { + if (animationEasing != null) { + lineChart.animateX(animationDuration, animationEasing) + } else { + lineChart.animateX(animationDuration) + } + } + + // Refresh + lineChart.invalidate() + } + ) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/PieChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/PieChartComposable.kt new file mode 100644 index 000000000..987c4f27f --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/PieChartComposable.kt @@ -0,0 +1,153 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +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.PieData +import info.appdev.charting.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [PieChart]. + * + * @param data The pie chart data to display + * @param modifier The modifier to be applied to the chart + * @param state The state holder for the chart + * @param onValueSelected Callback when a value is selected + * @param description Description text for the chart + * @param legend Configuration for the legend + * @param backgroundColor Background color for the chart + * @param touchEnabled Whether touch gestures are enabled + * @param rotationEnabled Whether rotation is enabled + * @param highlightPerTapEnabled Whether highlighting on tap is enabled + * @param drawEntryLabelsEnabled Whether to draw entry labels + * @param drawHoleEnabled Whether to draw a hole in the center + * @param drawSlicesUnderHoleEnabled Whether to draw slices under the hole + * @param usePercentValuesEnabled Whether to use percentage values + * @param drawRoundedSlicesEnabled Whether to draw rounded slices + * @param holeRadius Radius of the center hole (0-100) + * @param transparentCircleRadius Radius of the transparent circle (0-100) + * @param centerText Text to display in the center + * @param rotationAngle Starting rotation angle in degrees + * @param minAngleForSlices Minimum angle for slices + * @param animationDuration Animation duration in milliseconds + * @param animationEasing Animation easing function + */ +@Composable +fun PieChart( + data: PieData?, + modifier: Modifier = Modifier, + state: PieChartState = rememberPieChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + touchEnabled: Boolean = true, + rotationEnabled: Boolean = true, + highlightPerTapEnabled: Boolean = true, + drawEntryLabelsEnabled: Boolean = true, + drawHoleEnabled: Boolean = true, + drawSlicesUnderHoleEnabled: Boolean = false, + usePercentValuesEnabled: Boolean = false, + drawRoundedSlicesEnabled: Boolean = false, + holeRadius: Float = 50f, + transparentCircleRadius: Float = 55f, + centerText: CharSequence = "", + rotationAngle: Float = 270f, + minAngleForSlices: Float = 0f, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + PieChart(context).apply { + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { + chart.clear() + } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { pieChart -> + // Update data + pieChart.data = data + + // Update configuration + pieChart.setBackgroundColor(backgroundColor.toArgb()) + + // Touch settings + pieChart.setTouchEnabled(touchEnabled) + pieChart.isRotationEnabled = rotationEnabled + pieChart.isHighlightPerTap = highlightPerTapEnabled + + // Pie-specific settings + pieChart.isDrawEntryLabels = drawEntryLabelsEnabled + pieChart.isDrawHole = drawHoleEnabled + pieChart.isDrawSlicesUnderHole = drawSlicesUnderHoleEnabled + pieChart.isUsePercentValues = usePercentValuesEnabled + pieChart.isDrawRoundedSlices = drawRoundedSlicesEnabled + pieChart.holeRadius = holeRadius + pieChart.transparentCircleRadius = transparentCircleRadius + pieChart.centerText = centerText + pieChart.rotationAngle = rotationAngle + pieChart.minAngleForSlices = minAngleForSlices + + // Description + pieChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + // Legend + legend?.invoke(pieChart.legend) + + // Selection listener + if (onValueSelected != null) { + pieChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) { + onValueSelected(entry, highlight) + } + + override fun onNothingSelected() { + onValueSelected(null, null) + } + }) + } else { + pieChart.setOnChartValueSelectedListener(null) + } + + // Animation + if (animationDuration > 0) { + if (animationEasing != null) { + pieChart.spin(animationDuration, rotationAngle, rotationAngle + 360f, animationEasing) + } else { + pieChart.animateY(animationDuration) + } + } + + // Refresh + pieChart.invalidate() + } + ) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/RadarChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/RadarChartComposable.kt new file mode 100644 index 000000000..42b23c963 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/RadarChartComposable.kt @@ -0,0 +1,102 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.animation.Easing +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.RadarData +import info.appdev.charting.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [RadarChart]. + */ +@Composable +fun RadarChart( + data: RadarData?, + modifier: Modifier = Modifier, + state: RadarChartState = rememberRadarChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + xAxisConfig: ((XAxis) -> Unit)? = null, + yAxisConfig: ((YAxis) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + touchEnabled: Boolean = true, + rotationEnabled: Boolean = true, + webLineWidth: Float = 2.5f, + webLineWidthInner: Float = 1.5f, + webAlpha: Int = 150, + skipWebLineCount: Int = 0, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + RadarChart(context).apply { + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { chart.clear() } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { radarChart -> + radarChart.data = data + + radarChart.setBackgroundColor(backgroundColor.toArgb()) + radarChart.setTouchEnabled(touchEnabled) + radarChart.isRotationEnabled = rotationEnabled + radarChart.webLineWidth = webLineWidth + radarChart.webLineWidthInner = webLineWidthInner + radarChart.webAlpha = webAlpha + radarChart.skipWebLineCount = skipWebLineCount + + radarChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + legend?.invoke(radarChart.legend) + xAxisConfig?.invoke(radarChart.xAxis) + yAxisConfig?.invoke(radarChart.yAxis) + + if (onValueSelected != null) { + radarChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onNothingSelected() = onValueSelected(null, null) + }) + } + + if (animationDuration > 0) { + if (animationEasing != null) { + radarChart.animateXY(animationDuration, animationDuration, animationEasing, animationEasing) + } else { + radarChart.animateXY(animationDuration, animationDuration) + } + } + + radarChart.invalidate() + } + ) +} + diff --git a/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ScatterChartComposable.kt b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ScatterChartComposable.kt new file mode 100644 index 000000000..0ec1bef76 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/ScatterChartComposable.kt @@ -0,0 +1,112 @@ +package info.appdev.charting.compose + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.viewinterop.AndroidView +import info.appdev.charting.animation.Easing +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.ScatterData +import info.appdev.charting.highlight.Highlight +import info.appdev.charting.listener.OnChartValueSelectedListener + +/** + * A Composable wrapper for [ScatterChart]. + */ +@Composable +fun ScatterChart( + data: ScatterData?, + modifier: Modifier = Modifier, + state: ScatterChartState = rememberScatterChartState(), + onValueSelected: ((Entry?, Highlight?) -> Unit)? = null, + description: String? = null, + legend: ((Legend) -> Unit)? = null, + xAxisConfig: ((XAxis) -> Unit)? = null, + leftAxisConfig: ((YAxis) -> Unit)? = null, + rightAxisConfig: ((YAxis) -> Unit)? = null, + backgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.White, + gridBackgroundColor: androidx.compose.ui.graphics.Color = androidx.compose.ui.graphics.Color.LightGray, + drawGridBackground: Boolean = false, + touchEnabled: Boolean = true, + dragEnabled: Boolean = true, + scaleEnabled: Boolean = true, + scaleXEnabled: Boolean = true, + scaleYEnabled: Boolean = true, + pinchZoomEnabled: Boolean = true, + doubleTapToZoomEnabled: Boolean = true, + highlightPerTapEnabled: Boolean = true, + animationDuration: Int = 0, + animationEasing: Easing.EasingFunction? = null, +) { + val context = LocalContext.current + + val chart = remember { + ScatterChart(context).apply { + this.description.isEnabled = false + } + } + + DisposableEffect(chart) { + onDispose { chart.clear() } + } + + AndroidView( + factory = { chart }, + modifier = modifier.fillMaxSize(), + update = { scatterChart -> + scatterChart.data = data + + scatterChart.setBackgroundColor(backgroundColor.toArgb()) + scatterChart.setDrawGridBackground(drawGridBackground) + scatterChart.setGridBackgroundColor(gridBackgroundColor.toArgb()) + scatterChart.setTouchEnabled(touchEnabled) + scatterChart.isDragEnabled = dragEnabled + scatterChart.setScaleEnabled(scaleEnabled) + scatterChart.isScaleXEnabled = scaleXEnabled + scatterChart.isScaleYEnabled = scaleYEnabled + scatterChart.isPinchZoom = pinchZoomEnabled + scatterChart.isDoubleTapToZoomEnabled = doubleTapToZoomEnabled + scatterChart.isHighlightPerTap = highlightPerTapEnabled + + scatterChart.description.let { desc -> + if (description != null) { + desc.isEnabled = true + desc.text = description + } else { + desc.isEnabled = false + } + } + + legend?.invoke(scatterChart.legend) + xAxisConfig?.invoke(scatterChart.xAxis) + leftAxisConfig?.invoke(scatterChart.axisLeft) + rightAxisConfig?.invoke(scatterChart.axisRight) + + if (onValueSelected != null) { + scatterChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener { + override fun onValueSelected(entry: Entry, highlight: Highlight) = onValueSelected(entry, highlight) + override fun onNothingSelected() = onValueSelected(null, null) + }) + } + + if (animationDuration > 0) { + if (animationEasing != null) { + scatterChart.animateXY(animationDuration, animationDuration, animationEasing, animationEasing) + } else { + scatterChart.animateXY(animationDuration, animationDuration) + } + } + + scatterChart.invalidate() + } + ) +} + 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 new file mode 100644 index 000000000..df3e43848 --- /dev/null +++ b/chartLibCompose/src/main/kotlin/info/appdev/charting/compose/examples/ChartExamples.kt @@ -0,0 +1,381 @@ +package info.appdev.charting.compose.examples + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import info.appdev.charting.charts.ScatterChart +import info.appdev.charting.components.Legend +import info.appdev.charting.components.XAxis +import info.appdev.charting.compose.BarChart +import info.appdev.charting.compose.LineChart +import info.appdev.charting.compose.PieChart +import info.appdev.charting.compose.RadarChart +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.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.RadarData +import info.appdev.charting.data.RadarDataSet +import info.appdev.charting.data.RadarEntry +import info.appdev.charting.data.ScatterData +import info.appdev.charting.data.ScatterDataSet + +/** + * Example composable demonstrating all chart types in Compose. + * This file serves as a reference for using the new Compose chart APIs. + */ +@Composable +fun ChartExamplesScreen() { + Column( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(16.dp), + verticalArrangement = Arrangement.spacedBy(24.dp) + ) { + Text("Chart Examples", style = MaterialTheme.typography.headlineMedium) + + LineChartExample() + BarChartExample() + PieChartExample() + ScatterChartExample() + RadarChartExample() + } +} + +@Composable +fun LineChartExample() { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Line Chart", style = MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.height(8.dp)) + + val entries = remember { + (0..10).map { Entry(it.toFloat(), (10..50).random().toFloat()) } + } + + val dataSet = remember(entries) { + LineDataSet(entries.toMutableList(), "Sample Data").apply { + color = android.graphics.Color.BLUE + lineWidth = 2.5f + isDrawCircles = true + isDrawValues = false + isDrawFilled = true + fillColor = android.graphics.Color.BLUE + fillAlpha = 50 + } + } + + val lineData = remember(dataSet) { LineData(dataSet) } + + var selectedValue by remember { mutableStateOf(null) } + + LineChart( + data = lineData, + modifier = Modifier + .fillMaxWidth() + .height(250.dp), + description = "Monthly Sales", + animationDuration = 1000, + dragEnabled = true, + scaleEnabled = true, + pinchZoomEnabled = true, + onValueSelected = { entry, _ -> + selectedValue = entry?.y + }, + xAxisConfig = { xAxis -> + xAxis.position = XAxis.XAxisPosition.BOTTOM + xAxis.isDrawGridLines = true + }, + leftAxisConfig = { axis -> + axis.axisMinimum = 0f + }, + rightAxisConfig = { axis -> + axis.isEnabled = false + }, + legend = { legend -> + legend.isEnabled = true + legend.textSize = 12f + } + ) + + selectedValue?.let { + Spacer(modifier = Modifier.height(8.dp)) + Text( + "Selected Value: ${"%.2f".format(it)}", + style = MaterialTheme.typography.bodyMedium + ) + } + } + } +} + +@Composable +fun BarChartExample() { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Bar Chart", style = MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.height(8.dp)) + + val entries = remember { + (0..5).map { BarEntry(it.toFloat(), (20..80).random().toFloat()) } + } + + val dataSet = remember(entries) { + BarDataSet(entries.toMutableList(), "Revenue").apply { + color = android.graphics.Color.rgb(104, 241, 175) + isDrawValues = true + valueTextSize = 12f + } + } + + val barData = remember(dataSet) { BarData(dataSet) } + + BarChart( + data = barData, + modifier = Modifier + .fillMaxWidth() + .height(250.dp), + description = "Quarterly Revenue", + backgroundColor = Color(0xFFF5F5F5), + drawValueAboveBar = true, + animationDuration = 1200, + xAxisConfig = { xAxis -> + xAxis.position = XAxis.XAxisPosition.BOTTOM + xAxis.isDrawGridLines = false + xAxis.granularity = 1f + }, + leftAxisConfig = { axis -> + axis.axisMinimum = 0f + } + ) + } + } +} + +@Composable +fun PieChartExample() { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Pie Chart", style = MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.height(8.dp)) + + val entries = remember { + listOf( + PieEntry(30f, "Product A"), + PieEntry(25f, "Product B"), + PieEntry(20f, "Product C"), + PieEntry(15f, "Product D"), + PieEntry(10f, "Product E") + ) + } + + val dataSet = remember(entries) { + PieDataSet(entries.toMutableList(), "Sales Distribution").apply { + setColors( + mutableListOf( + android.graphics.Color.rgb(255, 102, 0), + android.graphics.Color.rgb(76, 175, 80), + android.graphics.Color.rgb(33, 150, 243), + android.graphics.Color.rgb(156, 39, 176), + android.graphics.Color.rgb(255, 193, 7) + ) + ) + valueTextSize = 14f +// valueTextColor = android.graphics.Color.WHITE + } + } + + val pieData = remember(dataSet) { PieData(dataSet) } + + PieChart( + data = pieData, + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + drawHoleEnabled = true, + holeRadius = 40f, + transparentCircleRadius = 45f, + centerText = "Market Share", + rotationEnabled = true, + usePercentValuesEnabled = true, + drawEntryLabelsEnabled = true, + animationDuration = 1500, + legend = { legend -> + legend.verticalAlignment = Legend.LegendVerticalAlignment.BOTTOM + legend.horizontalAlignment = Legend.LegendHorizontalAlignment.CENTER + legend.orientation = Legend.LegendOrientation.HORIZONTAL + legend.setDrawInside(false) + } + ) + } + } +} + +@Composable +fun ScatterChartExample() { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Scatter Chart", style = MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.height(8.dp)) + + val entries = remember { + (0..20).map { + Entry(it.toFloat(), (10..50).random().toFloat()) + } + } + + val dataSet = remember(entries) { + ScatterDataSet(entries.toMutableList(), "Data Points").apply { + color = android.graphics.Color.rgb(255, 87, 34) + setScatterShape(ScatterChart.ScatterShape.CIRCLE) + scatterShapeSize = 12f + isDrawValues = false + } + } + + val scatterData = remember(dataSet) { ScatterData(dataSet) } + + ScatterChart( + data = scatterData, + modifier = Modifier + .fillMaxWidth() + .height(250.dp), + description = "Distribution Analysis", + animationDuration = 1000, + xAxisConfig = { xAxis -> + xAxis.position = XAxis.XAxisPosition.BOTTOM + } + ) + } + } +} + +@Composable +fun RadarChartExample() { + Card(modifier = Modifier.fillMaxWidth()) { + Column(modifier = Modifier.padding(16.dp)) { + Text("Radar Chart", style = MaterialTheme.typography.titleMedium) + Spacer(modifier = Modifier.height(8.dp)) + + val entries = remember { + listOf( + RadarEntry(8f, "Speed"), + RadarEntry(7f, "Strength"), + RadarEntry(6f, "Defense"), + RadarEntry(9f, "Agility"), + RadarEntry(7f, "Intelligence") + ) + } + + val dataSet = remember(entries) { + RadarDataSet(entries.toMutableList(), "Character Stats").apply { + color = android.graphics.Color.rgb(103, 110, 129) + fillColor = android.graphics.Color.rgb(103, 110, 129) + isDrawFilled = true + fillAlpha = 100 + lineWidth = 2f + isDrawValues = true + } + } + + val radarData = remember(dataSet) { RadarData(mutableListOf(dataSet)) } + + RadarChart( + data = radarData, + modifier = Modifier + .fillMaxWidth() + .height(300.dp), + description = "Performance Metrics", + rotationEnabled = true, + webLineWidth = 1.5f, + webLineWidthInner = 0.75f, + webAlpha = 100, + animationDuration = 1000, + xAxisConfig = { xAxis -> + xAxis.textSize = 12f + } + ) + } + } +} + +/** + * Example showing state management with dynamic updates + */ +@Composable +fun DynamicChartExample() { + val state = rememberLineChartState() + var dataPoints by remember { mutableStateOf(10) } + + Column(modifier = Modifier.padding(16.dp)) { + Text("Dynamic Chart", style = MaterialTheme.typography.titleMedium) + + LineChart( + data = state.data, + modifier = Modifier + .fillMaxWidth() + .height(200.dp), + state = state + ) + + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Button(onClick = { + if (dataPoints > 5) dataPoints-- + }) { + Text("- Points") + } + + Text("Points: $dataPoints") + + Button(onClick = { + if (dataPoints < 20) dataPoints++ + }) { + Text("+ Points") + } + } + + Button( + onClick = { + val entries = (0 until dataPoints).map { + Entry(it.toFloat(), (10..50).random().toFloat()) + } + val dataSet = LineDataSet(entries.toMutableList(), "Random Data") + state.data = LineData(dataSet) + }, + modifier = Modifier.fillMaxWidth() + ) { + Text("Refresh Data") + } + } +} + diff --git a/gradle.properties b/gradle.properties index bfc84f04c..2f17915e1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,10 +2,8 @@ android.enableJetifier=false android.useAndroidX=true org.gradle.jvmargs=-Xmx4092M -#SONATYPE_HOST=DEFAULT -# or when publishing to https://s01.oss.sonatype.org -SONATYPE_HOST=S01 +# OSSRH was shut down on June 30, 2025. Using CENTRAL_PORTAL instead. +SONATYPE_HOST=CENTRAL_PORTAL RELEASE_SIGNING_ENABLED=true POM_ARTIFACT_ID=chart android.nonTransitiveRClass=false -android.nonFinalResIds=false diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f8e1ee312..61285a659 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2b5..37f78a6af 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.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/screenShotScript b/screenShotScript index 80bd9ac62..0b1a330be 160000 --- a/screenShotScript +++ b/screenShotScript @@ -1 +1 @@ -Subproject commit 80bd9ac62359db716803f3375968d5be93dbdc76 +Subproject commit 0b1a330be01b6b37596974e5180b28f33cb29c07 diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-1SampleClick.png index aa352e882..4dcf270d4 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-1SampleClick.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-1SampleClick.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-11ToggleFilled.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-11ToggleFilled.png index 6069b8a6d..8ea2ae7b0 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-11ToggleFilled.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-11ToggleFilled.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-12ToggleCircles.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-12ToggleCircles.png index 8a020ca27..1798cddfb 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-12ToggleCircles.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-12ToggleCircles.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-14ToggleCubic.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-14ToggleCubic.png index e511715fb..1e9fd1aeb 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-14ToggleCubic.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-14ToggleCubic.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-15ToggleStepped.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-15ToggleStepped.png index 5b1a068de..2df5a92e0 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-15ToggleStepped.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-15ToggleStepped.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-16ToggleHorizontalCubic.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-16ToggleHorizontalCubic.png index a28071702..52a1a8886 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-16ToggleHorizontalCubic.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-16ToggleHorizontalCubic.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-1ToggleValues.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-1ToggleValues.png index 492809250..9d06b7c5c 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-1ToggleValues.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-1ToggleValues.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-5ToggleHighlight.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-5ToggleHighlight.png index a28071702..52a1a8886 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-5ToggleHighlight.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-5ToggleHighlight.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-6TogglePinchZoom.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-6TogglePinchZoom.png index a28071702..52a1a8886 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-6TogglePinchZoom.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-6TogglePinchZoom.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-7ToggleAutoScale.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-7ToggleAutoScale.png index a28071702..52a1a8886 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-7ToggleAutoScale.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-2menu-click-7ToggleAutoScale.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click.png index a28071702..52a1a8886 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click2020.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click2020.png index a28071702..52a1a8886 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click2020.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click2020.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click7070.png b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click7070.png index a28071702..52a1a8886 100644 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click7070.png and b/screenshotsToCompare9/StartTest_smokeTestStart-2-MultiLineChartActivity-Multiple-click7070.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-1SampleClick.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-1SampleClick.png rename to screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-1SampleClick.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleAutoScaleMinMax.png b/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleAutoScaleMinMax.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleAutoScaleMinMax.png rename to screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleAutoScaleMinMax.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleBarBorders.png b/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleBarBorders.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleBarBorders.png rename to screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleBarBorders.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleHighlight.png b/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleHighlight.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleHighlight.png rename to screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleHighlight.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleIcons.png b/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleIcons.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleIcons.png rename to screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleIcons.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-TogglePinchZoom.png b/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-TogglePinchZoom.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-TogglePinchZoom.png rename to screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-TogglePinchZoom.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleValues.png b/screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleValues.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-Horizontal-ToggleValues.png rename to screenshotsToCompare9/StartTest_smokeTestStart-40-HorizontalBarComposeActivity-HorizontalCompose-ToggleValues.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-1SampleClick.png new file mode 100644 index 000000000..590b5e930 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-1SampleClick.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleAutoScaleMinMax.png b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleAutoScaleMinMax.png new file mode 100644 index 000000000..bf8701cf4 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleAutoScaleMinMax.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleBarBorders.png b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleBarBorders.png new file mode 100644 index 000000000..5c63e6967 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleBarBorders.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleHighlight.png b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleHighlight.png new file mode 100644 index 000000000..bf8701cf4 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleHighlight.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleIcons.png b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleIcons.png new file mode 100644 index 000000000..514353397 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleIcons.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-TogglePinchZoom.png b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-TogglePinchZoom.png new file mode 100644 index 000000000..bf8701cf4 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-TogglePinchZoom.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleValues.png b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleValues.png new file mode 100644 index 000000000..940db2cf1 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-41-HorizontalBarFullComposeActivity-HorizontalFullCompose-ToggleValues.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-1SampleClick.png new file mode 100644 index 000000000..99ff68ec7 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-1SampleClick.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-ToggleAutoScaleMinMax.png b/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-ToggleAutoScaleMinMax.png new file mode 100644 index 000000000..4b1dbcba3 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-ToggleAutoScaleMinMax.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-TogglePinchZoom.png b/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-TogglePinchZoom.png new file mode 100644 index 000000000..4b1dbcba3 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-TogglePinchZoom.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-ToggleValues.png b/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-ToggleValues.png new file mode 100644 index 000000000..4b1dbcba3 Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-42-MultiLineComposeActivity-MultiLineCompose-ToggleValues.png differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-43-TimeLineActivity-Timeline-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-43-TimeLineActivity-Timeline-1SampleClick.png deleted file mode 100644 index 384161b18..000000000 Binary files a/screenshotsToCompare9/StartTest_smokeTestStart-43-TimeLineActivity-Timeline-1SampleClick.png and /dev/null differ diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-42-GradientActivity-Gradient-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-44-GradientActivity-Gradient-1SampleClick.png similarity index 100% rename from screenshotsToCompare9/StartTest_smokeTestStart-42-GradientActivity-Gradient-1SampleClick.png rename to screenshotsToCompare9/StartTest_smokeTestStart-44-GradientActivity-Gradient-1SampleClick.png diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeLineActivity-Timeline-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeLineActivity-Timeline-1SampleClick.png new file mode 100644 index 000000000..b291c3f2f Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-45-TimeLineActivity-Timeline-1SampleClick.png differ diff --git a/settings.gradle.kts b/settings.gradle.kts index 186ce51d7..2edcf4e5b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1,3 @@ include("chartLib") +include("chartLibCompose") include("app")