Skip to content

Commit 05d340d

Browse files
committed
TimeLineActivity with high precision
The problem is invariant generics. LineDataSet is LineDataSet<EntryFloat>, so entries expects MutableList<EntryFloat>. Even though EntryDouble : EntryFloat, MutableList<EntryDouble> is not a MutableList<EntryFloat> because MutableList is invariant (it's both a producer and consumer). MutableList<T> is invariant in T because it both reads (get() → T) and writes (add(T)). Even though EntryDouble : EntryFloat, the compiler must reject the assignment to prevent this kind of unsound write: val list: MutableList<EntryFloat> = values // if allowed... list.add(EntryFloat(1f, 2f)) // would corrupt the original EntryDouble list! The @Suppress("UNCHECKED_CAST") cast is safe here because: * JVM erases generics at runtime — both are just MutableList on the heap * LineDataSet only reads entries from the list as EntryFloat, and EntryDouble IS a EntryFloat * Nothing writes a plain EntryFloat back into the list through DataSet
1 parent b26855d commit 05d340d

File tree

1 file changed

+25
-17
lines changed

1 file changed

+25
-17
lines changed

app/src/main/kotlin/info/appdev/chartexample/TimeLineActivity.kt

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import info.appdev.charting.components.Description
1717
import info.appdev.charting.components.Legend.LegendForm
1818
import info.appdev.charting.components.XAxis.XAxisPosition
1919
import info.appdev.charting.components.YAxis
20+
import info.appdev.charting.data.EntryDouble
2021
import info.appdev.charting.data.EntryFloat
2122
import info.appdev.charting.data.LineData
2223
import info.appdev.charting.data.LineDataSet
@@ -96,38 +97,43 @@ class TimeLineActivity : DemoBase() {
9697

9798
val sampleEntries = if (sinus)
9899
generateSineWaves(3, 30).mapIndexed { index, data ->
99-
val valueY = (data.toFloat() * range) + 50
100-
EntryFloat(timeOffset + index.toFloat() * 1000, valueY)
100+
val valueY = (data * range) + 50
101+
EntryDouble(timeOffset + index * 1000.0, valueY)
101102
}
102103
else {
103-
var previousEntryFloat: EntryFloat? = null
104+
var previousEntryDouble: EntryDouble? = null
104105
getSawtoothValues(14).mapIndexed { index, data ->
105-
val valueY = data.toFloat() * 20
106-
val entryFloat = previousEntryFloat?.let {
106+
val valueY = data * 20
107+
val entryFloat = previousEntryDouble?.let {
107108
// nay third value is 0, so we add here more than 1 second, otherwise we have a one-second entry
108109
if (index % 3 == 0) {
109-
EntryFloat(it.x + 3000, valueY)
110+
EntryDouble(it.xDouble + 3000.0, valueY)
110111
} else
111-
EntryFloat(it.x + 1000, valueY)
112+
EntryDouble(it.xDouble + 1000.0, valueY)
112113
} ?: run {
113-
EntryFloat(timeOffset + index.toFloat() * 1000, valueY)
114+
EntryDouble(timeOffset + index.toFloat() * 1000.0, valueY)
114115
}
115-
previousEntryFloat = entryFloat
116+
previousEntryDouble = entryFloat
116117
// Now you can use 'prev' which holds the previous Entry
117118
entryFloat
118119
}
119120
}
120121

121122
val simpleDateFormat = SimpleDateFormat("HH:mm:ss.sss", Locale.getDefault())
122123
sampleEntries.forEach { entry ->
123-
val entryText = "Entry: x=${simpleDateFormat.format(entry.x)} x=${entry.x}, y=${entry.y}"
124+
val entryText = "Entry: x=${simpleDateFormat.format(entry.xDouble.toLong())} xDouble=${entry.xDouble}, y=${entry.y}"
124125
Timber.d(entryText)
125126
}
126127

127-
val set1: LineDataSet
128+
val set1: LineDataSet<EntryDouble>
128129

130+
/*The @Suppress("UNCHECKED_CAST") cast is safe here because:
131+
* JVM erases generics at runtime — both are just MutableList on the heap
132+
* LineDataSet only reads entries from the list as EntryFloat, and EntryDouble IS a EntryFloat
133+
* Nothing writes a plain EntryFloat back into the list through LineDataSet*/
129134
if (binding.chart1.lineData.dataSetCount > 0) {
130-
set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet
135+
@Suppress("UNCHECKED_CAST")
136+
set1 = binding.chart1.lineData.getDataSetByIndex(0) as LineDataSet<EntryDouble>
131137
set1.entries = sampleEntries.toMutableList()
132138
if (sinus)
133139
set1.lineMode = LineDataSet.Mode.LINEAR
@@ -153,15 +159,17 @@ class TimeLineActivity : DemoBase() {
153159
set1.highLightColor = Color.rgb(244, 117, 117)
154160
set1.isDrawCircleHoleEnabled = false
155161
set1.fillFormatter = object : IFillFormatter {
156-
override fun getFillLinePosition(dataSet: ILineDataSet?, dataProvider: LineDataProvider): Float {
162+
override fun getFillLinePosition(dataSet: ILineDataSet<*>?, dataProvider: LineDataProvider): Float {
157163
// change the return value here to better understand the effect
158164
// return 0;
159165
return binding.chart1.axisLeft.axisMinimum
160166
}
161167
}
162168

163-
val dataSets = ArrayList<ILineDataSet>()
164-
dataSets.add(set1) // add the data sets
169+
@Suppress("UNCHECKED_CAST")
170+
val dataSets = ArrayList<ILineDataSet<EntryFloat>>()
171+
@Suppress("UNCHECKED_CAST")
172+
dataSets.add(set1 as ILineDataSet<EntryFloat>) // add the data sets
165173

166174
// create a data object with the data sets
167175
val data = LineData(dataSets)
@@ -209,9 +217,9 @@ class TimeLineActivity : DemoBase() {
209217
val first = this[0]
210218
val second = this[1] // needed to get time diff
211219
val last = this[size - 1]
212-
val timeDiff = (second as EntryFloat).x - (first as EntryFloat).x
220+
val timeDiff = (second as EntryDouble).xDouble - (first as EntryDouble).xDouble
213221
removeAt(0)
214-
first.x = (last as EntryFloat).x + timeDiff
222+
(first as EntryDouble).xDouble = (last as EntryDouble).xDouble + timeDiff
215223
add(first)
216224
}
217225

0 commit comments

Comments
 (0)