mirror of
https://github.com/dzeiocom/OpenHealth.git
synced 2025-07-08 20:39:18 +00:00
feat: Global update
This commit is contained in:
@ -1,64 +0,0 @@
|
||||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'org.jetbrains.kotlin.android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 32
|
||||
|
||||
defaultConfig {
|
||||
// Android 5 Lollipop
|
||||
minSdk 21
|
||||
|
||||
// Android 12
|
||||
targetSdk 32
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
||||
// Languages
|
||||
def locales = ["en", "fr"]
|
||||
|
||||
buildConfigField "String[]", "LOCALES", "new String[]{\""+locales.join("\",\"")+"\"}"
|
||||
resConfigs locales
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
||||
release {
|
||||
// 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_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.8.0'
|
||||
implementation 'androidx.appcompat:appcompat:1.4.2'
|
||||
implementation 'com.google.android.material:material:1.6.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
|
||||
}
|
38
charts/build.gradle.kts
Normal file
38
charts/build.gradle.kts
Normal file
@ -0,0 +1,38 @@
|
||||
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"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("com.google.android.material:material:1.6.1")
|
||||
}
|
2
charts/proguard-rules.pro
vendored
2
charts/proguard-rules.pro
vendored
@ -1,6 +1,6 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
# proguardFiles setting in build.gradle.kts.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
@ -1,5 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.dzeio.charts">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
</manifest>
|
||||
|
@ -12,113 +12,52 @@ 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.SerieAbstract
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import com.dzeio.charts.series.SerieInterface
|
||||
|
||||
class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
|
||||
View(context, attrs) {
|
||||
View(context, attrs), ChartViewInterface {
|
||||
|
||||
companion object {
|
||||
const val TAG = "DzeioCharts/ChartView"
|
||||
private companion object {
|
||||
const val TAG = "Charts/ChartView"
|
||||
}
|
||||
|
||||
var debug = false
|
||||
private val rect = RectF()
|
||||
|
||||
val xAxis = XAxis<Float>()
|
||||
|
||||
val yAxis = YAxis<Float>(this)
|
||||
|
||||
val animation = Animation()
|
||||
|
||||
val scroller = ChartScroll(this).apply {
|
||||
setOnChartMoved { movementX, movementY ->
|
||||
// Log.d(TAG, "scrolled: $movementX")
|
||||
movementOffset = movementX / 100
|
||||
refresh()
|
||||
}
|
||||
setOnZoomChanged {
|
||||
Log.d(TAG, "New Zoom: $it")
|
||||
zoom = (it * 1.2).toFloat()
|
||||
refresh()
|
||||
}
|
||||
private val debugStrokePaint = Paint().apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 8f
|
||||
color = Color.parseColor("#654321")
|
||||
}
|
||||
|
||||
private 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()
|
||||
}
|
||||
}
|
||||
override var debug: Boolean = false
|
||||
|
||||
/**
|
||||
* global padding
|
||||
*/
|
||||
var padding: Float = 8f
|
||||
override val xAxis = XAxis(this)
|
||||
|
||||
var series: ArrayList<SerieAbstract> = arrayListOf()
|
||||
set(value) {
|
||||
for (serie in value) {
|
||||
serie.view = this
|
||||
}
|
||||
field = value
|
||||
}
|
||||
override val yAxis = YAxis(this)
|
||||
|
||||
/**
|
||||
* Number of entries displayed at the same time
|
||||
*/
|
||||
private var zoom = 100f
|
||||
override var series: ArrayList<SerieInterface> = arrayListOf()
|
||||
|
||||
var movementOffset: Float = 0f
|
||||
|
||||
private val rectF = RectF()
|
||||
private val otherUseRectF = RectF()
|
||||
|
||||
fun refresh() {
|
||||
for (serie in series) {
|
||||
serie.prepareData()
|
||||
}
|
||||
rectF.set(
|
||||
padding,
|
||||
padding,
|
||||
measuredWidth - padding - yAxis.getWidth() - padding,
|
||||
height - padding
|
||||
)
|
||||
|
||||
removeCallbacks(animator)
|
||||
post(animator)
|
||||
}
|
||||
|
||||
private val fgPaint: Paint = Paint().also {
|
||||
it.isAntiAlias = true
|
||||
it.color = Color.parseColor("#123456")
|
||||
override fun refresh() {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
|
||||
if (yAxis.legendEnabled) {
|
||||
yAxis.display(canvas, measuredWidth, height)
|
||||
if (debug) {
|
||||
// draw corners
|
||||
canvas.drawRect(rect.apply {
|
||||
set(8f, 8f, width - 8f, height - 8f)
|
||||
}, debugStrokePaint)
|
||||
}
|
||||
|
||||
// chart draw rectangle
|
||||
rect.apply {
|
||||
set(0f, 0f, width.toFloat(), height.toFloat())
|
||||
}
|
||||
|
||||
for (serie in series) {
|
||||
serie.displayData(canvas, rectF)
|
||||
serie.onDraw(canvas, rect)
|
||||
}
|
||||
// canvas.drawRect(
|
||||
// measuredWidth - padding - yAxis.getWidth(),
|
||||
// 0f,
|
||||
// measuredWidth - padding,
|
||||
// height - padding,
|
||||
// fgPaint
|
||||
// )
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
|
||||
@ -127,40 +66,19 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
|
||||
return scroller.onTouchEvent(event)
|
||||
}
|
||||
|
||||
fun getXOffset(): Int {
|
||||
// Log.d(
|
||||
// TAG,
|
||||
// "baseOffset: ${xAxis.baseOffset}, mOffset: $movementOffset = ${xAxis.baseOffset + movementOffset}"
|
||||
// )
|
||||
// Log.d(
|
||||
// TAG,
|
||||
// "longestOffset: ${longestSerie()}, displayedEntries: ${getDisplayedEntries()} = ${longestSerie() - getDisplayedEntries()}"
|
||||
// )
|
||||
return min(
|
||||
max(0f, xAxis.baseOffset + movementOffset).toInt(),
|
||||
max(0, getCalculatedMax() - getDisplayedEntries())
|
||||
)
|
||||
}
|
||||
val scroller = ChartScroll(this).apply {
|
||||
var lastMovement = 0.0
|
||||
setOnChartMoved { movementX, _ ->
|
||||
|
||||
fun getDisplayedEntries(): Int {
|
||||
// Log.d(TAG, "Number of entries displayed ${list.size}, ${xAxis.entriesDisplayed} + (($zoom - 100) * 10) = ${xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()}")
|
||||
return max(0, xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt())
|
||||
}
|
||||
|
||||
fun getCalculatedMax(): Int {
|
||||
var size = 0
|
||||
for (serie in series) {
|
||||
if (serie.datas.size > size) size = serie.datas.size
|
||||
Log.d(TAG, "scrolled: ${(movementX - lastMovement) * (xAxis.increment / 10)}")
|
||||
xAxis.x = xAxis.x + (movementX - lastMovement) * (xAxis.increment / 10)
|
||||
lastMovement = movementX.toDouble()
|
||||
refresh()
|
||||
}
|
||||
return size
|
||||
// setOnZoomChanged {
|
||||
// Log.d(TAG, "New Zoom: $it")
|
||||
// zoom = (it * 1.2).toFloat()
|
||||
// refresh()
|
||||
// }
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
166
charts/src/main/java/com/dzeio/charts/ChartView.kt.old
Normal file
166
charts/src/main/java/com/dzeio/charts/ChartView.kt.old
Normal file
@ -0,0 +1,166 @@
|
||||
package com.dzeio.charts
|
||||
|
||||
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.util.Log
|
||||
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.SerieAbstract
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
|
||||
View(context, attrs) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "DzeioCharts/ChartView"
|
||||
}
|
||||
|
||||
override var debug = false
|
||||
|
||||
override val xAxis = XAxis<Float>()
|
||||
|
||||
override val yAxis = YAxis(this)
|
||||
|
||||
override val animation = Animation()
|
||||
|
||||
override val scroller = ChartScroll(this).apply {
|
||||
setOnChartMoved { movementX, _ ->
|
||||
// Log.d(TAG, "scrolled: $movementX")
|
||||
movementOffset = movementX / 100
|
||||
refresh()
|
||||
}
|
||||
setOnZoomChanged {
|
||||
Log.d(TAG, "New Zoom: $it")
|
||||
zoom = (it * 1.2).toFloat()
|
||||
refresh()
|
||||
}
|
||||
}
|
||||
|
||||
override 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()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* global padding
|
||||
*/
|
||||
override var padding: Float = 8f
|
||||
|
||||
override var series: ArrayList<SerieAbstract> = arrayListOf()
|
||||
set(value) {
|
||||
for (serie in value) {
|
||||
serie.view = this
|
||||
}
|
||||
field = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Number of entries displayed at the same time
|
||||
*/
|
||||
override var zoom = 100f
|
||||
|
||||
override var movementOffset: Float = 0f
|
||||
|
||||
override val rectF = RectF()
|
||||
override val otherUseRectF = RectF()
|
||||
|
||||
override fun refresh() {
|
||||
for (serie in series) {
|
||||
serie.prepareData()
|
||||
}
|
||||
rectF.set(
|
||||
padding,
|
||||
padding,
|
||||
measuredWidth - padding - yAxis.getWidth() - padding,
|
||||
height - padding
|
||||
)
|
||||
|
||||
removeCallbacks(animator)
|
||||
post(animator)
|
||||
}
|
||||
|
||||
override val fgPaint: Paint = Paint().also {
|
||||
it.isAntiAlias = true
|
||||
it.color = Color.parseColor("#123456")
|
||||
}
|
||||
|
||||
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(),
|
||||
// 0f,
|
||||
// measuredWidth - padding,
|
||||
// height - padding,
|
||||
// fgPaint
|
||||
// )
|
||||
super.onDraw(canvas)
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
performClick()
|
||||
return scroller.onTouchEvent(event)
|
||||
}
|
||||
|
||||
override fun getXOffset(): Int {
|
||||
// Log.d(
|
||||
// TAG,
|
||||
// "baseOffset: ${xAxis.baseOffset}, mOffset: $movementOffset = ${xAxis.baseOffset + movementOffset}"
|
||||
// )
|
||||
// Log.d(
|
||||
// TAG,
|
||||
// "longestOffset: ${longestSerie()}, displayedEntries: ${getDisplayedEntries()} = ${longestSerie() - getDisplayedEntries()}"
|
||||
// )
|
||||
return min(
|
||||
max(0f, xAxis.baseOffset + movementOffset).toInt(),
|
||||
max(0, getCalculatedMax() - getDisplayedEntries())
|
||||
)
|
||||
}
|
||||
|
||||
override fun getDisplayedEntries(): Int {
|
||||
// Log.d(TAG, "Number of entries displayed ${list.size}, ${xAxis.entriesDisplayed} + (($zoom - 100) * 10) = ${xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()}")
|
||||
return max(0, xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt())
|
||||
}
|
||||
|
||||
override fun getCalculatedMax(): Int {
|
||||
var size = 0
|
||||
for (serie in series) {
|
||||
if (serie.datas.size > size) size = serie.datas.size
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
override fun getXMax(displayedOnly: Boolean): Float {
|
||||
var max = 0f
|
||||
for (serie in series) {
|
||||
val res = serie.getYMax(displayedOnly)
|
||||
if (max < res) max = res
|
||||
}
|
||||
return max
|
||||
}
|
||||
}
|
35
charts/src/main/java/com/dzeio/charts/ChartViewInterface.kt
Normal file
35
charts/src/main/java/com/dzeio/charts/ChartViewInterface.kt
Normal file
@ -0,0 +1,35 @@
|
||||
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
|
||||
|
||||
/**
|
||||
* 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 EVERYTHING
|
||||
*/
|
||||
fun refresh()
|
||||
}
|
@ -1,25 +1,56 @@
|
||||
package com.dzeio.charts.axis
|
||||
|
||||
import com.dzeio.charts.XAxisLabels
|
||||
import android.graphics.RectF
|
||||
import com.dzeio.charts.ChartViewInterface
|
||||
import com.dzeio.charts.Entry
|
||||
|
||||
class XAxis<T> {
|
||||
class XAxis(
|
||||
private val view: ChartViewInterface
|
||||
) : XAxisInterface {
|
||||
override var x: Double = 0.0
|
||||
set(value) {
|
||||
val max = getXMax() - increment * displayCount
|
||||
if (value > max) {
|
||||
field = max
|
||||
return
|
||||
}
|
||||
|
||||
var max: T? = null
|
||||
var min: T? = null
|
||||
val min = getXMin()
|
||||
if (value < min) {
|
||||
field = min
|
||||
return
|
||||
}
|
||||
|
||||
val labels = XAxisLabels()
|
||||
field = value
|
||||
}
|
||||
override var increment: Double = 1.0
|
||||
override var displayCount: Int = 10
|
||||
override var labelCount: Int = 3
|
||||
|
||||
/**
|
||||
* Number of entries displayed in the chart at the same time
|
||||
*/
|
||||
var entriesDisplayed = 5
|
||||
|
||||
/**
|
||||
* Offset in the list
|
||||
*/
|
||||
var baseOffset = 0
|
||||
|
||||
var onValueFormat: (it: T) -> String = onValueFormat@{
|
||||
return@onValueFormat it.toString()
|
||||
override fun getPositionOnRect(entry: Entry, rect: RectF): Double {
|
||||
return translatePositionToRect(entry.x, rect)
|
||||
}
|
||||
}
|
||||
|
||||
fun translatePositionToRect(value: Double, rect: RectF): Double {
|
||||
val rectItem = rect.width() / displayCount // item size in graph
|
||||
return rectItem * value / increment
|
||||
}
|
||||
|
||||
override fun getXOffset(rect: RectF): Double {
|
||||
return translatePositionToRect(x, rect)
|
||||
}
|
||||
|
||||
override fun getXMax(): Double {
|
||||
return view.series.maxOf { serie ->
|
||||
serie.entries.maxOf { entry -> entry.x }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getXMin(): Double {
|
||||
return view.series.minOf { serie ->
|
||||
serie.entries.minOf { entry -> entry.x }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
25
charts/src/main/java/com/dzeio/charts/axis/XAxis.kt.old
Normal file
25
charts/src/main/java/com/dzeio/charts/axis/XAxis.kt.old
Normal file
@ -0,0 +1,25 @@
|
||||
package com.dzeio.charts.axis
|
||||
|
||||
import com.dzeio.charts.XAxisLabels
|
||||
|
||||
class XAxis<T> {
|
||||
|
||||
var max: T? = null
|
||||
var min: T? = null
|
||||
|
||||
val labels = XAxisLabels()
|
||||
|
||||
/**
|
||||
* Number of entries displayed in the chart at the same time
|
||||
*/
|
||||
var entriesDisplayed = 5
|
||||
|
||||
/**
|
||||
* Offset in the list
|
||||
*/
|
||||
var baseOffset = 0
|
||||
|
||||
var onValueFormat: (it: T) -> String = onValueFormat@{
|
||||
return@onValueFormat it.toString()
|
||||
}
|
||||
}
|
35
charts/src/main/java/com/dzeio/charts/axis/XAxisInterface.kt
Normal file
35
charts/src/main/java/com/dzeio/charts/axis/XAxisInterface.kt
Normal file
@ -0,0 +1,35 @@
|
||||
package com.dzeio.charts.axis
|
||||
|
||||
import android.graphics.RectF
|
||||
import com.dzeio.charts.Entry
|
||||
|
||||
sealed interface XAxisInterface {
|
||||
|
||||
/**
|
||||
* set X position
|
||||
*/
|
||||
var x: Double
|
||||
|
||||
/**
|
||||
* X increment
|
||||
*/
|
||||
var increment: Double
|
||||
|
||||
/**
|
||||
* indicate the max number of entries are displayed
|
||||
*/
|
||||
var displayCount: Int
|
||||
|
||||
/**
|
||||
* indicate the number of labels displayed
|
||||
*/
|
||||
var labelCount: Int
|
||||
|
||||
fun getPositionOnRect(entry: Entry, rect: RectF): Double
|
||||
|
||||
fun getXOffset(rect: RectF): Double
|
||||
|
||||
fun getXMax(): Double
|
||||
|
||||
fun getXMin(): Double
|
||||
}
|
@ -3,72 +3,85 @@ 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
|
||||
import android.graphics.RectF
|
||||
import com.dzeio.charts.ChartViewInterface
|
||||
|
||||
class YAxis<T>(
|
||||
private val chartView: ChartView
|
||||
) {
|
||||
var max: T? = null
|
||||
var min: T? = null
|
||||
class YAxis(
|
||||
private val view: ChartViewInterface
|
||||
) : YAxisInterface {
|
||||
|
||||
/**
|
||||
* Number of labels displayed on the sidebar
|
||||
*/
|
||||
var labelCount: Int = 4
|
||||
override var enabled = false
|
||||
|
||||
@ColorInt
|
||||
var color = Color.parseColor("#FC496D")
|
||||
|
||||
var textPaint: Paint = Paint().also {
|
||||
it.isAntiAlias = true
|
||||
it.color = color
|
||||
it.textSize = 30f
|
||||
it.textAlign = Paint.Align.RIGHT
|
||||
}
|
||||
|
||||
var linePaint = Paint().apply {
|
||||
override val textLabel = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = Color.parseColor("#FC496D")
|
||||
textSize = 30f
|
||||
textAlign = Paint.Align.RIGHT
|
||||
}
|
||||
|
||||
var legendEnabled = true
|
||||
override var labelCount: Int = 3
|
||||
|
||||
private val rect: Rect = Rect()
|
||||
private var min: Float? = null
|
||||
private var max: Float? = null
|
||||
|
||||
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()
|
||||
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!!
|
||||
}
|
||||
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)
|
||||
if (view.series.isEmpty()) {
|
||||
return 100f
|
||||
}
|
||||
return view.series
|
||||
.maxOf { serie ->
|
||||
if (serie.getDisplayedEntries().isEmpty()) {
|
||||
return@maxOf 0f
|
||||
}
|
||||
return@maxOf serie.getDisplayedEntries().maxOf { entry -> entry.y }
|
||||
}
|
||||
}
|
||||
|
||||
var onValueFormat: (value: Float, shortVersion: Boolean) -> String = { it, _ -> it.toString() }
|
||||
override fun getYMin(): Float {
|
||||
if (min != null) {
|
||||
return min!!
|
||||
}
|
||||
if (view.series.isEmpty()) {
|
||||
return 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, drawLocation: RectF) {
|
||||
val min = getYMin()
|
||||
val max = getYMax() - min
|
||||
val top = drawLocation.top
|
||||
val bottom = drawLocation.bottom
|
||||
|
||||
val increment = (bottom - top) / labelCount
|
||||
val valueIncrement = (max - min) / labelCount
|
||||
for (index in 0 until labelCount) {
|
||||
canvas.drawText(
|
||||
(valueIncrement * (labelCount + 1)).toString(),
|
||||
bottom - (index + 1) * increment,
|
||||
drawLocation.right,
|
||||
textLabel
|
||||
)
|
||||
}
|
||||
TODO("IDK if it works tbh")
|
||||
}
|
||||
}
|
74
charts/src/main/java/com/dzeio/charts/axis/YAxis.kt.old
Normal file
74
charts/src/main/java/com/dzeio/charts/axis/YAxis.kt.old
Normal file
@ -0,0 +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>(
|
||||
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 textPaint: Paint = Paint().also {
|
||||
it.isAntiAlias = true
|
||||
it.color = color
|
||||
it.textSize = 30f
|
||||
it.textAlign = Paint.Align.RIGHT
|
||||
}
|
||||
|
||||
var linePaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
}
|
||||
|
||||
var legendEnabled = true
|
||||
|
||||
private val rect: Rect = Rect()
|
||||
|
||||
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() }
|
||||
|
||||
}
|
58
charts/src/main/java/com/dzeio/charts/axis/YAxisInterface.kt
Normal file
58
charts/src/main/java/com/dzeio/charts/axis/YAxisInterface.kt
Normal file
@ -0,0 +1,58 @@
|
||||
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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
/**
|
||||
* function that draw our legend
|
||||
*/
|
||||
fun onDraw(canvas: Canvas, drawLocation: RectF)
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package com.dzeio.charts.axis
|
||||
|
||||
enum class YAxisPosition {
|
||||
LEFT,
|
||||
RIGHT
|
||||
}
|
@ -58,7 +58,7 @@ class ChartScroll(view: View) {
|
||||
return super.onScale(detector)
|
||||
}
|
||||
|
||||
override fun onScaleEnd(detector: ScaleGestureDetector?) {
|
||||
override fun onScaleEnd(detector: ScaleGestureDetector) {
|
||||
super.onScaleEnd(detector)
|
||||
|
||||
lastZoom += -currentZoom + 1
|
||||
|
@ -1,163 +1,68 @@
|
||||
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 kotlin.math.max
|
||||
import com.dzeio.charts.ChartView
|
||||
|
||||
class BarSerie : SerieAbstract() {
|
||||
class BarSerie(
|
||||
private val view: ChartView
|
||||
) : BaseSerie(view) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "DzeioCharts/BarSerie"
|
||||
private companion object {
|
||||
const val TAG = "Charts/BarSerie"
|
||||
}
|
||||
|
||||
var spacing: Float = 8f
|
||||
|
||||
var targetPercentList = arrayListOf<Float>()
|
||||
var percentList = arrayListOf<Float>()
|
||||
|
||||
private var fgPaint: Paint = Paint().also {
|
||||
it.isAntiAlias = true
|
||||
val barPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = Color.parseColor("#123456")
|
||||
}
|
||||
|
||||
var previousRefresh = 0
|
||||
override fun onDraw(canvas: Canvas, drawableSpace: RectF) {
|
||||
val spacing = drawableSpace.width() / view.xAxis.displayCount / 10
|
||||
val barWidth = drawableSpace.width() / view.xAxis.displayCount - spacing
|
||||
val displayedEntries = getDisplayedEntries()
|
||||
val max = view.yAxis.getYMax()
|
||||
val min = view.yAxis.getYMin()
|
||||
|
||||
private val r = Rect()
|
||||
// Log.d(TAG, "${space.left}, ${space.right}")
|
||||
|
||||
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
|
||||
for (entry in displayedEntries) {
|
||||
// calculated height in percent from 0 to 100
|
||||
val height = (1 - entry.y / max) * drawableSpace.height()
|
||||
// -1.945763981752553E-21
|
||||
// 2.103653925902835E-21
|
||||
val posX = view.xAxis.getPositionOnRect(entry, drawableSpace) - view.xAxis.getXOffset(drawableSpace) - canvas.width
|
||||
// Log.d(TAG, "gpor = ${view.xAxis.getPositionOnRect(entry, space)}, gxo = ${view.xAxis.getXOffset(space)}")
|
||||
// Log.d(TAG, "max = $max, y = ${entry.y}, height = $height")
|
||||
// Log.d(TAG, "posX: ${posX / 60 / 60 / 1000}, offsetX = ${view.xAxis.x / (1000 * 60 * 60)}, x = ${entry.x / (1000 * 60 * 60)}, pouet: ${(view.xAxis.x + view.xAxis.displayCount * view.xAxis.increment) / (1000 * 60 * 60)}")
|
||||
|
||||
// Log.d(
|
||||
// TAG, """
|
||||
// ${posX.toFloat()},
|
||||
// $height,
|
||||
// ${(posX + barWidth).toFloat()},
|
||||
// ${space.bottom}""".trimIndent()
|
||||
// )
|
||||
|
||||
canvas.drawRect(
|
||||
posX.toFloat(),
|
||||
height,
|
||||
(posX + barWidth).toFloat(),
|
||||
drawableSpace.bottom,
|
||||
barPaint
|
||||
)
|
||||
|
||||
if (value != percentList[i]) {
|
||||
needNewFrame = true
|
||||
percentList[i] = value
|
||||
}
|
||||
}
|
||||
return needNewFrame
|
||||
}
|
||||
canvas.drawText(
|
||||
entry.y.toString(),
|
||||
(posX + barWidth / 2).toFloat(),
|
||||
drawableSpace.bottom / 2,
|
||||
view.yAxis.textLabel.apply {
|
||||
textAlign = Paint.Align.CENTER
|
||||
|
||||
override fun prepareData() {
|
||||
val max: Float = if (view.yAxis.max != null) view.yAxis.max!! else {
|
||||
getMax()
|
||||
}
|
||||
|
||||
targetPercentList = arrayListOf()
|
||||
|
||||
// Log.d(TAG, "offset: ${view.getXOffset()}, displayed: ${view.getDisplayedEntries()}")
|
||||
for (item in datas.subList(
|
||||
view.getXOffset(),
|
||||
view.getXOffset() + view.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(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(
|
||||
text,
|
||||
left + (right - left) / 2,
|
||||
y,
|
||||
view.xAxis.labels.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getMax(): Float {
|
||||
var calculatedMax = 0f
|
||||
for (entry in datas.subList(
|
||||
view.getXOffset(),
|
||||
view.getDisplayedEntries() + view.getXOffset()
|
||||
)) {
|
||||
if (entry.y > calculatedMax) calculatedMax = entry.y
|
||||
}
|
||||
return if (calculatedMax < 0) 0f else calculatedMax
|
||||
}
|
||||
}
|
||||
}
|
160
charts/src/main/java/com/dzeio/charts/series/BarSerie.kt.old
Normal file
160
charts/src/main/java/com/dzeio/charts/series/BarSerie.kt.old
Normal file
@ -0,0 +1,160 @@
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
charts/src/main/java/com/dzeio/charts/series/BaseSerie.kt
Normal file
33
charts/src/main/java/com/dzeio/charts/series/BaseSerie.kt
Normal file
@ -0,0 +1,33 @@
|
||||
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> {
|
||||
// -+ view.xAxis.increment = one out of display
|
||||
val minX = view.xAxis.x - view.xAxis.increment
|
||||
val maxX =
|
||||
view.xAxis.x + view.xAxis.displayCount * 2 * view.xAxis.increment + view.xAxis.increment
|
||||
|
||||
return entries.filter {
|
||||
return@filter it.x in minX..maxX
|
||||
} as ArrayList<Entry>
|
||||
}
|
||||
|
||||
abstract override fun onDraw(canvas: Canvas, drawableSpace: RectF)
|
||||
}
|
@ -12,18 +12,31 @@ abstract class SerieAbstract {
|
||||
|
||||
lateinit var view: ChartView
|
||||
|
||||
/**
|
||||
* get Serie Y max
|
||||
*/
|
||||
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
|
||||
var max = Float.MIN_VALUE
|
||||
val localDatas = if (displayedOnly) getDisplayedEntries() else datas
|
||||
for (data in localDatas) {
|
||||
if (max < data.y) max= data.y
|
||||
if (max < data.y) max = data.y
|
||||
}
|
||||
return max
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get Serie Y min
|
||||
*/
|
||||
fun getYMin(displayedOnly: Boolean = false): Float {
|
||||
var min = Float.MAX_VALUE
|
||||
val localDatas = if (displayedOnly) getDisplayedEntries() else datas
|
||||
for (data in localDatas) {
|
||||
if (min > data.y) min = data.y
|
||||
}
|
||||
return min
|
||||
}
|
||||
|
||||
/**
|
||||
* Animation updates
|
||||
*/
|
||||
@ -41,4 +54,11 @@ abstract class SerieAbstract {
|
||||
* @param rect the rectangle in which you have to draw data
|
||||
*/
|
||||
abstract fun displayData(canvas: Canvas, rect: RectF)
|
||||
|
||||
protected fun getDisplayedEntries(): MutableList<Entry> {
|
||||
return datas.subList(
|
||||
view.getXOffset(),
|
||||
min(datas.size, view.getDisplayedEntries() + view.getXOffset())
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
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>
|
||||
|
||||
fun onDraw(canvas: Canvas, drawableSpace: RectF)
|
||||
}
|
@ -1,324 +0,0 @@
|
||||
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.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
|
||||
|
||||
class BarChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet? = null) :
|
||||
View(context, attrs) {
|
||||
|
||||
companion object {
|
||||
const val TAG = "DzeioCharts/BarView"
|
||||
}
|
||||
|
||||
var debug: Boolean = false
|
||||
|
||||
/**
|
||||
* Number of entries displayed at the same time
|
||||
*/
|
||||
private var zoom = 100f
|
||||
|
||||
/**
|
||||
* Number of labels displayed at the same time
|
||||
*/
|
||||
var numberOfLabels = 3
|
||||
|
||||
/**
|
||||
* Spacing between entries
|
||||
*/
|
||||
var spacing = 22
|
||||
|
||||
val xAxis: XAxis<Double> = XAxis()
|
||||
|
||||
val yAxis: YAxis<Float> = YAxis()
|
||||
|
||||
val animation = Animation()
|
||||
|
||||
private val textTopMargin = 5
|
||||
|
||||
private var barWidth: Int = 0
|
||||
|
||||
private var percentList: ArrayList<Float> = ArrayList()
|
||||
|
||||
/**
|
||||
* value goes from 1 to 0 (1 at bottom, 0 at top)
|
||||
*/
|
||||
private var targetPercentList: ArrayList<Float> = ArrayList()
|
||||
|
||||
private var fgPaint: Paint = Paint().also {
|
||||
it.isAntiAlias = true
|
||||
it.color = yAxis.color
|
||||
}
|
||||
|
||||
private var linePaint = Paint().apply {
|
||||
color = Color.parseColor("#123456")
|
||||
strokeWidth = 6f
|
||||
}
|
||||
|
||||
private val rect: Rect = Rect()
|
||||
|
||||
private var bottomTextDescent = 0
|
||||
private var bottomTextHeight = 0
|
||||
|
||||
private var movementOffset: Int = 0
|
||||
|
||||
private val animator: Runnable = object : Runnable {
|
||||
override fun run() {
|
||||
var needNewFrame = false
|
||||
// var txt = ""
|
||||
// for (tpl in targetPercentList) {
|
||||
// txt += "$tpl, "
|
||||
// }
|
||||
// Log.d(TAG, txt)
|
||||
for (i in targetPercentList.indices) {
|
||||
val value = animation.updateValue(
|
||||
1f,
|
||||
targetPercentList[i],
|
||||
percentList[i],
|
||||
0f,
|
||||
0.01f
|
||||
)
|
||||
|
||||
if (value != percentList[i]) {
|
||||
// if (!needNewFrame) {
|
||||
// Log.d(TAG, "$i, $value, ${percentList[i]}")
|
||||
// }
|
||||
needNewFrame = true
|
||||
percentList[i] = value
|
||||
}
|
||||
}
|
||||
if (needNewFrame) {
|
||||
postDelayed(this, animation.getDelay().toLong())
|
||||
}
|
||||
invalidate()
|
||||
}
|
||||
}
|
||||
|
||||
var list: ArrayList<Entry> = arrayListOf()
|
||||
|
||||
private var fiftyPercent = 0f
|
||||
|
||||
private var bottomTexts: ArrayList<String> = arrayListOf()
|
||||
|
||||
var previousOffset = getXOffset()
|
||||
|
||||
fun refresh() {
|
||||
val r = Rect()
|
||||
|
||||
// // prepare bottom texts
|
||||
bottomTextDescent = 0
|
||||
bottomTextHeight = 0
|
||||
bottomTexts = arrayListOf()
|
||||
|
||||
// // prepare values
|
||||
|
||||
// set the bar Width (also handle div by 0)
|
||||
barWidth = measuredWidth / max(min(list.size, getDisplayedEntries()), 1) - spacing
|
||||
|
||||
// calculate max depending on the maximum value displayed or set in the yAxis params
|
||||
val max: Float = if (yAxis.max != null) yAxis.max!! else {
|
||||
var calculatedMax = 0f
|
||||
for (entry in list.subList(
|
||||
this.getXOffset(),
|
||||
getDisplayedEntries() + this.getXOffset()
|
||||
)) {
|
||||
if (entry.y > calculatedMax) calculatedMax = entry.y
|
||||
}
|
||||
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)
|
||||
bottomTexts.add(text)
|
||||
|
||||
// get Text boundaries
|
||||
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
|
||||
|
||||
// add to animations the values
|
||||
targetPercentList.add(1 - item.y / max)
|
||||
}
|
||||
|
||||
// post list
|
||||
val movement = getXOffset() - previousOffset
|
||||
previousOffset = getXOffset()
|
||||
// Log.d(TAG, movement.toString())
|
||||
if (movement >= 1) {
|
||||
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 + 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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// Misc operations
|
||||
fgPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
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) {
|
||||
// Log.d(TAG, percentList[i - 1].toString())
|
||||
val left = spacing * i + barWidth * (i - 1).toFloat()
|
||||
// Log.d(TAG, "$spacing, $i, $barWidth = $left")
|
||||
val right = (spacing + barWidth) * i.toFloat()
|
||||
val top = bottom * percentList[i - 1]
|
||||
|
||||
// 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 (debug) {
|
||||
canvas.drawText(
|
||||
bottomTexts[i - 1].toString(),
|
||||
left + (right - left) / 2,
|
||||
top + (bottom - top) / 2,
|
||||
xAxis.labels.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bottomTexts.isNotEmpty() && numberOfLabels > 0) {
|
||||
val size = bottomTexts.size
|
||||
var i = 1
|
||||
var items = size / max(2, (numberOfLabels - 2))
|
||||
|
||||
// handle cases where size is even and numberOfLabels is 3
|
||||
if (size % 2 != 0) {
|
||||
items += 1
|
||||
}
|
||||
|
||||
|
||||
// Log.i(TAG, "$size / max($numberOfLabels - 2, 2) = $items")
|
||||
for (s in bottomTexts) {
|
||||
if ((numberOfLabels <= 2 || i % items != 0) && i != 1 && i != size) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
|
||||
// Log.i(TAG, "Drawing $i")
|
||||
|
||||
xAxis.labels.build().getTextBounds(s, 0, s.length, rect)
|
||||
|
||||
canvas.drawText(
|
||||
s,
|
||||
// handle last entry overflowing
|
||||
min(
|
||||
// handle first entry overflowing
|
||||
max(
|
||||
(spacing * i + barWidth * (i - 1) + barWidth / 2).toFloat(),
|
||||
rect.width() / 2f
|
||||
),
|
||||
measuredWidth - rect.width() / 2f
|
||||
),
|
||||
|
||||
(height - bottomTextDescent).toFloat(),
|
||||
xAxis.labels.build()
|
||||
)
|
||||
i++
|
||||
if (numberOfLabels == 1) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// override fun onChartMoved(movementX: Float, movementY: Float) {
|
||||
// movementOffset = (movementX / 100).toInt()
|
||||
// refresh()
|
||||
// }
|
||||
|
||||
// override fun onZoomChanged(scale: Float) {
|
||||
// Log.d(TAG, "New Zoom: $scale")
|
||||
// zoom = (scale * 1.2).toFloat()
|
||||
// refresh()
|
||||
// }
|
||||
|
||||
private fun getXOffset(): Int {
|
||||
return min(max(0, xAxis.baseOffset + movementOffset), list.size - getDisplayedEntries())
|
||||
}
|
||||
|
||||
private fun getDisplayedEntries(): Int {
|
||||
// Log.d(TAG, "Number of entries displayed ${list.size}, ${xAxis.entriesDisplayed} + (($zoom - 100) * 10) = ${xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()}")
|
||||
return max(1, min(list.size, xAxis.entriesDisplayed + ((zoom - 100) * 10).toInt()))
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user