diff --git a/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt b/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt
index 37a12937f..5bbdbaf80 100644
--- a/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt
+++ b/app/src/androidTest/kotlin/info/appdev/chartexample/StartTest.kt
@@ -27,6 +27,7 @@ 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
@@ -185,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")
@@ -265,6 +276,7 @@ class StartTest {
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 2550255e3..648410d1e 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -27,6 +27,7 @@
+
diff --git a/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarFullComposeActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarFullComposeActivity.kt
index 0414bc142..414c1342a 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarFullComposeActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/compose/HorizontalBarFullComposeActivity.kt
@@ -24,6 +24,7 @@ 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
@@ -66,6 +67,15 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
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) }
@@ -73,7 +83,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
Scaffold(
topBar = {
TopAppBar(
- title = { Text(this.javaClass.simpleName.replace("Activity", "")) },
+ title = { Text("HorizontalBarFullCompose") },
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary
@@ -107,7 +117,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Toggle Values") },
onClick = {
showMenu = false
- toggleValues()
+ showValues = !showValues
},
modifier = Modifier.testTag("menuItem_Toggle Values")
)
@@ -115,7 +125,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Toggle Icons") },
onClick = {
showMenu = false
- toggleIcons()
+ showIcons = !showIcons
},
modifier = Modifier.testTag("menuItem_Toggle Icons")
)
@@ -123,7 +133,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Toggle Highlight") },
onClick = {
showMenu = false
- toggleHighlight()
+ highlightEnabled = !highlightEnabled
},
modifier = Modifier.testTag("menuItem_Toggle Highlight")
)
@@ -131,7 +141,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Toggle Pinch Zoom") },
onClick = {
showMenu = false
- togglePinchZoom()
+ pinchZoomEnabled = !pinchZoomEnabled
},
modifier = Modifier.testTag("menuItem_Toggle Pinch Zoom")
)
@@ -139,7 +149,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Toggle Auto Scale MinMax") },
onClick = {
showMenu = false
- toggleAutoScaleMinMax()
+ autoScaleMinMaxEnabled = !autoScaleMinMaxEnabled
},
modifier = Modifier.testTag("menuItem_Toggle Auto Scale MinMax")
)
@@ -147,7 +157,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Toggle Bar Borders") },
onClick = {
showMenu = false
- toggleBarBorders()
+ barBordersEnabled = !barBordersEnabled
},
modifier = Modifier.testTag("menuItem_Toggle Bar Borders")
)
@@ -155,7 +165,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Animate X") },
onClick = {
showMenu = false
- animateX()
+ animationTrigger++
},
modifier = Modifier.testTag("menuItem_Animate X")
)
@@ -163,7 +173,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Animate Y") },
onClick = {
showMenu = false
- animateY()
+ animationTrigger++
},
modifier = Modifier.testTag("menuItem_Animate Y")
)
@@ -171,7 +181,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
text = { Text("Animate XY") },
onClick = {
showMenu = false
- animateXY()
+ animationTrigger++
},
modifier = Modifier.testTag("menuItem_Animate XY")
)
@@ -196,51 +206,74 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
.background(Color.White)
) {
// Chart - Using Compose HorizontalBarChart
- val barData = remember(seekBarXValue, seekBarYValue) {
- createBarData(seekBarXValue.toInt(), seekBarYValue)
+ val barData = remember(
+ seekBarXValue,
+ seekBarYValue,
+ showIcons,
+ barBordersEnabled,
+ showValues
+ ) {
+ createBarData(
+ seekBarXValue.toInt(),
+ seekBarYValue,
+ showIcons,
+ barBordersEnabled,
+ showValues
+ )
}
- HorizontalBarChart(
- data = barData,
- modifier = Modifier
- .fillMaxWidth()
- .weight(1f),
- drawValueAboveBar = true,
- drawBarShadow = false,
- animationDuration = 2500,
- onValueSelected = { entry, highlight ->
- entry?.let {
- Timber.d("Selected: x=${it.x}, y=${it.y}")
+ 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
}
- },
- 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
- leftAxis.axisMinimum = 0f
- },
- rightAxisConfig = { rightAxis ->
- rightAxis.typeface = tfLight
- rightAxis.isDrawAxisLine = true
- rightAxis.isDrawGridLines = false
- 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(
@@ -289,7 +322,7 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
}
}
- private fun createBarData(count: Int, range: Float): BarData {
+ private fun createBarData(count: Int, range: Float, showIcons: Boolean, barBordersEnabled: Boolean, showValues: Boolean): BarData {
val barWidth = 9f
val spaceForBar = 10f
val values = ArrayList()
@@ -297,16 +330,25 @@ class HorizontalBarFullComposeActivity : DemoBaseCompose() {
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..