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:
@ -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'
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ class XAxisLabels {
|
||||
it.isAntiAlias = true
|
||||
it.color = color
|
||||
it.textSize = size
|
||||
it.isFakeBoldText = true
|
||||
it.textAlign = Paint.Align.CENTER
|
||||
}
|
||||
}
|
||||
|
@ -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() }
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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()
|
||||
)
|
||||
}
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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) {
|
Reference in New Issue
Block a user