1
0
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:
Florian Bouillon 2023-01-16 10:15:49 +01:00
parent 854195abed
commit 7875271b7e
38 changed files with 140 additions and 1655 deletions

View File

@ -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")

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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
} }

View File

@ -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>
) )
} }
} }

View File

@ -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(),

View File

@ -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()

View File

@ -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()
} }
} }
} }

View File

@ -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
)
}
}
}
}
}
} }

View File

@ -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
View File

@ -1 +0,0 @@
/build

View File

@ -1,3 +0,0 @@
# Dzeio Charts
Originally from https://github.com/HackPlan/AndroidCharts but We wanted more options :D

View File

@ -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"
}

View File

@ -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

View File

@ -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)
}
}

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest></manifest>

View File

@ -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
}

View File

@ -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>
}
}

View File

@ -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>
}

View File

@ -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
)

View File

@ -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())
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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?)
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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")
}
}

View File

@ -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()
)
}
}
}
}
}

View File

@ -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)
}

View File

@ -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")
}
}

View File

@ -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()
}

View File

@ -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
)
}

View File

@ -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)
}
}

0
gradlew vendored Normal file → Executable file
View File

View File

@ -18,4 +18,3 @@ dependencyResolutionManagement {
rootProject.name = "OpenHealth" rootProject.name = "OpenHealth"
include ':app' include ':app'
include ':charts'