mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-04-22 02:42:15 +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 {
|
||||
// Dzeio Charts
|
||||
implementation(project(":charts"))
|
||||
implementation("com.dzeio:charts:edd78e87e1")
|
||||
|
||||
// Dzeio Crash Handler
|
||||
implementation("com.dzeio:crashhandler:1.0.1")
|
||||
@ -139,7 +139,7 @@ dependencies {
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.appcompat:appcompat:1.7.0-alpha01")
|
||||
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.lifecycle:lifecycle-livedata-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.databinding.ItemFoodBinding
|
||||
import com.dzeio.openhealth.utils.DownloadImageTask
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
||||
@ -23,6 +26,11 @@ class FoodAdapter : BaseAdapter<Food, ItemFoodBinding>() {
|
||||
item: Food,
|
||||
position: Int
|
||||
) {
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
}
|
||||
|
||||
// Download remote picture
|
||||
DownloadImageTask(holder.binding.productImage).execute(item.image)
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.dzeio.openhealth.data.food
|
||||
|
||||
import android.util.Log
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
import com.dzeio.openhealth.data.openfoodfact.OFFProduct
|
||||
@ -63,7 +64,11 @@ data class Food(
|
||||
/**
|
||||
* 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
|
||||
var eaten = quantity ?: food.servingQuantity ?: food.productQuantity ?: 0f
|
||||
@ -72,10 +77,13 @@ data class Food(
|
||||
eaten = food.servingQuantity!!
|
||||
} else if (food.productQuantity != null && food.productQuantity != 0f) {
|
||||
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(
|
||||
name = food.name,
|
||||
name = food.name!!,
|
||||
// do some slight edit on the serving to remove strange entries like `100 g`
|
||||
serving = (food.servingSize ?: food.quantity ?: "unknown").replace(Regex(" +"), ""),
|
||||
quantity = eaten,
|
||||
|
@ -13,7 +13,7 @@ data class OFFProduct(
|
||||
* the product name
|
||||
*/
|
||||
@SerializedName("product_name")
|
||||
var name: String,
|
||||
var name: String?,
|
||||
|
||||
/**
|
||||
* the size of a serving
|
||||
|
@ -4,7 +4,7 @@ import android.graphics.Color
|
||||
import android.view.View
|
||||
import com.dzeio.openhealth.data.weight.Weight
|
||||
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.components.LimitLine
|
||||
import com.github.mikephil.charting.components.YAxis
|
||||
@ -29,7 +29,7 @@ object WeightChart {
|
||||
goal: Float?,
|
||||
limit: Boolean = true
|
||||
) {
|
||||
GraphUtils.lineChartSetup(
|
||||
ChartUtils.lineChartSetup(
|
||||
chart,
|
||||
MaterialColors.getColor(
|
||||
view,
|
||||
@ -84,7 +84,7 @@ object WeightChart {
|
||||
)
|
||||
}
|
||||
|
||||
val rawData = GraphUtils.lineDataSet(
|
||||
val rawData = ChartUtils.lineDataSet(
|
||||
LineDataSet(
|
||||
data.mapIndexed { _, weight ->
|
||||
return@mapIndexed Entry(
|
||||
@ -98,7 +98,7 @@ object WeightChart {
|
||||
axisDependency = YAxis.AxisDependency.RIGHT
|
||||
}
|
||||
|
||||
val averageData = GraphUtils.lineDataSet(LineDataSet(averageYs, "Average")).apply {
|
||||
val averageData = ChartUtils.lineDataSet(LineDataSet(averageYs, "Average")).apply {
|
||||
axisDependency = YAxis.AxisDependency.RIGHT
|
||||
color = Color.GREEN
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import com.dzeio.openhealth.data.food.FoodRepository
|
||||
import com.dzeio.openhealth.data.openfoodfact.OpenFoodFactService
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.ArrayList
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
@ -23,9 +24,10 @@ class SearchFoodDialogViewModel @Inject internal constructor(
|
||||
val product = response.body()
|
||||
if (product != null) {
|
||||
|
||||
items.postValue(product.products
|
||||
.filter { it.name != null }
|
||||
.map { Food.fromOpenFoodFact(it) }
|
||||
items.postValue(
|
||||
product.products
|
||||
.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.units.Units
|
||||
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 dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlin.math.max
|
||||
@ -47,7 +47,6 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
|
||||
/**
|
||||
* Water Intake
|
||||
*/
|
||||
|
||||
binding.fragmentHomeWaterAdd.setOnClickListener {
|
||||
val water = viewModel.water.value
|
||||
if (water == null || !water.isToday()) {
|
||||
@ -91,7 +90,7 @@ class HomeFragment : BaseFragment<HomeViewModel, FragmentHomeBinding>(HomeViewMo
|
||||
}
|
||||
|
||||
// Make a line Chart using the graph library
|
||||
GraphUtils.lineChartSetup(
|
||||
ChartUtils.lineChartSetup(
|
||||
binding.weightGraph,
|
||||
MaterialColors.getColor(
|
||||
requireView(),
|
||||
|
@ -11,7 +11,7 @@ import com.dzeio.openhealth.Application
|
||||
import com.dzeio.openhealth.adapters.StepsAdapter
|
||||
import com.dzeio.openhealth.core.BaseFragment
|
||||
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 java.text.DateFormat
|
||||
import java.util.Calendar
|
||||
@ -53,46 +53,16 @@ class StepsHomeFragment :
|
||||
val chart = binding.chart
|
||||
|
||||
// setup serie
|
||||
val serie = BarSerie(chart).apply {
|
||||
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
|
||||
)
|
||||
val serie = BarSerie(chart)
|
||||
|
||||
chart.apply {
|
||||
series = arrayListOf(serie)
|
||||
// debug = true
|
||||
|
||||
ChartUtils.materielTheme(chart, requireView())
|
||||
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()}" }
|
||||
}
|
||||
|
||||
xAxis.apply {
|
||||
dataWidth = 604800000.0
|
||||
textPaint.color = MaterialColors.getColor(
|
||||
requireView(),
|
||||
com.google.android.material.R.attr.colorOnPrimaryContainer
|
||||
)
|
||||
textPaint.textSize = 32f
|
||||
onValueFormat = onValueFormat@{
|
||||
val formatter = DateFormat.getDateTimeInstance(
|
||||
@ -107,8 +77,10 @@ class StepsHomeFragment :
|
||||
}
|
||||
|
||||
viewModel.goal.observe(viewLifecycleOwner) {
|
||||
chart.yAxis.setGoalLine(it?.toFloat())
|
||||
chart.refresh()
|
||||
if (it != null) {
|
||||
chart.yAxis.addLine(it.toFloat())
|
||||
chart.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.items.observe(viewLifecycleOwner) { list ->
|
||||
@ -118,16 +90,8 @@ class StepsHomeFragment :
|
||||
return@observe
|
||||
}
|
||||
|
||||
|
||||
|
||||
// chart.animation.enabled = false
|
||||
// chart.animation.refreshRate = 60
|
||||
// chart.animation.duration = 300
|
||||
|
||||
// chart.scroller.zoomEnabled = false
|
||||
|
||||
// chart.xAxis.labels.size = 32f
|
||||
|
||||
val entries: HashMap<Long, Entry> = HashMap()
|
||||
|
||||
list.forEach {
|
||||
@ -138,7 +102,7 @@ class StepsHomeFragment :
|
||||
cal.set(Calendar.AM_PM, Calendar.AM)
|
||||
val ts = cal.timeInMillis
|
||||
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()
|
||||
|
@ -6,15 +6,16 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.navigation.fragment.findNavController
|
||||
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.core.BaseFragment
|
||||
import com.dzeio.openhealth.databinding.FragmentMainWaterHomeBinding
|
||||
import com.dzeio.openhealth.utils.GraphUtils
|
||||
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 com.dzeio.openhealth.utils.ChartUtils
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
@AndroidEntryPoint
|
||||
class WaterHomeFragment :
|
||||
@ -45,39 +46,45 @@ class WaterHomeFragment :
|
||||
|
||||
val chart = binding.chart
|
||||
|
||||
GraphUtils.barChartSetup(
|
||||
chart,
|
||||
MaterialColors.getColor(
|
||||
requireView(),
|
||||
com.google.android.material.R.attr.colorPrimary
|
||||
),
|
||||
MaterialColors.getColor(
|
||||
requireView(),
|
||||
com.google.android.material.R.attr.colorOnBackground
|
||||
)
|
||||
)
|
||||
val serie = BarSerie(chart)
|
||||
|
||||
chart.apply {
|
||||
ChartUtils.materielTheme(chart, requireView())
|
||||
|
||||
yAxis.apply {
|
||||
// onValueFormat
|
||||
}
|
||||
|
||||
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 {
|
||||
findNavController().navigate(WaterHomeFragmentDirections.actionNavWaterHomeToNavWaterSizeDialog())
|
||||
}
|
||||
|
||||
chart.xAxis.valueFormatter = GraphUtils.DateValueFormatter(1000 * 60 * 60 * 24)
|
||||
|
||||
viewModel.items.observe(viewLifecycleOwner) { list ->
|
||||
adapter.set(list)
|
||||
|
||||
val dataset = BarDataSet(
|
||||
list.map {
|
||||
return@map BarEntry(
|
||||
(it.timestamp / 1000 / 60 / 60 / 24).toFloat(),
|
||||
it.value.toFloat()
|
||||
)
|
||||
},
|
||||
""
|
||||
)
|
||||
val dataset = list.map {
|
||||
return@map Entry(
|
||||
it.timestamp.toDouble(),
|
||||
it.value.toFloat()
|
||||
)
|
||||
}
|
||||
|
||||
chart.data = BarData(dataset)
|
||||
chart.invalidate()
|
||||
serie.entries = dataset as ArrayList<Entry>
|
||||
chart.xAxis.x = dataset[0].x
|
||||
chart.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
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.LineChart
|
||||
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.formatter.ValueFormatter
|
||||
import com.github.mikephil.charting.interfaces.datasets.IBarLineScatterCandleBubbleDataSet
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
@ -20,7 +24,7 @@ import java.util.Locale
|
||||
*
|
||||
* TODO: migrate to DzeioCharts once it is ready
|
||||
*/
|
||||
object GraphUtils {
|
||||
object ChartUtils {
|
||||
|
||||
fun lineChartSetup(chart: LineChart, mainColor: Int, textColor: Int) {
|
||||
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(
|
||||
chart: BarLineChartBase<T>,
|
||||
mainColor: Int,
|
||||
@ -101,4 +101,62 @@ object GraphUtils {
|
||||
// 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:layout_width="match_parent"
|
||||
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"
|
||||
|
||||
include ':app'
|
||||
include ':charts'
|
||||
|
Loading…
x
Reference in New Issue
Block a user