mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-22 10:52:13 +00:00
feat: Moved from in chart to the library one
This commit is contained in:
parent
854195abed
commit
7875271b7e
@ -130,7 +130,7 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// Dzeio Charts
|
// Dzeio Charts
|
||||||
implementation(project(":charts"))
|
implementation("com.dzeio:charts:edd78e87e1")
|
||||||
|
|
||||||
// Dzeio Crash Handler
|
// Dzeio Crash Handler
|
||||||
implementation("com.dzeio:crashhandler:1.0.1")
|
implementation("com.dzeio:crashhandler:1.0.1")
|
||||||
@ -139,7 +139,7 @@ dependencies {
|
|||||||
implementation("androidx.core:core-ktx:1.9.0")
|
implementation("androidx.core:core-ktx:1.9.0")
|
||||||
implementation("androidx.appcompat:appcompat:1.7.0-alpha01")
|
implementation("androidx.appcompat:appcompat:1.7.0-alpha01")
|
||||||
implementation("javax.inject:javax.inject:1")
|
implementation("javax.inject:javax.inject:1")
|
||||||
implementation("com.google.android.material:material:1.8.0-beta01")
|
implementation("com.google.android.material:material:1.8.0-rc01")
|
||||||
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
|
||||||
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
|
implementation("androidx.lifecycle:lifecycle-livedata-ktx:2.5.1")
|
||||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
|
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1")
|
||||||
|
Binary file not shown.
@ -8,6 +8,9 @@ import com.dzeio.openhealth.core.BaseViewHolder
|
|||||||
import com.dzeio.openhealth.data.food.Food
|
import com.dzeio.openhealth.data.food.Food
|
||||||
import com.dzeio.openhealth.databinding.ItemFoodBinding
|
import com.dzeio.openhealth.databinding.ItemFoodBinding
|
||||||
import com.dzeio.openhealth.utils.DownloadImageTask
|
import com.dzeio.openhealth.utils.DownloadImageTask
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
@ -23,6 +26,11 @@ class FoodAdapter : BaseAdapter<Food, ItemFoodBinding>() {
|
|||||||
item: Food,
|
item: Food,
|
||||||
position: Int
|
position: Int
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Download remote picture
|
// Download remote picture
|
||||||
DownloadImageTask(holder.binding.productImage).execute(item.image)
|
DownloadImageTask(holder.binding.productImage).execute(item.image)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.dzeio.openhealth.data.food
|
package com.dzeio.openhealth.data.food
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.room.Entity
|
import androidx.room.Entity
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import com.dzeio.openhealth.data.openfoodfact.OFFProduct
|
import com.dzeio.openhealth.data.openfoodfact.OFFProduct
|
||||||
@ -63,7 +64,11 @@ data class Food(
|
|||||||
/**
|
/**
|
||||||
* Transform an OpenFoodFact product to use for our Database
|
* Transform an OpenFoodFact product to use for our Database
|
||||||
*/
|
*/
|
||||||
fun fromOpenFoodFact(food: OFFProduct, quantity: Float? = null): Food {
|
fun fromOpenFoodFact(food: OFFProduct, quantity: Float? = null): Food? {
|
||||||
|
// filter out foods that we can't use in the app
|
||||||
|
if (food.name == null || ((food.servingSize == null || food.servingSize == "") && (food.quantity == null || food.quantity == "") && food.servingQuantity == null && food.productQuantity == null)) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
// try to know how much was eaten by the user if not said
|
// try to know how much was eaten by the user if not said
|
||||||
var eaten = quantity ?: food.servingQuantity ?: food.productQuantity ?: 0f
|
var eaten = quantity ?: food.servingQuantity ?: food.productQuantity ?: 0f
|
||||||
@ -72,10 +77,13 @@ data class Food(
|
|||||||
eaten = food.servingQuantity!!
|
eaten = food.servingQuantity!!
|
||||||
} else if (food.productQuantity != null && food.productQuantity != 0f) {
|
} else if (food.productQuantity != null && food.productQuantity != 0f) {
|
||||||
eaten = food.productQuantity!!
|
eaten = food.productQuantity!!
|
||||||
|
} else if (food.servingSize != null || food.quantity != null) {
|
||||||
|
Log.d("pouet", ".${food.servingSize ?: food.quantity}. .${(food.servingSize ?: food.quantity)!!.replace(Regex(" +\\w+$"), "")}. ${food}")
|
||||||
|
eaten = (food.servingSize ?: food.quantity)!!.trim().replace(Regex(" +\\w+$"), "").toInt().toFloat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Food(
|
return Food(
|
||||||
name = food.name,
|
name = food.name!!,
|
||||||
// do some slight edit on the serving to remove strange entries like `100 g`
|
// do some slight edit on the serving to remove strange entries like `100 g`
|
||||||
serving = (food.servingSize ?: food.quantity ?: "unknown").replace(Regex(" +"), ""),
|
serving = (food.servingSize ?: food.quantity ?: "unknown").replace(Regex(" +"), ""),
|
||||||
quantity = eaten,
|
quantity = eaten,
|
||||||
|
@ -13,7 +13,7 @@ data class OFFProduct(
|
|||||||
* the product name
|
* the product name
|
||||||
*/
|
*/
|
||||||
@SerializedName("product_name")
|
@SerializedName("product_name")
|
||||||
var name: String,
|
var name: String?,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the size of a serving
|
* the size of a serving
|
||||||
|
@ -4,7 +4,7 @@ import android.graphics.Color
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import com.dzeio.openhealth.data.weight.Weight
|
import com.dzeio.openhealth.data.weight.Weight
|
||||||
import com.dzeio.openhealth.units.Units
|
import com.dzeio.openhealth.units.Units
|
||||||
import com.dzeio.openhealth.utils.GraphUtils
|
import com.dzeio.openhealth.utils.ChartUtils
|
||||||
import com.github.mikephil.charting.charts.LineChart
|
import com.github.mikephil.charting.charts.LineChart
|
||||||
import com.github.mikephil.charting.components.LimitLine
|
import com.github.mikephil.charting.components.LimitLine
|
||||||
import com.github.mikephil.charting.components.YAxis
|
import com.github.mikephil.charting.components.YAxis
|
||||||
@ -29,7 +29,7 @@ object WeightChart {
|
|||||||
goal: Float?,
|
goal: Float?,
|
||||||
limit: Boolean = true
|
limit: Boolean = true
|
||||||
) {
|
) {
|
||||||
GraphUtils.lineChartSetup(
|
ChartUtils.lineChartSetup(
|
||||||
chart,
|
chart,
|
||||||
MaterialColors.getColor(
|
MaterialColors.getColor(
|
||||||
view,
|
view,
|
||||||
@ -84,7 +84,7 @@ object WeightChart {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val rawData = GraphUtils.lineDataSet(
|
val rawData = ChartUtils.lineDataSet(
|
||||||
LineDataSet(
|
LineDataSet(
|
||||||
data.mapIndexed { _, weight ->
|
data.mapIndexed { _, weight ->
|
||||||
return@mapIndexed Entry(
|
return@mapIndexed Entry(
|
||||||
@ -98,7 +98,7 @@ object WeightChart {
|
|||||||
axisDependency = YAxis.AxisDependency.RIGHT
|
axisDependency = YAxis.AxisDependency.RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
val averageData = GraphUtils.lineDataSet(LineDataSet(averageYs, "Average")).apply {
|
val averageData = ChartUtils.lineDataSet(LineDataSet(averageYs, "Average")).apply {
|
||||||
axisDependency = YAxis.AxisDependency.RIGHT
|
axisDependency = YAxis.AxisDependency.RIGHT
|
||||||
color = Color.GREEN
|
color = Color.GREEN
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import com.dzeio.openhealth.data.food.FoodRepository
|
|||||||
import com.dzeio.openhealth.data.openfoodfact.OpenFoodFactService
|
import com.dzeio.openhealth.data.openfoodfact.OpenFoodFactService
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.util.ArrayList
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@ -23,9 +24,10 @@ class SearchFoodDialogViewModel @Inject internal constructor(
|
|||||||
val product = response.body()
|
val product = response.body()
|
||||||
if (product != null) {
|
if (product != null) {
|
||||||
|
|
||||||
items.postValue(product.products
|
items.postValue(
|
||||||
.filter { it.name != null }
|
product.products
|
||||||
.map { Food.fromOpenFoodFact(it) }
|
.map { Food.fromOpenFoodFact(it) }
|
||||||
|
.filter { it != null } as List<Food>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import com.dzeio.openhealth.graphs.WeightChart
|
|||||||
import com.dzeio.openhealth.ui.weight.WeightDialog
|
import com.dzeio.openhealth.ui.weight.WeightDialog
|
||||||
import com.dzeio.openhealth.units.Units
|
import com.dzeio.openhealth.units.Units
|
||||||
import com.dzeio.openhealth.utils.DrawUtils
|
import com.dzeio.openhealth.utils.DrawUtils
|
||||||
import com.dzeio.openhealth.utils.GraphUtils
|
import com.dzeio.openhealth.utils.ChartUtils
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
@ -47,7 +47,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
|
|||||||
/**
|
/**
|
||||||
* Water Intake
|
* Water Intake
|
||||||
*/
|
*/
|
||||||
|
|
||||||
binding.fragmentHomeWaterAdd.setOnClickListener {
|
binding.fragmentHomeWaterAdd.setOnClickListener {
|
||||||
val water = viewModel.water.value
|
val water = viewModel.water.value
|
||||||
if (water == null || !water.isToday()) {
|
if (water == null || !water.isToday()) {
|
||||||
@ -91,7 +90,7 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Make a line Chart using the graph library
|
// Make a line Chart using the graph library
|
||||||
GraphUtils.lineChartSetup(
|
ChartUtils.lineChartSetup(
|
||||||
binding.weightGraph,
|
binding.weightGraph,
|
||||||
MaterialColors.getColor(
|
MaterialColors.getColor(
|
||||||
requireView(),
|
requireView(),
|
||||||
|
@ -11,7 +11,7 @@ import com.dzeio.openhealth.Application
|
|||||||
import com.dzeio.openhealth.adapters.StepsAdapter
|
import com.dzeio.openhealth.adapters.StepsAdapter
|
||||||
import com.dzeio.openhealth.core.BaseFragment
|
import com.dzeio.openhealth.core.BaseFragment
|
||||||
import com.dzeio.openhealth.databinding.FragmentStepsHomeBinding
|
import com.dzeio.openhealth.databinding.FragmentStepsHomeBinding
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.dzeio.openhealth.utils.ChartUtils
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
@ -53,46 +53,16 @@ class StepsHomeFragment :
|
|||||||
val chart = binding.chart
|
val chart = binding.chart
|
||||||
|
|
||||||
// setup serie
|
// setup serie
|
||||||
val serie = BarSerie(chart).apply {
|
val serie = BarSerie(chart)
|
||||||
barPaint.color = MaterialColors.getColor(
|
|
||||||
requireView(),
|
|
||||||
com.google.android.material.R.attr.colorPrimary
|
|
||||||
)
|
|
||||||
textPaint.color = MaterialColors.getColor(
|
|
||||||
requireView(),
|
|
||||||
com.google.android.material.R.attr.colorOnPrimary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val errorColor = MaterialColors.getColor(
|
|
||||||
requireView(),
|
|
||||||
com.google.android.material.R.attr.colorError
|
|
||||||
)
|
|
||||||
|
|
||||||
chart.apply {
|
chart.apply {
|
||||||
series = arrayListOf(serie)
|
ChartUtils.materielTheme(chart, requireView())
|
||||||
// debug = true
|
|
||||||
|
|
||||||
yAxis.apply {
|
yAxis.apply {
|
||||||
textLabel.color = MaterialColors.getColor(
|
|
||||||
requireView(),
|
|
||||||
com.google.android.material.R.attr.colorOnPrimaryContainer
|
|
||||||
)
|
|
||||||
linePaint.color = MaterialColors.getColor(
|
|
||||||
requireView(),
|
|
||||||
com.google.android.material.R.attr.colorOnPrimaryContainer
|
|
||||||
)
|
|
||||||
goalLinePaint.color = errorColor
|
|
||||||
//
|
|
||||||
onValueFormat = { value -> "${value.toInt()}" }
|
onValueFormat = { value -> "${value.toInt()}" }
|
||||||
}
|
}
|
||||||
|
|
||||||
xAxis.apply {
|
xAxis.apply {
|
||||||
dataWidth = 604800000.0
|
dataWidth = 604800000.0
|
||||||
textPaint.color = MaterialColors.getColor(
|
|
||||||
requireView(),
|
|
||||||
com.google.android.material.R.attr.colorOnPrimaryContainer
|
|
||||||
)
|
|
||||||
textPaint.textSize = 32f
|
textPaint.textSize = 32f
|
||||||
onValueFormat = onValueFormat@{
|
onValueFormat = onValueFormat@{
|
||||||
val formatter = DateFormat.getDateTimeInstance(
|
val formatter = DateFormat.getDateTimeInstance(
|
||||||
@ -107,8 +77,10 @@ class StepsHomeFragment :
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewModel.goal.observe(viewLifecycleOwner) {
|
viewModel.goal.observe(viewLifecycleOwner) {
|
||||||
chart.yAxis.setGoalLine(it?.toFloat())
|
if (it != null) {
|
||||||
chart.refresh()
|
chart.yAxis.addLine(it.toFloat())
|
||||||
|
chart.refresh()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.items.observe(viewLifecycleOwner) { list ->
|
viewModel.items.observe(viewLifecycleOwner) { list ->
|
||||||
@ -118,16 +90,8 @@ class StepsHomeFragment :
|
|||||||
return@observe
|
return@observe
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// chart.animation.enabled = false
|
|
||||||
// chart.animation.refreshRate = 60
|
|
||||||
// chart.animation.duration = 300
|
|
||||||
|
|
||||||
// chart.scroller.zoomEnabled = false
|
// chart.scroller.zoomEnabled = false
|
||||||
|
|
||||||
// chart.xAxis.labels.size = 32f
|
|
||||||
|
|
||||||
val entries: HashMap<Long, Entry> = HashMap()
|
val entries: HashMap<Long, Entry> = HashMap()
|
||||||
|
|
||||||
list.forEach {
|
list.forEach {
|
||||||
@ -138,7 +102,7 @@ class StepsHomeFragment :
|
|||||||
cal.set(Calendar.AM_PM, Calendar.AM)
|
cal.set(Calendar.AM_PM, Calendar.AM)
|
||||||
val ts = cal.timeInMillis
|
val ts = cal.timeInMillis
|
||||||
if (!entries.containsKey(ts)) {
|
if (!entries.containsKey(ts)) {
|
||||||
entries[ts] = Entry((ts).toDouble(), 0F, errorColor)
|
entries[ts] = Entry((ts).toDouble(), 0F, chart.yAxis.goalLinePaint.color)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries[ts]!!.y += it.value.toFloat()
|
entries[ts]!!.y += it.value.toFloat()
|
||||||
|
@ -6,15 +6,16 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
|
import com.dzeio.charts.Entry
|
||||||
|
import com.dzeio.charts.series.BarSerie
|
||||||
import com.dzeio.openhealth.adapters.WaterAdapter
|
import com.dzeio.openhealth.adapters.WaterAdapter
|
||||||
import com.dzeio.openhealth.core.BaseFragment
|
import com.dzeio.openhealth.core.BaseFragment
|
||||||
import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding
|
import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding
|
||||||
import com.dzeio.openhealth.utils.GraphUtils
|
import com.dzeio.openhealth.utils.ChartUtils
|
||||||
import com.github.mikephil.charting.data.BarData
|
|
||||||
import com.github.mikephil.charting.data.BarDataSet
|
|
||||||
import com.github.mikephil.charting.data.BarEntry
|
|
||||||
import com.google.android.material.color.MaterialColors
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class WaterHomeFragment :
|
class WaterHomeFragment :
|
||||||
@ -45,39 +46,45 @@ class WaterHomeFragment :
|
|||||||
|
|
||||||
val chart = binding.chart
|
val chart = binding.chart
|
||||||
|
|
||||||
GraphUtils.barChartSetup(
|
val serie = BarSerie(chart)
|
||||||
chart,
|
|
||||||
MaterialColors.getColor(
|
chart.apply {
|
||||||
requireView(),
|
ChartUtils.materielTheme(chart, requireView())
|
||||||
com.google.android.material.R.attr.colorPrimary
|
|
||||||
),
|
yAxis.apply {
|
||||||
MaterialColors.getColor(
|
// onValueFormat
|
||||||
requireView(),
|
}
|
||||||
com.google.android.material.R.attr.colorOnBackground
|
|
||||||
)
|
xAxis.apply {
|
||||||
)
|
dataWidth = 604800000.0
|
||||||
|
textPaint.textSize = 32f
|
||||||
|
onValueFormat = onValueFormat@{
|
||||||
|
return@onValueFormat SimpleDateFormat(
|
||||||
|
"yyyy-MM-dd",
|
||||||
|
Locale.getDefault()
|
||||||
|
).format(Date(it.toLong()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
binding.buttonEditDefaultIntake.setOnClickListener {
|
binding.buttonEditDefaultIntake.setOnClickListener {
|
||||||
findNavController().navigate(WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterSizeDialog())
|
findNavController().navigate(WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterSizeDialog())
|
||||||
}
|
}
|
||||||
|
|
||||||
chart.xAxis.valueFormatter = GraphUtils.DateValueFormatter(1000 * 60 * 60 * 24)
|
|
||||||
|
|
||||||
viewModel.items.observe(viewLifecycleOwner) { list ->
|
viewModel.items.observe(viewLifecycleOwner) { list ->
|
||||||
adapter.set(list)
|
adapter.set(list)
|
||||||
|
|
||||||
val dataset = BarDataSet(
|
val dataset = list.map {
|
||||||
list.map {
|
return@map Entry(
|
||||||
return@map BarEntry(
|
it.timestamp.toDouble(),
|
||||||
(it.timestamp / 1000 / 60 / 60 / 24).toFloat(),
|
it.value.toFloat()
|
||||||
it.value.toFloat()
|
)
|
||||||
)
|
}
|
||||||
},
|
|
||||||
""
|
|
||||||
)
|
|
||||||
|
|
||||||
chart.data = BarData(dataset)
|
serie.entries = dataset as ArrayList<Entry>
|
||||||
chart.invalidate()
|
chart.xAxis.x = dataset[0].x
|
||||||
|
chart.refresh()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package com.dzeio.openhealth.utils
|
package com.dzeio.openhealth.utils
|
||||||
|
|
||||||
import com.github.mikephil.charting.charts.BarChart
|
import android.view.View
|
||||||
|
import com.dzeio.charts.ChartView
|
||||||
|
import com.dzeio.charts.series.BarSerie
|
||||||
|
import com.dzeio.charts.series.LineSerie
|
||||||
import com.github.mikephil.charting.charts.BarLineChartBase
|
import com.github.mikephil.charting.charts.BarLineChartBase
|
||||||
import com.github.mikephil.charting.charts.LineChart
|
import com.github.mikephil.charting.charts.LineChart
|
||||||
import com.github.mikephil.charting.components.AxisBase
|
import com.github.mikephil.charting.components.AxisBase
|
||||||
@ -11,6 +14,7 @@ import com.github.mikephil.charting.data.Entry
|
|||||||
import com.github.mikephil.charting.data.LineDataSet
|
import com.github.mikephil.charting.data.LineDataSet
|
||||||
import com.github.mikephil.charting.formatter.ValueFormatter
|
import com.github.mikephil.charting.formatter.ValueFormatter
|
||||||
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet
|
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet
|
||||||
|
import com.google.android.material.color.MaterialColors
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -20,7 +24,7 @@ import java.util.Locale
|
|||||||
*
|
*
|
||||||
* TODO: migrate to DzeioCharts once it is ready
|
* TODO: migrate to DzeioCharts once it is ready
|
||||||
*/
|
*/
|
||||||
object GraphUtils {
|
object ChartUtils {
|
||||||
|
|
||||||
fun lineChartSetup(chart: LineChart, mainColor: Int, textColor: Int) {
|
fun lineChartSetup(chart: LineChart, mainColor: Int, textColor: Int) {
|
||||||
barLineChartSetup(chart, mainColor, textColor)
|
barLineChartSetup(chart, mainColor, textColor)
|
||||||
@ -35,10 +39,6 @@ object GraphUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun barChartSetup(chart: BarChart, mainColor: Int, textColor: Int) {
|
|
||||||
barLineChartSetup(chart, mainColor, textColor)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T : BarLineScatterCandleBubbleData<out IBarLineScatterCandleBubbleDataSet<out Entry>>?> barLineChartSetup(
|
private fun <T : BarLineScatterCandleBubbleData<out IBarLineScatterCandleBubbleDataSet<out Entry>>?> barLineChartSetup(
|
||||||
chart: BarLineChartBase<T>,
|
chart: BarLineChartBase<T>,
|
||||||
mainColor: Int,
|
mainColor: Int,
|
||||||
@ -101,4 +101,62 @@ object GraphUtils {
|
|||||||
// return super.getAxisLabel(value, axis)
|
// return super.getAxisLabel(value, axis)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply Material theme to a DzeioChart [ChartView]
|
||||||
|
*/
|
||||||
|
fun materielTheme(chart: ChartView, view: View) {
|
||||||
|
|
||||||
|
val errorColor = MaterialColors.getColor(
|
||||||
|
view,
|
||||||
|
com.google.android.material.R.attr.colorError
|
||||||
|
)
|
||||||
|
|
||||||
|
chart.apply {
|
||||||
|
yAxis.apply {
|
||||||
|
textLabel.color = MaterialColors.getColor(
|
||||||
|
view,
|
||||||
|
com.google.android.material.R.attr.colorOnPrimaryContainer
|
||||||
|
)
|
||||||
|
linePaint.color = MaterialColors.getColor(
|
||||||
|
view,
|
||||||
|
com.google.android.material.R.attr.colorOnPrimaryContainer
|
||||||
|
)
|
||||||
|
goalLinePaint.color = errorColor
|
||||||
|
}
|
||||||
|
|
||||||
|
xAxis.apply {
|
||||||
|
textPaint.color = MaterialColors.getColor(
|
||||||
|
view,
|
||||||
|
com.google.android.material.R.attr.colorOnPrimaryContainer
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (serie in series) {
|
||||||
|
if (serie is BarSerie) {
|
||||||
|
serie.apply {
|
||||||
|
barPaint.color = MaterialColors.getColor(
|
||||||
|
view,
|
||||||
|
com.google.android.material.R.attr.colorPrimary
|
||||||
|
)
|
||||||
|
textPaint.color = MaterialColors.getColor(
|
||||||
|
view,
|
||||||
|
com.google.android.material.R.attr.colorOnPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else if (serie is LineSerie) {
|
||||||
|
serie.apply {
|
||||||
|
linePaint.color = MaterialColors.getColor(
|
||||||
|
view,
|
||||||
|
com.google.android.material.R.attr.colorPrimary
|
||||||
|
)
|
||||||
|
textPaint.color = MaterialColors.getColor(
|
||||||
|
view,
|
||||||
|
com.google.android.material.R.attr.colorOnPrimary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<com.github.mikephil.charting.charts.BarChart
|
<com.dzeio.charts.ChartView
|
||||||
android:id="@+id/chart"
|
android:id="@+id/chart"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="200dp"
|
android:layout_height="200dp"
|
||||||
|
1
charts/.gitignore
vendored
1
charts/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
/build
|
|
@ -1,3 +0,0 @@
|
|||||||
# Dzeio Charts
|
|
||||||
|
|
||||||
Originally from https://github.com/HackPlan/AndroidCharts but We wanted more options :D
|
|
@ -1,34 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("com.android.library")
|
|
||||||
kotlin("android")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
compileSdk = 33
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 21
|
|
||||||
targetSdk = 33
|
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
consumerProguardFiles("consumer-rules.pro")
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
isMinifyEnabled = true
|
|
||||||
proguardFiles(
|
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
||||||
"proguard-rules.pro"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility = JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "1.8"
|
|
||||||
}
|
|
||||||
namespace = "com.dzeio.charts"
|
|
||||||
}
|
|
21
charts/proguard-rules.pro
vendored
21
charts/proguard-rules.pro
vendored
@ -1,21 +0,0 @@
|
|||||||
# Add project specific ProGuard rules here.
|
|
||||||
# You can control the set of applied configuration files using the
|
|
||||||
# proguardFiles setting in build.gradle.kts.
|
|
||||||
#
|
|
||||||
# For more details, see
|
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
|
||||||
# class:
|
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
|
||||||
# public *;
|
|
||||||
#}
|
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
|
||||||
# debugging stack traces.
|
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
|
||||||
# hide the original source file name.
|
|
||||||
#-renamesourcefileattribute SourceFile
|
|
@ -1,24 +0,0 @@
|
|||||||
package com.dzeio.charts
|
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instrumented test, which will execute on an Android device.
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
|
||||||
class ExampleInstrumentedTest {
|
|
||||||
@Test
|
|
||||||
fun useAppContext() {
|
|
||||||
// Context of the app under test.
|
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
|
||||||
assertEquals("com.dzeio.charts.test", appContext.packageName)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest></manifest>
|
|
@ -1,66 +0,0 @@
|
|||||||
package com.dzeio.charts
|
|
||||||
|
|
||||||
import kotlin.math.abs
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
data class Animation(
|
|
||||||
/**
|
|
||||||
* Enable / Disable the Chart Animations
|
|
||||||
*/
|
|
||||||
var enabled: Boolean = true,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of milliseconds the animation is running before it ends
|
|
||||||
*/
|
|
||||||
var duration: Int = 1000,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Number of updates per seconds
|
|
||||||
*/
|
|
||||||
var refreshRate: Int = 50
|
|
||||||
) {
|
|
||||||
/**
|
|
||||||
* Update the value depending on the maximum obtainable value
|
|
||||||
*
|
|
||||||
* @param maxValue the maximum value the item can obtain
|
|
||||||
* @param targetValue the value you want to obtain at the end of the animation
|
|
||||||
* @param currentValue the current value
|
|
||||||
*
|
|
||||||
* @return the new updated value
|
|
||||||
*/
|
|
||||||
fun updateValue(
|
|
||||||
maxValue: Float,
|
|
||||||
targetValue: Float,
|
|
||||||
currentValue: Float,
|
|
||||||
minValue: Float,
|
|
||||||
minStep: Float
|
|
||||||
): Float {
|
|
||||||
if (!enabled) {
|
|
||||||
return targetValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentValue < minValue) {
|
|
||||||
return minValue
|
|
||||||
}
|
|
||||||
|
|
||||||
val moveValue = max(minStep, (maxValue - targetValue) / refreshRate)
|
|
||||||
|
|
||||||
var result = targetValue
|
|
||||||
if (currentValue < targetValue) {
|
|
||||||
result = currentValue + moveValue
|
|
||||||
} else if (currentValue > targetValue) {
|
|
||||||
result = currentValue - moveValue
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
abs(targetValue - currentValue) <= moveValue ||
|
|
||||||
result < minValue ||
|
|
||||||
result > maxValue
|
|
||||||
) {
|
|
||||||
return targetValue
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getDelay() = this.duration / this.refreshRate
|
|
||||||
}
|
|
@ -1,150 +0,0 @@
|
|||||||
package com.dzeio.charts
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.RectF
|
|
||||||
import android.util.AttributeSet
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.View
|
|
||||||
import com.dzeio.charts.axis.XAxis
|
|
||||||
import com.dzeio.charts.axis.YAxis
|
|
||||||
import com.dzeio.charts.components.ChartScroll
|
|
||||||
import com.dzeio.charts.series.SerieInterface
|
|
||||||
|
|
||||||
class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
|
|
||||||
View(context, attrs), ChartViewInterface {
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val TAG = "Charts/ChartView"
|
|
||||||
}
|
|
||||||
|
|
||||||
override var debug: Boolean = false
|
|
||||||
|
|
||||||
override val xAxis = XAxis(this)
|
|
||||||
|
|
||||||
override val yAxis = YAxis(this)
|
|
||||||
|
|
||||||
override var series: ArrayList<SerieInterface> = arrayListOf()
|
|
||||||
|
|
||||||
override var padding: Float = 8f
|
|
||||||
|
|
||||||
private val scroller = ChartScroll(this).apply {
|
|
||||||
var lastMovement = 0.0
|
|
||||||
setOnChartMoved { movementX, _ ->
|
|
||||||
xAxis.x += (movementX - lastMovement) * xAxis.getDataWidth() / width
|
|
||||||
lastMovement = movementX.toDouble()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
// setOnZoomChanged {
|
|
||||||
// Log.d(TAG, "New Zoom: $it")
|
|
||||||
// zoom = (it * 1.2).toFloat()
|
|
||||||
// refresh()
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// val animator: Runnable = object : Runnable {
|
|
||||||
// override fun run() {
|
|
||||||
// var needNewFrame = false
|
|
||||||
// for (serie in series) {
|
|
||||||
// val result = serie.onUpdate()
|
|
||||||
// if (result) {
|
|
||||||
// needNewFrame = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// if (needNewFrame) {
|
|
||||||
// postDelayed(this, animation.getDelay().toLong())
|
|
||||||
// }
|
|
||||||
// invalidate()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// rect used for calculations
|
|
||||||
private val rect = RectF()
|
|
||||||
|
|
||||||
// stroke used while in debug
|
|
||||||
private val debugStrokePaint = Paint().apply {
|
|
||||||
style = Paint.Style.STROKE
|
|
||||||
strokeWidth = 8f
|
|
||||||
color = Color.parseColor("#654321")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun refresh() {
|
|
||||||
// run Axis logics
|
|
||||||
xAxis.refresh()
|
|
||||||
yAxis.refresh()
|
|
||||||
|
|
||||||
// run series logic
|
|
||||||
for (serie in series) {
|
|
||||||
serie.refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
// invalidate the view
|
|
||||||
invalidate()
|
|
||||||
// removeCallbacks(animator)
|
|
||||||
// post(animator)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
|
||||||
|
|
||||||
// don't draw anything if everything is empty
|
|
||||||
if (series.isEmpty() || series.maxOf { it.entries.size } == 0) {
|
|
||||||
super.onDraw(canvas)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (debug) {
|
|
||||||
// draw corners
|
|
||||||
canvas.drawRect(rect.apply {
|
|
||||||
set(
|
|
||||||
padding / 2,
|
|
||||||
padding / 2,
|
|
||||||
width.toFloat() - padding / 2,
|
|
||||||
height.toFloat() - padding / 2
|
|
||||||
)
|
|
||||||
}, debugStrokePaint)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
val bottom = xAxis.onDraw(canvas, rect.apply {
|
|
||||||
set(padding, 0f, width.toFloat() - padding, height.toFloat() - padding)
|
|
||||||
})
|
|
||||||
|
|
||||||
// right distance from the yAxis
|
|
||||||
val rightDistance = yAxis.onDraw(canvas, rect.apply {
|
|
||||||
set(padding, padding, width.toFloat() - padding, height.toFloat() - bottom - padding)
|
|
||||||
})
|
|
||||||
|
|
||||||
// chart draw rectangle
|
|
||||||
rect.apply {
|
|
||||||
set(
|
|
||||||
padding,
|
|
||||||
padding,
|
|
||||||
width.toFloat() - padding - rightDistance,
|
|
||||||
height - bottom - padding
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
for (serie in series) {
|
|
||||||
serie.onDraw(canvas, rect)
|
|
||||||
}
|
|
||||||
super.onDraw(canvas)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("ClickableViewAccessibility")
|
|
||||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
|
||||||
performClick()
|
|
||||||
return scroller.onTouchEvent(event)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDataset(): ArrayList<Entry> {
|
|
||||||
val data: ArrayList<Entry> = arrayListOf()
|
|
||||||
for (serie in series) {
|
|
||||||
data.addAll(serie.entries)
|
|
||||||
}
|
|
||||||
data.sortBy { it.x }
|
|
||||||
return data.filterIndexed { index, entry -> data.indexOf(entry) == index } as ArrayList<Entry>
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package com.dzeio.charts
|
|
||||||
|
|
||||||
import com.dzeio.charts.axis.XAxisInterface
|
|
||||||
import com.dzeio.charts.axis.YAxisInterface
|
|
||||||
import com.dzeio.charts.series.SerieInterface
|
|
||||||
|
|
||||||
interface ChartViewInterface {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Make the whole view in debug mode
|
|
||||||
*
|
|
||||||
* add debug texts, logs, and more
|
|
||||||
*/
|
|
||||||
var debug: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the padding inside the view
|
|
||||||
*/
|
|
||||||
var padding: Float
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hold metadata about the X axis
|
|
||||||
*/
|
|
||||||
val xAxis: XAxisInterface
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hold informations about the Y axis
|
|
||||||
*/
|
|
||||||
val yAxis: YAxisInterface
|
|
||||||
|
|
||||||
/**
|
|
||||||
* handle the series
|
|
||||||
*/
|
|
||||||
var series: ArrayList<SerieInterface>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* refresh and run pre-display logic the chart
|
|
||||||
*
|
|
||||||
* this function should be run if you change parameters in the view
|
|
||||||
*/
|
|
||||||
fun refresh()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return the whole dataset (sorted and cleaned up of dupps)
|
|
||||||
*/
|
|
||||||
fun getDataset(): ArrayList<Entry>
|
|
||||||
}
|
|
@ -1,10 +0,0 @@
|
|||||||
package com.dzeio.charts
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A Base entry for any charts
|
|
||||||
*/
|
|
||||||
data class Entry(
|
|
||||||
var x: Double,
|
|
||||||
var y: Float,
|
|
||||||
var color: Int? = null
|
|
||||||
)
|
|
@ -1,125 +0,0 @@
|
|||||||
package com.dzeio.charts.axis
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.RectF
|
|
||||||
import com.dzeio.charts.ChartViewInterface
|
|
||||||
import com.dzeio.charts.Entry
|
|
||||||
|
|
||||||
class XAxis(
|
|
||||||
private val view: ChartViewInterface
|
|
||||||
) : XAxisInterface {
|
|
||||||
override var x: Double = 0.0
|
|
||||||
set(value) {
|
|
||||||
val max = getXMax() - getDataWidth()
|
|
||||||
val min = getXMin()
|
|
||||||
if (value > max && min <= max) {
|
|
||||||
field = max
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < min) {
|
|
||||||
field = min
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
field = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override var enabled = true
|
|
||||||
|
|
||||||
override var dataWidth: Double? = null
|
|
||||||
|
|
||||||
override var labelCount: Int = 2
|
|
||||||
|
|
||||||
var spacing = 16.0
|
|
||||||
|
|
||||||
override val textPaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
color = Color.parseColor("#FC496D")
|
|
||||||
textSize = 30f
|
|
||||||
textAlign = Paint.Align.LEFT
|
|
||||||
}
|
|
||||||
|
|
||||||
private val rect = Rect()
|
|
||||||
|
|
||||||
override fun getPositionOnRect(entry: Entry, drawableSpace: RectF): Double {
|
|
||||||
return translatePositionToRect(entry.x, drawableSpace)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun translatePositionToRect(value: Double, drawableSpace: RectF): Double {
|
|
||||||
return drawableSpace.width() * (value - x) / getDataWidth()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getXMax(): Double {
|
|
||||||
return view.series.maxOf { serie ->
|
|
||||||
if (serie.entries.isEmpty()) {
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
serie.entries.maxOf { entry -> entry.x }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getXMin(): Double {
|
|
||||||
return view.series.minOf { serie ->
|
|
||||||
if (serie.entries.isEmpty()) {
|
|
||||||
return 0.0
|
|
||||||
}
|
|
||||||
serie.entries.minOf { entry -> entry.x }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var onValueFormat: (value: Double) -> String = { it -> it.toString() }
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas, space: RectF): Float {
|
|
||||||
if (!enabled) {
|
|
||||||
return 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxHeight = 0f
|
|
||||||
|
|
||||||
val graphIncrement = space.width() / (labelCount - 1)
|
|
||||||
val valueIncrement = (getDataWidth() / (labelCount - 1)).toDouble()
|
|
||||||
for (index in 0 until labelCount) {
|
|
||||||
val text = onValueFormat(x + valueIncrement * index)
|
|
||||||
textPaint.getTextBounds(text, 0, text.length, rect)
|
|
||||||
maxHeight = maxHeight.coerceAtLeast(rect.height().toFloat() + 1)
|
|
||||||
|
|
||||||
var xPos = space.left + graphIncrement * index
|
|
||||||
|
|
||||||
if (xPos + rect.width() > space.right) {
|
|
||||||
xPos = space.right - rect.width()
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawText(
|
|
||||||
text,
|
|
||||||
xPos,
|
|
||||||
space.bottom,
|
|
||||||
textPaint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return maxHeight + 32f
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun refresh() {
|
|
||||||
// TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getEntryWidth(drawableSpace: RectF): Double {
|
|
||||||
var smallest = -1.0
|
|
||||||
val dataset = view.getDataset()
|
|
||||||
for (idx in 0 until dataset.size - 1) {
|
|
||||||
val distance = dataset[idx + 1].x - dataset[idx].x
|
|
||||||
if (smallest == -1.0 || smallest > distance) {
|
|
||||||
smallest = distance
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return drawableSpace.width() * smallest / getDataWidth() - spacing
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDataWidth(): Double {
|
|
||||||
return dataWidth ?: (getXMax() - getXMin())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,84 +0,0 @@
|
|||||||
package com.dzeio.charts.axis
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.RectF
|
|
||||||
import com.dzeio.charts.Entry
|
|
||||||
|
|
||||||
sealed interface XAxisInterface {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* enable/disable the display of the xAxis
|
|
||||||
*/
|
|
||||||
var enabled: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set X position
|
|
||||||
*/
|
|
||||||
var x: Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the "width" of the graph
|
|
||||||
*
|
|
||||||
* if not set it will be `XMax - XMin`
|
|
||||||
*
|
|
||||||
* ex: to display a 7 days graph history with x values being timestamp in secs, use 7*24*60*60
|
|
||||||
*/
|
|
||||||
var dataWidth: Double?
|
|
||||||
|
|
||||||
/**
|
|
||||||
* text Paint
|
|
||||||
*/
|
|
||||||
val textPaint: Paint
|
|
||||||
|
|
||||||
/**
|
|
||||||
* indicate the number of labels displayed
|
|
||||||
*/
|
|
||||||
var labelCount: Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* run when manually refreshing the system
|
|
||||||
*
|
|
||||||
* this is where the pre-logic is handled to make [onDraw] quicker
|
|
||||||
*/
|
|
||||||
fun refresh()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the entry position on the rect
|
|
||||||
*
|
|
||||||
* @return the left side of the position of the entry
|
|
||||||
*/
|
|
||||||
fun getPositionOnRect(entry: Entry, drawableSpace: RectF): Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the maximum the X can get to
|
|
||||||
*/
|
|
||||||
fun getXMax(): Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the minimum the X can get to
|
|
||||||
*/
|
|
||||||
fun getXMin(): Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get the size of an entry in the graph
|
|
||||||
*
|
|
||||||
* @return the size in [drawableSpace] px
|
|
||||||
*/
|
|
||||||
fun getEntryWidth(drawableSpace: RectF): Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* return the currently used dataWidth
|
|
||||||
*/
|
|
||||||
fun getDataWidth(): Double
|
|
||||||
|
|
||||||
/**
|
|
||||||
* onDraw event that will draw the XAxis
|
|
||||||
*
|
|
||||||
* @param canvas the canvas to draw on
|
|
||||||
* @param space the space where it is allowed to draw
|
|
||||||
*
|
|
||||||
* @return the final height of the XAxis
|
|
||||||
*/
|
|
||||||
fun onDraw(canvas: Canvas, space: RectF): Float
|
|
||||||
}
|
|
@ -1,145 +0,0 @@
|
|||||||
package com.dzeio.charts.axis
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.RectF
|
|
||||||
import com.dzeio.charts.ChartViewInterface
|
|
||||||
import com.dzeio.charts.utils.drawDottedLine
|
|
||||||
|
|
||||||
class YAxis(
|
|
||||||
private val view: ChartViewInterface
|
|
||||||
) : YAxisInterface {
|
|
||||||
|
|
||||||
override var enabled = true
|
|
||||||
|
|
||||||
override val textLabel = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
color = Color.parseColor("#FC496D")
|
|
||||||
textSize = 30f
|
|
||||||
textAlign = Paint.Align.LEFT
|
|
||||||
}
|
|
||||||
|
|
||||||
override val linePaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
color = Color.BLUE
|
|
||||||
}
|
|
||||||
|
|
||||||
override val goalLinePaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
color = Color.RED
|
|
||||||
strokeWidth = 4f
|
|
||||||
}
|
|
||||||
|
|
||||||
var onValueFormat: (value: Float) -> String = { it -> it.toString() }
|
|
||||||
|
|
||||||
override var labelCount = 5
|
|
||||||
|
|
||||||
private var min: Float? = 0f
|
|
||||||
private var max: Float? = null
|
|
||||||
|
|
||||||
private val rect = Rect()
|
|
||||||
|
|
||||||
override fun setYMin(yMin: Float?): YAxisInterface {
|
|
||||||
min = yMin
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setYMax(yMax: Float?): YAxisInterface {
|
|
||||||
max = yMax
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getYMax(): Float {
|
|
||||||
if (max != null) {
|
|
||||||
return max!!
|
|
||||||
}
|
|
||||||
if (view.series.isEmpty()) {
|
|
||||||
return (this.goalLine ?: 90f) + 10f
|
|
||||||
}
|
|
||||||
val seriesMax = view.series
|
|
||||||
.maxOf { serie ->
|
|
||||||
if (serie.getDisplayedEntries().isEmpty()) {
|
|
||||||
return@maxOf 0f
|
|
||||||
}
|
|
||||||
return@maxOf serie.getDisplayedEntries().maxOf { entry -> entry.y }
|
|
||||||
}
|
|
||||||
if (this.goalLine != null) {
|
|
||||||
return if (seriesMax > this.goalLine!!) seriesMax else this.goalLine!! + 1000f
|
|
||||||
}
|
|
||||||
return seriesMax
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getYMin(): Float {
|
|
||||||
if (min != null) {
|
|
||||||
return min!!
|
|
||||||
}
|
|
||||||
if (view.series.isEmpty()) {
|
|
||||||
return this.goalLine ?: 0f
|
|
||||||
}
|
|
||||||
return view.series
|
|
||||||
.minOf { serie ->
|
|
||||||
if (serie.getDisplayedEntries().isEmpty()) {
|
|
||||||
return@minOf 0f
|
|
||||||
}
|
|
||||||
return@minOf serie.getDisplayedEntries().minOf { entry -> entry.y }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas, space: RectF): Float {
|
|
||||||
if (!enabled) {
|
|
||||||
return 0f
|
|
||||||
}
|
|
||||||
|
|
||||||
val min = getYMin()
|
|
||||||
val max = getYMax() - min
|
|
||||||
val top = space.top
|
|
||||||
val bottom = space.bottom
|
|
||||||
var maxWidth = 0f
|
|
||||||
|
|
||||||
val increment = (bottom - top) / labelCount
|
|
||||||
val valueIncrement = (max - min) / labelCount
|
|
||||||
for (index in 0 until labelCount) {
|
|
||||||
val text = onValueFormat((valueIncrement * (index + 1)))
|
|
||||||
textLabel.getTextBounds(text, 0, text.length, rect)
|
|
||||||
maxWidth = maxWidth.coerceAtLeast(rect.width().toFloat())
|
|
||||||
|
|
||||||
val posY = bottom - (index + 1) * increment
|
|
||||||
|
|
||||||
canvas.drawText(
|
|
||||||
text,
|
|
||||||
space.width() - rect.width().toFloat(),
|
|
||||||
(posY + rect.height() / 2).coerceAtLeast(rect.height().toFloat()),
|
|
||||||
textLabel
|
|
||||||
)
|
|
||||||
// canvas.drawDottedLine(0f, posY, canvas.width.toFloat(), posY, 40f, linePaint)
|
|
||||||
canvas.drawLine(space.left, posY, space.right - maxWidth - 32f, posY, linePaint)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.goalLine != null) {
|
|
||||||
val pos = (1 - this.goalLine!! / max) * space.height() + space.top
|
|
||||||
canvas.drawDottedLine(
|
|
||||||
0f,
|
|
||||||
pos,
|
|
||||||
space.right - maxWidth - 32f,
|
|
||||||
pos,
|
|
||||||
space.right / 20,
|
|
||||||
goalLinePaint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxWidth + 32f
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun refresh() {
|
|
||||||
// TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
private var goalLine: Float? = null
|
|
||||||
|
|
||||||
override fun setGoalLine(height: Float?) {
|
|
||||||
goalLine = height
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
package com.dzeio.charts.axis
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.RectF
|
|
||||||
|
|
||||||
sealed interface YAxisInterface {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* whether or not this axis is displayed
|
|
||||||
*/
|
|
||||||
var enabled: Boolean
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get/set the number of label of this Y axis
|
|
||||||
*
|
|
||||||
* the first/last labels are at the bottom/top of the chart
|
|
||||||
*/
|
|
||||||
var labelCount: Int
|
|
||||||
|
|
||||||
/**
|
|
||||||
* text label paint
|
|
||||||
*/
|
|
||||||
val textLabel: Paint
|
|
||||||
|
|
||||||
/**
|
|
||||||
* paint for the lines
|
|
||||||
*/
|
|
||||||
val linePaint: Paint
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Goal line paint
|
|
||||||
*/
|
|
||||||
val goalLinePaint: Paint
|
|
||||||
|
|
||||||
/**
|
|
||||||
* run when manually refreshing the system
|
|
||||||
*
|
|
||||||
* this is where the pre-logic is handled to make [onDraw] quicker
|
|
||||||
*/
|
|
||||||
fun refresh()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* override Y minimum
|
|
||||||
*
|
|
||||||
* @param yMin is set the min will ba at the value, if null it is calculated
|
|
||||||
*/
|
|
||||||
fun setYMin(yMin: Float?): YAxisInterface
|
|
||||||
|
|
||||||
/**
|
|
||||||
* override Y maximum
|
|
||||||
*
|
|
||||||
* @param yMax is set the max will ba at the value, if null it is calculated
|
|
||||||
*/
|
|
||||||
fun setYMax(yMax: Float?): YAxisInterface
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get Y maximum
|
|
||||||
*
|
|
||||||
* @return the maximum value Y can get (for displayed values)
|
|
||||||
*/
|
|
||||||
fun getYMax(): Float
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get Y minimum
|
|
||||||
*
|
|
||||||
* @return the minimum value Y can get (for displayed values)
|
|
||||||
*/
|
|
||||||
fun getYMin(): Float
|
|
||||||
|
|
||||||
/**
|
|
||||||
* function that draw our legend
|
|
||||||
*
|
|
||||||
* @param canvas the canvas to draw on
|
|
||||||
* @param space the space where it is allowed to draw on
|
|
||||||
*
|
|
||||||
* @return the width of the sidebar
|
|
||||||
*/
|
|
||||||
fun onDraw(canvas: Canvas, space: RectF): Float
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a Goal line
|
|
||||||
*/
|
|
||||||
fun setGoalLine(height: Float?)
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package com.dzeio.charts.axis
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CURRENTLY UNUSED
|
|
||||||
*
|
|
||||||
* declare where the YAxis for the graph will be
|
|
||||||
*/
|
|
||||||
enum class YAxisPosition {
|
|
||||||
LEFT,
|
|
||||||
RIGHT
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
package com.dzeio.charts.components
|
|
||||||
|
|
||||||
import android.view.MotionEvent
|
|
||||||
import android.view.MotionEvent.INVALID_POINTER_ID
|
|
||||||
import android.view.ScaleGestureDetector
|
|
||||||
import android.view.View
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class handling the scroll/zoom for the library
|
|
||||||
*/
|
|
||||||
class ChartScroll(view: View) {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enabled the zoom/unzoom of datas
|
|
||||||
*/
|
|
||||||
var zoomEnabled = true
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable the horizontal scroll feature
|
|
||||||
*/
|
|
||||||
var scrollEnabled = true
|
|
||||||
|
|
||||||
// The ‘active pointer’ is the one currently moving our object.
|
|
||||||
private var activePointerId = INVALID_POINTER_ID
|
|
||||||
|
|
||||||
private var lastTouchX: Float = 0f
|
|
||||||
private var lastTouchY: Float = 0f
|
|
||||||
|
|
||||||
private var posX: Float = 0f
|
|
||||||
private var posY: Float = 0f
|
|
||||||
|
|
||||||
private var lastZoom: Float = 100f
|
|
||||||
private var currentZoom: Float = 0f
|
|
||||||
|
|
||||||
private var onChartMoved: ((movementX: Float, movementY: Float) -> Unit)? = null
|
|
||||||
fun setOnChartMoved(fn: (movementX: Float, movementY: Float) -> Unit) {
|
|
||||||
onChartMoved = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
private var onZoomChanged: ((scale: Float) -> Unit)? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param fn.scale Float starting from 100%
|
|
||||||
*
|
|
||||||
* 99-% zoom out,
|
|
||||||
* 101+% zoom in
|
|
||||||
*/
|
|
||||||
fun setOnZoomChanged(fn: (scale: Float) -> Unit) {
|
|
||||||
onZoomChanged = fn
|
|
||||||
}
|
|
||||||
|
|
||||||
private val scaleGestureDetector = ScaleGestureDetector(
|
|
||||||
view.context,
|
|
||||||
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
|
||||||
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
|
||||||
if (currentZoom != detector.scaleFactor) {
|
|
||||||
currentZoom = detector.scaleFactor
|
|
||||||
onZoomChanged?.invoke(lastZoom + -currentZoom + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return super.onScale(detector)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onScaleEnd(detector: ScaleGestureDetector) {
|
|
||||||
super.onScaleEnd(detector)
|
|
||||||
|
|
||||||
lastZoom += -currentZoom + 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Code mostly stolen from https://developer.android.com/training/gestures/scale#drag
|
|
||||||
*/
|
|
||||||
fun onTouchEvent(ev: MotionEvent): Boolean {
|
|
||||||
|
|
||||||
if (zoomEnabled) {
|
|
||||||
scaleGestureDetector.onTouchEvent(ev)
|
|
||||||
}
|
|
||||||
|
|
||||||
when (ev.actionMasked) {
|
|
||||||
MotionEvent.ACTION_DOWN -> {
|
|
||||||
onToggleScroll?.invoke(false)
|
|
||||||
ev.actionIndex.also { pointerIndex ->
|
|
||||||
// Remember where we started (for dragging)
|
|
||||||
lastTouchX = ev.getX(pointerIndex)
|
|
||||||
lastTouchY = ev.getY(pointerIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save the ID of this pointer (for dragging)
|
|
||||||
activePointerId = ev.getPointerId(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
MotionEvent.ACTION_MOVE -> {
|
|
||||||
// Find the index of the active pointer and fetch its position
|
|
||||||
val (x: Float, y: Float) =
|
|
||||||
ev.findPointerIndex(activePointerId).let { pointerIndex ->
|
|
||||||
// Calculate the distance moved
|
|
||||||
ev.getX(pointerIndex) to ev.getY(pointerIndex)
|
|
||||||
}
|
|
||||||
|
|
||||||
posX += x - lastTouchX
|
|
||||||
posY += y - lastTouchY
|
|
||||||
|
|
||||||
if (scrollEnabled) {
|
|
||||||
onChartMoved?.invoke(-posX, posY)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remember this touch position for the next move event
|
|
||||||
lastTouchX = x
|
|
||||||
lastTouchY = y
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
|
||||||
onToggleScroll?.invoke(true)
|
|
||||||
activePointerId = INVALID_POINTER_ID
|
|
||||||
}
|
|
||||||
MotionEvent.ACTION_POINTER_UP -> {
|
|
||||||
onToggleScroll?.invoke(true)
|
|
||||||
ev.actionIndex.also { pointerIndex ->
|
|
||||||
ev.getPointerId(pointerIndex)
|
|
||||||
.takeIf { it == activePointerId }
|
|
||||||
?.run {
|
|
||||||
// This was our active pointer going up. Choose a new
|
|
||||||
// active pointer and adjust accordingly.
|
|
||||||
val newPointerIndex = if (pointerIndex == 0) 1 else 0
|
|
||||||
lastTouchX = ev.getX(newPointerIndex)
|
|
||||||
lastTouchY = ev.getY(newPointerIndex)
|
|
||||||
activePointerId = ev.getPointerId(newPointerIndex)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
private var onToggleScroll: ((Boolean) -> Unit)? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ev if input is false disable scroll
|
|
||||||
*/
|
|
||||||
fun setOnToggleScroll(ev: (Boolean) -> Unit) {
|
|
||||||
onToggleScroll = ev
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,125 +0,0 @@
|
|||||||
package com.dzeio.charts.series
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.RectF
|
|
||||||
import android.util.Log
|
|
||||||
import com.dzeio.charts.ChartView
|
|
||||||
import com.dzeio.charts.utils.drawRoundRect
|
|
||||||
|
|
||||||
class BarSerie(
|
|
||||||
private val view: ChartView
|
|
||||||
) : BaseSerie(view) {
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val TAG = "Charts/BarSerie"
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
view.series.add(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
val barPaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
color = Color.parseColor("#123456")
|
|
||||||
}
|
|
||||||
|
|
||||||
val textPaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
color = Color.parseColor("#FC496D")
|
|
||||||
textSize = 30f
|
|
||||||
textAlign = Paint.Align.CENTER
|
|
||||||
}
|
|
||||||
|
|
||||||
private val rect = Rect()
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
|
|
||||||
val displayedEntries = getDisplayedEntries()
|
|
||||||
val barWidth = view.xAxis.getEntryWidth(drawableSpace).toFloat()
|
|
||||||
val max = view.yAxis.getYMax()
|
|
||||||
val min = view.yAxis.getYMin()
|
|
||||||
|
|
||||||
// Log.d(TAG, "${space.left}, ${space.right}")
|
|
||||||
|
|
||||||
for (entry in displayedEntries) {
|
|
||||||
// calculated height in percent from 0 to 100
|
|
||||||
val top = (1 - entry.y / max) * drawableSpace.height() + drawableSpace.top
|
|
||||||
var posX = drawableSpace.left + view.xAxis.getPositionOnRect(
|
|
||||||
entry,
|
|
||||||
drawableSpace
|
|
||||||
).toFloat()
|
|
||||||
|
|
||||||
val right = (posX + barWidth).coerceAtMost(drawableSpace.right)
|
|
||||||
|
|
||||||
if (posX > right) {
|
|
||||||
continue
|
|
||||||
} else if (posX < drawableSpace.left) {
|
|
||||||
posX = drawableSpace.left
|
|
||||||
}
|
|
||||||
|
|
||||||
if (right < drawableSpace.left) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle color recoloration
|
|
||||||
val paint = Paint(barPaint)
|
|
||||||
|
|
||||||
if (entry.color != null) {
|
|
||||||
paint.color = entry.color!!
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas.drawRoundRect(
|
|
||||||
posX,
|
|
||||||
top,
|
|
||||||
right,
|
|
||||||
drawableSpace.bottom,
|
|
||||||
// 8f, 8f,
|
|
||||||
32f,
|
|
||||||
32f,
|
|
||||||
0f,
|
|
||||||
0f,
|
|
||||||
paint
|
|
||||||
)
|
|
||||||
|
|
||||||
// handle text display
|
|
||||||
val text = view.yAxis.onValueFormat(entry.y)
|
|
||||||
|
|
||||||
textPaint.getTextBounds(text, 0, text.length, rect)
|
|
||||||
|
|
||||||
val textLeft = (posX + barWidth / 2)
|
|
||||||
|
|
||||||
if (
|
|
||||||
// handle right side
|
|
||||||
textLeft + rect.width() / 2 > right ||
|
|
||||||
// handle left sie
|
|
||||||
textLeft - rect.width() / 2 < drawableSpace.left
|
|
||||||
) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val doDisplayIn =
|
|
||||||
rect.height() + 40f < drawableSpace.bottom - top &&
|
|
||||||
rect.width() < barWidth
|
|
||||||
|
|
||||||
var textY = if (doDisplayIn) top + rect.height() + 20f else top - 20f
|
|
||||||
|
|
||||||
if (textY < 0) {
|
|
||||||
textY = drawableSpace.top + rect.height()
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
canvas.drawText(
|
|
||||||
text,
|
|
||||||
textLeft,
|
|
||||||
textY,
|
|
||||||
textPaint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun refresh() {
|
|
||||||
// TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,160 +0,0 @@
|
|||||||
package com.dzeio.charts.series
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.RectF
|
|
||||||
import android.util.Log
|
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class BarSerie : SerieAbstract() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val TAG = "DzeioCharts/BarSerie"
|
|
||||||
}
|
|
||||||
|
|
||||||
var spacing: Float = 8f
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Values displayed on the grapd
|
|
||||||
*/
|
|
||||||
var displayedDatas = arrayListOf<Float>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Target values
|
|
||||||
*/
|
|
||||||
var targetDatas = arrayListOf<Float>()
|
|
||||||
|
|
||||||
var targetPercentList = arrayListOf<Float>()
|
|
||||||
var percentList = arrayListOf<Float>()
|
|
||||||
|
|
||||||
var previousRefresh = 0
|
|
||||||
|
|
||||||
private var fgPaint: Paint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private val r = Rect()
|
|
||||||
|
|
||||||
override fun onUpdate(): Boolean {
|
|
||||||
var needNewFrame = false
|
|
||||||
for (i in targetPercentList.indices) {
|
|
||||||
val value = view.animation.updateValue(
|
|
||||||
1f,
|
|
||||||
targetPercentList[i],
|
|
||||||
percentList[i],
|
|
||||||
0f,
|
|
||||||
0.00f
|
|
||||||
)
|
|
||||||
|
|
||||||
if (value != percentList[i]) {
|
|
||||||
needNewFrame = true
|
|
||||||
percentList[i] = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return needNewFrame
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun prepareData() {
|
|
||||||
val max: Float = if (view.yAxis.max != null) view.yAxis.max!! else {
|
|
||||||
getYMax(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
targetPercentList = arrayListOf()
|
|
||||||
|
|
||||||
// Log.d(TAG, "offset: ${view.getXOffset()}, displayed: ${view.getDisplayedEntries()}")
|
|
||||||
for (item in getDisplayedEntries()) {
|
|
||||||
// // // Process bottom texts
|
|
||||||
// val text = view.xAxis.onValueFormat(item.x)
|
|
||||||
// bottomTexts.add(text)
|
|
||||||
//
|
|
||||||
// // get Text boundaries
|
|
||||||
// view.xAxis.labels.build().getTextBounds(text, 0, text.length, r)
|
|
||||||
//
|
|
||||||
// // get height of text
|
|
||||||
// if (bottomTextHeight < r.height()) {
|
|
||||||
// bottomTextHeight = r.height()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // get text descent
|
|
||||||
// val descent = abs(r.bottom)
|
|
||||||
// if (bottomTextDescent < descent) {
|
|
||||||
// bottomTextDescent = descent
|
|
||||||
// }
|
|
||||||
|
|
||||||
// process values
|
|
||||||
// Log.d(TAG, item.y.toString())
|
|
||||||
|
|
||||||
// add to animations the values
|
|
||||||
targetPercentList.add(1 - item.y / max)
|
|
||||||
}
|
|
||||||
|
|
||||||
// post list
|
|
||||||
val offset = view.getXOffset()
|
|
||||||
val movement = offset - previousRefresh
|
|
||||||
Log.d(TAG, "$offset - $previousRefresh = $movement")
|
|
||||||
if (movement != 0) {
|
|
||||||
previousRefresh = offset
|
|
||||||
}
|
|
||||||
// if (movement != 0) {
|
|
||||||
// Log.d(TAG, movement.toString())
|
|
||||||
// }
|
|
||||||
if (movement >= 1) {
|
|
||||||
percentList = percentList.subList(1, percentList.size).toCollection(ArrayList())
|
|
||||||
percentList.add(1f)
|
|
||||||
} else if (movement <= -1) {
|
|
||||||
percentList = percentList.subList(0, percentList.size - 1).toCollection(ArrayList())
|
|
||||||
percentList.add(0, 1f)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (percentList.isEmpty() || percentList.size < targetPercentList.size) {
|
|
||||||
val temp = targetPercentList.size - percentList.size
|
|
||||||
for (i in 0 until temp) {
|
|
||||||
percentList.add(1f)
|
|
||||||
}
|
|
||||||
} else if (percentList.size > targetPercentList.size) {
|
|
||||||
val temp = percentList.size - targetPercentList.size
|
|
||||||
for (i in 0 until temp) {
|
|
||||||
percentList.removeAt(percentList.size - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fgPaint.color = view.yAxis.color
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun displayData(canvas: Canvas, rect: RectF) {
|
|
||||||
val barWidth = (rect.width() - view.padding * 2) / view.getDisplayedEntries() - spacing
|
|
||||||
|
|
||||||
if (percentList.isNotEmpty()) {
|
|
||||||
// draw each rectangles
|
|
||||||
for (i in 1..percentList.size) {
|
|
||||||
// Log.d(TAG, percentList[i - 1].toString())
|
|
||||||
val left = rect.left + spacing * i + barWidth * (i - 1).toFloat() + view.padding
|
|
||||||
// Log.d(TAG, "$spacing, $i, $barWidth = $left")
|
|
||||||
val right = rect.left + (spacing + barWidth) * i.toFloat()
|
|
||||||
val bottom = rect.top + rect.height() - view.padding
|
|
||||||
val top = (bottom - rect.top) * percentList[i - 1] + view.padding
|
|
||||||
|
|
||||||
// create rounded rect
|
|
||||||
canvas.drawRoundRect(left, top, right, bottom, 8f, 8f, fgPaint)
|
|
||||||
// remove the bottom corners DUH
|
|
||||||
canvas.drawRect(left, max(top, bottom - 8f), right, bottom, fgPaint)
|
|
||||||
val targetTop = (bottom - rect.top) * targetPercentList[i - 1]
|
|
||||||
|
|
||||||
val text = view.yAxis.onValueFormat(getYMax(true) - getYMax(true) * targetPercentList[i - 1], true)
|
|
||||||
view.xAxis.labels.build().getTextBounds(text, 0, text.length, r)
|
|
||||||
val doDisplayIn =
|
|
||||||
r.width() + 10f < barWidth && bottom - targetTop > r.height() + 40f
|
|
||||||
if (view.debug || !doDisplayIn || (doDisplayIn && bottom - top > r.height() + 40f)) {
|
|
||||||
val y = if (doDisplayIn) top + r.height() + 20f else top - r.height()
|
|
||||||
canvas.drawText(
|
|
||||||
text,
|
|
||||||
left + (right - left) / 2,
|
|
||||||
y,
|
|
||||||
view.xAxis.labels.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,47 +0,0 @@
|
|||||||
package com.dzeio.charts.series
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.RectF
|
|
||||||
import com.dzeio.charts.ChartViewInterface
|
|
||||||
import com.dzeio.charts.Entry
|
|
||||||
import com.dzeio.charts.axis.YAxisPosition
|
|
||||||
|
|
||||||
sealed class BaseSerie(
|
|
||||||
private val view: ChartViewInterface
|
|
||||||
) : SerieInterface {
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val TAG = "Charts/BaseSerie"
|
|
||||||
}
|
|
||||||
|
|
||||||
override var yAxisPosition: YAxisPosition = YAxisPosition.RIGHT
|
|
||||||
|
|
||||||
override var entries: ArrayList<Entry> = arrayListOf()
|
|
||||||
|
|
||||||
override fun getDisplayedEntries(): ArrayList<Entry> {
|
|
||||||
val minX = view.xAxis.x
|
|
||||||
val maxX = minX + view.xAxis.getDataWidth()
|
|
||||||
|
|
||||||
val result: ArrayList<Entry> = arrayListOf()
|
|
||||||
|
|
||||||
var lastIndex = -1
|
|
||||||
for (i in 0 until entries.size) {
|
|
||||||
val it = entries[i]
|
|
||||||
if (it.x in minX..maxX) {
|
|
||||||
if (result.size === 0 && i > 0) {
|
|
||||||
result.add((entries[i - 1]))
|
|
||||||
}
|
|
||||||
lastIndex = i
|
|
||||||
result.add(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastIndex < entries.size - 1) {
|
|
||||||
result.add(entries [lastIndex + 1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract override fun onDraw(canvas: Canvas, drawableSpace: RectF)
|
|
||||||
}
|
|
@ -1,74 +0,0 @@
|
|||||||
package com.dzeio.charts.series
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.RectF
|
|
||||||
import com.dzeio.charts.ChartView
|
|
||||||
|
|
||||||
class LineSerie(
|
|
||||||
private val view: ChartView
|
|
||||||
) : BaseSerie(view) {
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
const val TAG = "Charts/LineSerie"
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
view.series.add(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
val linePaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
color = Color.parseColor("#123456")
|
|
||||||
strokeWidth = 5f
|
|
||||||
}
|
|
||||||
|
|
||||||
val textPaint = Paint().apply {
|
|
||||||
isAntiAlias = true
|
|
||||||
color = Color.parseColor("#FC496D")
|
|
||||||
textSize = 30f
|
|
||||||
textAlign = Paint.Align.CENTER
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
|
|
||||||
val displayedEntries = getDisplayedEntries()
|
|
||||||
displayedEntries.sortBy { it.x }
|
|
||||||
val max = view.yAxis.getYMax()
|
|
||||||
|
|
||||||
var previousPosX: Float? = null
|
|
||||||
var previousPosY: Float? = null
|
|
||||||
|
|
||||||
for (entry in displayedEntries) {
|
|
||||||
// calculated height in percent from 0 to 100
|
|
||||||
val top = (1 - entry.y / max) * drawableSpace.height() + drawableSpace.top
|
|
||||||
val posX = (drawableSpace.left +
|
|
||||||
view.xAxis.getPositionOnRect(entry, drawableSpace) +
|
|
||||||
view.xAxis.getEntryWidth(drawableSpace) / 2f).toFloat()
|
|
||||||
|
|
||||||
// handle color recoloration
|
|
||||||
val paint = Paint(linePaint)
|
|
||||||
|
|
||||||
if (entry.color != null) {
|
|
||||||
paint.color = entry.color!!
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw smol point
|
|
||||||
if (posX < drawableSpace.right) {
|
|
||||||
canvas.drawCircle(posX, top, paint.strokeWidth, paint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// draw line
|
|
||||||
if (previousPosX != null && previousPosY != null) {
|
|
||||||
canvas.drawLine(previousPosX, previousPosY, posX, top, paint)
|
|
||||||
|
|
||||||
}
|
|
||||||
previousPosX = posX
|
|
||||||
previousPosY = top
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun refresh() {
|
|
||||||
// TODO("Not yet implemented")
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,41 +0,0 @@
|
|||||||
package com.dzeio.charts.series
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.RectF
|
|
||||||
import com.dzeio.charts.Entry
|
|
||||||
import com.dzeio.charts.axis.YAxisPosition
|
|
||||||
|
|
||||||
sealed interface SerieInterface {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* location of the Y axis
|
|
||||||
*/
|
|
||||||
var yAxisPosition: YAxisPosition
|
|
||||||
|
|
||||||
/**
|
|
||||||
* filter out out of display entries
|
|
||||||
*
|
|
||||||
* @return the list of entries displayed
|
|
||||||
*/
|
|
||||||
fun getDisplayedEntries(): ArrayList<Entry>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set the entries for the list
|
|
||||||
*/
|
|
||||||
var entries: ArrayList<Entry>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* function that display the graph
|
|
||||||
*
|
|
||||||
* @param canvas the canvas to draw on
|
|
||||||
* @param drawableSpace the space you are allowed to draw on
|
|
||||||
*/
|
|
||||||
fun onDraw(canvas: Canvas, drawableSpace: RectF)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* run when manually refreshing the system
|
|
||||||
*
|
|
||||||
* this is where the pre-logic is handled to make [onDraw] quicker
|
|
||||||
*/
|
|
||||||
fun refresh()
|
|
||||||
}
|
|
@ -1,144 +0,0 @@
|
|||||||
package com.dzeio.charts.utils
|
|
||||||
|
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Paint
|
|
||||||
import android.graphics.RectF
|
|
||||||
import kotlin.math.sqrt
|
|
||||||
|
|
||||||
/**
|
|
||||||
* draw a dotted line
|
|
||||||
*/
|
|
||||||
fun Canvas.drawDottedLine(
|
|
||||||
startX: Float,
|
|
||||||
startY: Float,
|
|
||||||
endX: Float,
|
|
||||||
endY: Float,
|
|
||||||
spacing: Float,
|
|
||||||
paint: Paint
|
|
||||||
) {
|
|
||||||
//calculate line length
|
|
||||||
val length = if (endX - startX == 0f) {
|
|
||||||
// just length of Y
|
|
||||||
endY - startY
|
|
||||||
} else if (endY - startY == 0f) {
|
|
||||||
// just length of X
|
|
||||||
endX - startX
|
|
||||||
} else {
|
|
||||||
// calculate using the Pythagorean theorem
|
|
||||||
sqrt((startX + endX) * (startX + endX) + (startY + endY) * (startY + endY))
|
|
||||||
}
|
|
||||||
|
|
||||||
val lineCount = (length / spacing).toInt()
|
|
||||||
|
|
||||||
val lenX = endX - startX
|
|
||||||
val lenY = endY - startY
|
|
||||||
|
|
||||||
// Log.d("DrawDottedLine", "----------- Start -----------")
|
|
||||||
// Log.d("DrawDottedLine", "lenX: $lenX, lenY: $lenY")
|
|
||||||
for (line in 0 until lineCount) {
|
|
||||||
if (line % 2 == 0) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val sx = lenX / lineCount * line + startX
|
|
||||||
val sy = lenY / lineCount * line + startY
|
|
||||||
val ex = lenX / lineCount * (line + 1) + startX
|
|
||||||
val ey = lenY / lineCount * (line + 1) + startY
|
|
||||||
// Log.d("DrawDottedLine", "$sx, $sy, $ex, $ey")
|
|
||||||
this.drawLine(sx, sy, ex, ey, paint)
|
|
||||||
// line
|
|
||||||
// total line startX, endX, startY, endY
|
|
||||||
// total line length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A more customizable drawRoundRect function
|
|
||||||
*/
|
|
||||||
fun Canvas.drawRoundRect(
|
|
||||||
left: Float,
|
|
||||||
top: Float,
|
|
||||||
right: Float,
|
|
||||||
bottom: Float,
|
|
||||||
topLeft: Float,
|
|
||||||
topRight: Float,
|
|
||||||
bottomLeft: Float,
|
|
||||||
bottomRight: Float,
|
|
||||||
paint: Paint
|
|
||||||
) {
|
|
||||||
val maxRound = arrayOf(topLeft, topRight, bottomLeft, bottomRight).maxOf { it }
|
|
||||||
val width = right - left
|
|
||||||
val height = bottom - top
|
|
||||||
|
|
||||||
// draw first/global rect
|
|
||||||
drawRoundRect(left, top, right, bottom, maxRound, maxRound, paint)
|
|
||||||
|
|
||||||
// top left border
|
|
||||||
if (topLeft == 0f) {
|
|
||||||
drawRect(left, top, left + width / 2, top + height / 2, paint)
|
|
||||||
} else {
|
|
||||||
drawRoundRect(left, top, left + width / 2, top + height / 2, topLeft, topLeft, paint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// top right border
|
|
||||||
if (topRight == 0f) {
|
|
||||||
drawRect(right - width / 2, top, right, top + height / 2, paint)
|
|
||||||
} else {
|
|
||||||
drawRoundRect(right - width / 2, top, right, top + height / 2, topRight, topRight, paint)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bottom left border
|
|
||||||
if (bottomLeft == 0f) {
|
|
||||||
drawRect(left, bottom - height / 2, left + width / 2, bottom, paint)
|
|
||||||
} else {
|
|
||||||
drawRoundRect(
|
|
||||||
left,
|
|
||||||
bottom - height / 2,
|
|
||||||
left + width / 2,
|
|
||||||
bottom,
|
|
||||||
bottomLeft,
|
|
||||||
bottomLeft,
|
|
||||||
paint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// bottom right border
|
|
||||||
if (bottomRight == 0f) {
|
|
||||||
drawRect(right - width / 2, bottom - height / 2, right, bottom, paint)
|
|
||||||
} else {
|
|
||||||
drawRoundRect(
|
|
||||||
right - width / 2,
|
|
||||||
bottom - height / 2,
|
|
||||||
right,
|
|
||||||
bottom,
|
|
||||||
bottomRight,
|
|
||||||
bottomRight,
|
|
||||||
paint
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A more customizable drawRoundRect function
|
|
||||||
*/
|
|
||||||
fun Canvas.drawRoundRect(
|
|
||||||
rect: RectF,
|
|
||||||
topLeft: Float,
|
|
||||||
topRight: Float,
|
|
||||||
bottomLeft: Float,
|
|
||||||
bottomRight: Float,
|
|
||||||
paint: Paint
|
|
||||||
) {
|
|
||||||
drawRoundRect(
|
|
||||||
rect.left,
|
|
||||||
rect.top,
|
|
||||||
rect.right,
|
|
||||||
rect.bottom,
|
|
||||||
topLeft,
|
|
||||||
topRight,
|
|
||||||
bottomLeft,
|
|
||||||
bottomRight,
|
|
||||||
paint
|
|
||||||
)
|
|
||||||
}
|
|
@ -1,17 +0,0 @@
|
|||||||
package com.dzeio.charts
|
|
||||||
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
import org.junit.Assert.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
|
||||||
*
|
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
|
||||||
*/
|
|
||||||
class ExampleUnitTest {
|
|
||||||
@Test
|
|
||||||
fun addition_isCorrect() {
|
|
||||||
assertEquals(4, 2 + 2)
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,4 +18,3 @@ dependencyResolutionManagement {
|
|||||||
rootProject.name = "OpenHealth"
|
rootProject.name = "OpenHealth"
|
||||||
|
|
||||||
include ':app'
|
include ':app'
|
||||||
include ':charts'
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user