diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 21f507e72..7850e0641 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -56,6 +56,7 @@
+
diff --git a/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt b/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt
index 386f658e4..abf30e942 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/DataTools.kt
@@ -13,6 +13,8 @@ import info.appdev.charting.interfaces.dataprovider.LineDataProvider
import info.appdev.charting.interfaces.datasets.ILineDataSet
import info.appdev.charting.utils.getSDKInt
import timber.log.Timber
+import kotlin.math.PI
+import kotlin.math.sin
class DataTools {
companion object {
@@ -135,6 +137,19 @@ class DataTools {
fun getValues(size: Int) = VAL_102.copyOf(size)
+ /**
+ * Generates points for n complete sine waves.
+ * @param cycles Number of complete sine graphs (n)
+ * @param pointsPerCycle Number of data points to generate for each cycle
+ */
+ fun generateSineWaves(cycles: Int, pointsPerCycle: Int): List {
+ val totalPoints = cycles * pointsPerCycle
+ val stepSize = (2 * PI * cycles) / totalPoints
+
+ return (0 until totalPoints).map { i ->
+ sin(i * stepSize)
+ }
+ }
fun getMuchValues(size: Int): Array {
var result = VAL_102.copyOf(VAL_102.size)
while (result.size < size)
diff --git a/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt b/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt
new file mode 100644
index 000000000..4b7325eef
--- /dev/null
+++ b/app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt
@@ -0,0 +1,151 @@
+package info.appdev.chartexample
+
+import android.content.Intent
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Menu
+import android.view.MenuItem
+import androidx.core.net.toUri
+import info.appdev.chartexample.DataTools.Companion.generateSineWaves
+import info.appdev.chartexample.databinding.ActivityLinechartNoseekbarBinding
+import info.appdev.chartexample.formatter.UnixTimeAxisValueFormatter
+import info.appdev.chartexample.notimportant.DemoBase
+import info.appdev.charting.components.Description
+import info.appdev.charting.components.Legend.LegendForm
+import info.appdev.charting.components.XAxis.XAxisPosition
+import info.appdev.charting.components.YAxis
+import info.appdev.charting.data.Entry
+import info.appdev.charting.data.LineData
+import info.appdev.charting.data.LineDataSet
+import info.appdev.charting.formatter.IFillFormatter
+import info.appdev.charting.interfaces.dataprovider.LineDataProvider
+import info.appdev.charting.interfaces.datasets.ILineDataSet
+
+class TimeLineActivity : DemoBase() {
+ private lateinit var binding: ActivityLinechartNoseekbarBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityLinechartNoseekbarBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ binding.chart1.setBackgroundColor(Color.WHITE)
+ binding.chart1.setDrawGridBackground(true)
+
+ binding.chart1.setDrawBorders(true)
+
+ // no description text
+ binding.chart1.description = Description().apply {
+ text = "Sinus Time Line"
+ }
+ binding.chart1.description.isEnabled = true
+
+ binding.chart1.xAxis.apply {
+// enableGridDashedLine(10f, 10f, 0f)
+// this.position = XAxis.XAxisPosition.BOTH_SIDED
+ isEnabled = true
+ position = XAxisPosition.BOTTOM
+ typeface = tfLight
+ labelRotationAngle = 45f
+ setDrawGridLines(false)
+// granularity = 1f // only intervals of 1 day
+ labelCount = 7
+ valueFormatter = UnixTimeAxisValueFormatter("yyyy-MM-dd HH:mm:ss")
+ }
+
+ // if disabled, scaling can be done on x- and y-axis separately
+ binding.chart1.setPinchZoom(false)
+
+ binding.chart1.legend.apply {
+ isEnabled = false
+ form = LegendForm.LINE
+ }
+
+ binding.chart1.axisLeft.apply {
+ axisMaximum = 150f
+ axisMinimum = -50f
+ setDrawAxisLine(true)
+ setDrawZeroLine(true)
+ setDrawGridLines(true)
+ }
+
+ binding.chart1.axisRight.isEnabled = false
+
+ setData(60f, TIME_OFFSET)
+
+ binding.chart1.invalidate()
+ }
+
+ @Suppress("SameParameterValue")
+ private fun setData(range: Float, timeOffset: Long) {
+
+ val sampleEntries = generateSineWaves(3, 30)
+ .mapIndexed { index, data ->
+ val valueY = (data.toFloat() * range) + 50
+ Entry(timeOffset + index.toFloat() * 1000, valueY)
+ }.toMutableList()
+
+ val set1: LineDataSet
+
+ if (binding.chart1.lineData.dataSetCount > 0) {
+ set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet
+ set1.entries = sampleEntries
+ binding.chart1.lineData.notifyDataChanged()
+ binding.chart1.notifyDataSetChanged()
+ } else {
+ // create a dataset and give it a type
+ set1 = LineDataSet(sampleEntries, "DataSet 1")
+
+ set1.axisDependency = YAxis.AxisDependency.LEFT
+ set1.color = Color.rgb(255, 241, 46)
+ set1.isDrawCirclesEnabled = false
+ set1.lineWidth = 2f
+ set1.circleRadius = 3f
+ set1.fillAlpha = 255
+ set1.isDrawFilledEnabled = true
+ set1.fillColor = Color.WHITE
+ set1.highLightColor = Color.rgb(244, 117, 117)
+ set1.isDrawCircleHoleEnabled = false
+ set1.fillFormatter = object : IFillFormatter {
+ override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float {
+ // change the return value here to better understand the effect
+ // return 0;
+ return binding.chart1.axisLeft.axisMinimum
+ }
+ }
+
+ val dataSets = ArrayList()
+ dataSets.add(set1) // add the data sets
+
+ // create a data object with the data sets
+ val data = LineData(dataSets)
+ data.setDrawValues(false)
+
+ binding.chart1.setData(data)
+ }
+ }
+
+ override fun onCreateOptionsMenu(menu: Menu?): Boolean {
+ menuInflater.inflate(R.menu.only_github, menu)
+ return true
+ }
+
+ override fun onOptionsItemSelected(item: MenuItem): Boolean {
+ when (item.itemId) {
+ R.id.viewGithub -> {
+ val i = Intent(Intent.ACTION_VIEW)
+ i.data =
+ "https://github.com/AppDevNext/AndroidChart/blob/master/app/src/main/java/info/appdev/chartexample/FilledLineActivity.kt".toUri()
+ startActivity(i)
+ }
+ }
+
+ return true
+ }
+
+ public override fun saveToGallery() = Unit // Intentionally left empty
+
+ companion object {
+ const val TIME_OFFSET = 1767105583L
+ }
+}
diff --git a/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeAxisValueFormatter.kt b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeAxisValueFormatter.kt
new file mode 100644
index 000000000..3efeb6ae3
--- /dev/null
+++ b/app/src/main/kotlin/info/appdev/chartexample/formatter/UnixTimeAxisValueFormatter.kt
@@ -0,0 +1,16 @@
+package info.appdev.chartexample.formatter
+
+import info.appdev.charting.components.AxisBase
+import info.appdev.charting.formatter.IAxisValueFormatter
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class UnixTimeAxisValueFormatter(val format: String = "yyyy-MM-dd'T'HH:mm:ss'Z'") : IAxisValueFormatter {
+
+ val sdf = SimpleDateFormat(format, Locale.getDefault())
+
+ override fun getFormattedValue(value: Float, axis: AxisBase?): String {
+ return sdf.format(value * 1000L)
+ }
+
+}
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 fe41d4030..c8f06c2e3 100644
--- a/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt
+++ b/app/src/main/kotlin/info/appdev/chartexample/notimportant/MainActivity.kt
@@ -77,6 +77,7 @@ import info.appdev.chartexample.ScrollViewActivity
import info.appdev.chartexample.SpecificPositionsLineChartActivity
import info.appdev.chartexample.StackedBarActivity
import info.appdev.chartexample.StackedBarActivityNegative
+import info.appdev.chartexample.TimeLineActivity
import info.appdev.chartexample.compose.HorizontalBarComposeActivity
import info.appdev.chartexample.fragments.ViewPagerSimpleChartDemo
@@ -212,6 +213,7 @@ class MainActivity : ComponentActivity() {
add(ContentItem("Demonstrate and fix issues"))
add(ContentItem("Gradient", "Show a gradient edge case", GradientActivity::class.java))
+ add(ContentItem("Timeline", "Show a time line with Unix timestamp", TimeLineActivity::class.java))
}
}
}
diff --git a/screenshotsToCompare9/StartTest_smokeTestStart-43-TimeLineActivity-Timeline-1SampleClick.png b/screenshotsToCompare9/StartTest_smokeTestStart-43-TimeLineActivity-Timeline-1SampleClick.png
new file mode 100644
index 000000000..cd8c55ad3
Binary files /dev/null and b/screenshotsToCompare9/StartTest_smokeTestStart-43-TimeLineActivity-Timeline-1SampleClick.png differ