1
0
mirror of https://github.com/dzeiocom/OpenHealth.git synced 2025-07-08 20:39:18 +00:00

feat: Continue work on Chart

This commit is contained in:
2022-07-28 18:18:08 +02:00
parent f6b5715572
commit 4a9d4b8ede
19 changed files with 941 additions and 115 deletions

View File

@ -7,25 +7,49 @@ android {
compileSdk 32
defaultConfig {
// Android 5 Lollipop
minSdk 21
// Android 12
targetSdk 32
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
// Languages
def locales = ["en", "fr"]
buildConfigField "String[]", "LOCALES", "new String[]{\""+locales.join("\",\"")+"\"}"
resConfigs locales
}
buildTypes {
release {
minifyEnabled false
// Slimmer version
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
debug {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
debuggable true
// make it debuggable
renderscriptDebuggable true
// Optimization Level
renderscriptOptimLevel 0
}
}
// Compile using JAVA 8
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_6
targetCompatibility JavaVersion.VERSION_1_6
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.6'
jvmTarget = '1.8'
}
}

View File

@ -27,7 +27,7 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
val xAxis = XAxis<Float>()
val yAxis = YAxis<Float>()
val yAxis = YAxis<Float>(this)
val animation = Animation()
@ -90,7 +90,7 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
rectF.set(
padding,
padding,
measuredWidth - padding - yAxis.getWidth(longestSerie().toFloat()) - padding,
measuredWidth - padding - yAxis.getWidth() - padding,
height - padding
)
@ -104,16 +104,21 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
}
override fun onDraw(canvas: Canvas) {
if (yAxis.legendEnabled) {
yAxis.display(canvas, measuredWidth, height)
}
for (serie in series) {
serie.displayData(canvas, rectF)
}
canvas.drawRect(
measuredWidth - padding - yAxis.getWidth(longestSerie().toFloat()),
0f,
measuredWidth - padding,
height - padding,
fgPaint
)
// canvas.drawRect(
// measuredWidth - padding - yAxis.getWidth(),
// 0f,
// measuredWidth - padding,
// height - padding,
// fgPaint
// )
super.onDraw(canvas)
}
@ -133,7 +138,7 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
// )
return min(
max(0f, xAxis.baseOffset + movementOffset).toInt(),
longestSerie() - getDisplayedEntries()
max(0, getCalculatedMax() - getDisplayedEntries())
)
}
@ -142,11 +147,20 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
return max(0, xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt())
}
private fun longestSerie(): Int {
fun getCalculatedMax(): Int {
var size = 0
for (serie in series) {
if (serie.datas.size > size) size = serie.datas.size
}
return size
}
fun getXMax(displayedOnly: Boolean = false): Float {
var max = 0f
for (serie in series) {
val res = serie.getYMax(displayedOnly)
if (max < res) max = res
}
return max
}
}

View File

@ -16,6 +16,7 @@ class XAxisLabels {
it.isAntiAlias = true
it.color = color
it.textSize = size
it.isFakeBoldText = true
it.textAlign = Paint.Align.CENTER
}
}

View File

@ -1,30 +1,74 @@
package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import androidx.annotation.ColorInt
import com.dzeio.charts.ChartView
import com.dzeio.charts.utils.drawDottedLine
class YAxis<T> {
class YAxis<T>(
private val chartView: ChartView
) {
var max: T? = null
var min: T? = null
/**
* Number of labels displayed on the sidebar
*/
var labelCount: Int = 4
@ColorInt
var color = Color.parseColor("#FC496D")
var paint: Paint = Paint().also {
var textPaint: Paint = Paint().also {
it.isAntiAlias = true
it.color = color
it.textSize = 30f
it.textAlign = Paint.Align.CENTER
it.textAlign = Paint.Align.RIGHT
}
var linePaint = Paint().apply {
isAntiAlias = true
}
var legendEnabled = true
private val rect: Rect = Rect()
fun getWidth(max: Float): Int {
paint.getTextBounds(max.toString(), 0, max.toString().length, rect)
return rect.width()
fun getWidth(): Int {
var maxWidth = 0
val max = chartView.getXMax(true)
val vIncrement = max / labelCount
for (i in 0 until labelCount) {
val text = onValueFormat(vIncrement * (labelCount - i), true)
textPaint.getTextBounds(text, 0, text.length, rect)
if (rect.width() > maxWidth) maxWidth = rect.width()
}
return maxWidth + chartView.padding.toInt()
}
/**
* Function to display the YAxis sidebar on the right
*
* it migh also display content over the Graph
*/
fun display(canvas: Canvas, width: Int, height: Int) {
val max = chartView.getXMax(true)
val increment = (height - chartView.padding * 2) / labelCount
val vIncrement = max / labelCount
for (i in 0 until labelCount) {
val text = onValueFormat(vIncrement * (labelCount - i), true)
textPaint.getTextBounds(text, 0, text.length, rect)
val posY = increment * i.toFloat()
canvas.drawDottedLine(0f, posY + chartView.padding, width - rect.width().toFloat() - chartView.padding, posY, 40f, linePaint)
canvas.drawText(text,
width.toFloat() - chartView.padding, posY + chartView.padding, textPaint)
// canvas.drawDottedLine(0f, posY, measuredWidth.toFloat(), posY, 10f, linePaint)
}
}
var onValueFormat: (value: Float, shortVersion: Boolean) -> String = { it, _ -> it.toString() }
}

View File

@ -7,6 +7,16 @@ import android.view.View
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
@ -60,7 +70,10 @@ class ChartScroll(view: View) {
* Code mostly stolen from https://developer.android.com/training/gestures/scale#drag
*/
fun onTouchEvent(ev: MotionEvent): Boolean {
scaleGestureDetector.onTouchEvent(ev)
if (zoomEnabled) {
scaleGestureDetector.onTouchEvent(ev)
}
when (ev.actionMasked) {
MotionEvent.ACTION_DOWN -> {
@ -86,7 +99,9 @@ class ChartScroll(view: View) {
posX += x - lastTouchX
posY += y - lastTouchY
onChartMoved?.invoke(-posX, posY)
if (scrollEnabled) {
onChartMoved?.invoke(-posX, posY)
}
// Remember this touch position for the next move event
lastTouchX = x

View File

@ -1,15 +0,0 @@
package com.dzeio.charts.components
import android.graphics.RectF
class Sidebar {
var enabled = true
private val rect: RectF = RectF()
fun refresh(max: Float) {
}
}

View File

@ -2,6 +2,7 @@ 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
@ -23,6 +24,8 @@ class BarSerie : SerieAbstract() {
var previousRefresh = 0
private val r = Rect()
override fun onUpdate(): Boolean {
var needNewFrame = false
for (i in targetPercentList.indices) {
@ -31,7 +34,7 @@ class BarSerie : SerieAbstract() {
targetPercentList[i],
percentList[i],
0f,
0.01f
0.00f
)
if (value != percentList[i]) {
@ -113,27 +116,33 @@ class BarSerie : SerieAbstract() {
}
override fun displayData(canvas: Canvas, rect: RectF) {
val barWidth = rect.width() / view.getDisplayedEntries() - spacing
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()
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()
val top = (bottom - rect.top) * percentList[i - 1]
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)
if (view.debug) {
val targetTop = (bottom - rect.top) * targetPercentList[i - 1]
val text = view.yAxis.onValueFormat(getMax() - getMax() * 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(
(getMax() - getMax() * targetPercentList[i - 1]).toString(),
text,
left + (right - left) / 2,
top + (bottom - top) / 2,
y,
view.xAxis.labels.build()
)
}

View File

@ -4,6 +4,7 @@ import android.graphics.Canvas
import android.graphics.RectF
import com.dzeio.charts.ChartView
import com.dzeio.charts.Entry
import kotlin.math.min
abstract class SerieAbstract {
@ -11,6 +12,18 @@ abstract class SerieAbstract {
lateinit var view: ChartView
fun getYMax(displayedOnly: Boolean = false): Float {
var max = 0f
var localDatas = if (displayedOnly) datas.subList(
view.getXOffset(),
min(view.getXOffset() + view.getDisplayedEntries(), datas.size)
) else datas
for (data in localDatas) {
if (max < data.y) max= data.y
}
return max
}
/**
* Animation updates
*/

View File

@ -2,15 +2,16 @@ package com.dzeio.charts.views
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.RectF
import android.util.AttributeSet
import android.view.View
import com.dzeio.charts.Animation
import com.dzeio.charts.Entry
import com.dzeio.charts.axis.XAxis
import com.dzeio.charts.axis.YAxis
import com.dzeio.charts.utils.drawDottedLine
import kotlin.math.abs
import kotlin.math.max
import kotlin.math.min
@ -60,7 +61,13 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
it.isAntiAlias = true
it.color = yAxis.color
}
private val rect: RectF = RectF()
private var linePaint = Paint().apply {
color = Color.parseColor("#123456")
strokeWidth = 6f
}
private val rect: Rect = Rect()
private var bottomTextDescent = 0
private var bottomTextHeight = 0
@ -101,9 +108,11 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
var list: ArrayList<Entry> = arrayListOf()
private var fiftyPercent = 0f
private var bottomTexts: ArrayList<String> = arrayListOf()
var previousRefresh = movementOffset
var previousOffset = getXOffset()
fun refresh() {
val r = Rect()
@ -130,10 +139,13 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
if (calculatedMax < 0) 0f else calculatedMax
}
fiftyPercent = max / 2
// make sure the target list
// Log.d(TAG, list.size.toString())
targetPercentList = arrayListOf()
// Log.d(TAG, "List selected: ${getXOffset()} to ${getXOffset() + getDisplayedEntries() - 1}")
for (item in list.subList(getXOffset(), getXOffset() + getDisplayedEntries())) {
// // Process bottom texts
val text = xAxis.onValueFormat(item.x)
@ -160,15 +172,23 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
}
// post list
val movement = movementOffset - previousRefresh
previousRefresh = movementOffset
val movement = getXOffset() - previousOffset
previousOffset = getXOffset()
// Log.d(TAG, movement.toString())
if (movement >= 1) {
percentList = percentList.subList(1, percentList.size).toCollection(ArrayList())
percentList.add(1f)
percentList = percentList.subList(min(movement, percentList.size), percentList.size).toCollection(
ArrayList()
)
for (i in 0 until movement) {
percentList.add(1f)
}
} else if (movement <= -1) {
percentList = percentList.subList(0, percentList.size - 1).toCollection(ArrayList())
percentList.add(0, 1f)
percentList = percentList.subList(0, percentList.size + movement).toCollection(
ArrayList()
)
for (i in 0 until abs(movement)) {
percentList.add(0, 1f)
}
}
if (percentList.isEmpty() || percentList.size < targetPercentList.size) {
val temp = targetPercentList.size - percentList.size
@ -188,11 +208,32 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
color = yAxis.color
}
linePaint = Paint().apply {
color = yAxis.lineColor
strokeWidth = 4f
}
removeCallbacks(animator)
post(animator)
}
override fun onDraw(canvas: Canvas) {
val bottom = height - bottomTextHeight - textTopMargin.toFloat()
// draw sidebar and lines
val increment = bottom / yAxis.labelCount
val vIncrement = fiftyPercent * 2 / yAxis.labelCount
for (i in 0 until yAxis.labelCount) {
val text = (vIncrement * (yAxis.labelCount - i)).toString()
xAxis.labels.build().getTextBounds(text, 0, text.length, rect)
val posY = increment * i
canvas.drawDottedLine(0f, posY, measuredWidth.toFloat(), posY, 40f, linePaint)
canvas.drawText(text,
(measuredWidth - rect.width()).toFloat(), posY + rect.height() + 20f, xAxis.labels.build())
// canvas.drawDottedLine(0f, posY, measuredWidth.toFloat(), posY, 10f, linePaint)
}
if (percentList.isNotEmpty()) {
// draw each rectangles
for (i in 1..percentList.size) {
@ -200,7 +241,6 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
val left = spacing * i + barWidth * (i - 1).toFloat()
// Log.d(TAG, "$spacing, $i, $barWidth = $left")
val right = (spacing + barWidth) * i.toFloat()
val bottom = height - bottomTextHeight - textTopMargin.toFloat()
val top = bottom * percentList[i - 1]
// create rounded rect
@ -227,7 +267,6 @@ class BarChartView @JvmOverloads constructor(context: Context?, attrs: Attribute
items += 1
}
val rect = Rect()
// Log.i(TAG, "$size / max($numberOfLabels - 2, 2) = $items")
for (s in bottomTexts) {