mirror of
https://github.com/dzeiocom/charts.git
synced 2025-04-23 19:12:10 +00:00
feat: Basic idea is present
This commit is contained in:
parent
e1946e98d0
commit
3965ac2f81
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
#Sun Aug 07 22:38:24 CEST 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-rc-1-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
@ -9,6 +9,7 @@ import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
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
|
||||
@ -82,16 +83,21 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
|
||||
return@setOnChartMoved
|
||||
}
|
||||
if (xAxis.scrollEnabled) {
|
||||
xAxis.x += (movementX - lastMovementX) * xAxis.getDataWidth() / width
|
||||
val currentXMax = xAxis.getCurrentMax()
|
||||
val currentXMin = xAxis.getCurrentMin()
|
||||
val change = (movementX.toDouble() - lastMovementX) * (currentXMax - currentXMin) / width
|
||||
xAxis.setCurrent(
|
||||
currentXMin + change,
|
||||
currentXMax + change
|
||||
)
|
||||
lastMovementX = movementX.toDouble()
|
||||
}
|
||||
|
||||
if (yAxis.scrollEnabled) {
|
||||
val currentYMax = yAxis.getYMax()
|
||||
val currentYMin = yAxis.getYMin()
|
||||
val currentYMax = yAxis.getCurrentMax()
|
||||
val currentYMin = yAxis.getCurrentMin()
|
||||
val change = (movementY - lastMovementY) * (currentYMax - currentYMin) / height
|
||||
yAxis.setYMax(currentYMax + change)
|
||||
yAxis.setYMin(currentYMin + change)
|
||||
yAxis.setCurrent(currentYMin + change, currentYMax + change)
|
||||
lastMovementY = movementY
|
||||
}
|
||||
|
||||
@ -99,7 +105,9 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
|
||||
}
|
||||
setOnToggleScroll {
|
||||
// note: true == no scroll
|
||||
parent?.requestDisallowInterceptTouchEvent(!it && (yAxis.scrollEnabled || xAxis.scrollEnabled))
|
||||
parent?.requestDisallowInterceptTouchEvent(
|
||||
!it && (yAxis.scrollEnabled || xAxis.scrollEnabled || xAxis.zoomEnabled || yAxis.zoomEnabled)
|
||||
)
|
||||
}
|
||||
setOnChartClick { x, y ->
|
||||
if (getDataset().isEmpty()) {
|
||||
@ -132,11 +140,53 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
|
||||
}
|
||||
refresh()
|
||||
}
|
||||
// setOnZoomChanged {
|
||||
// Log.d(TAG, "New Zoom: $it")
|
||||
setOnZoomChanged { scaleX, scaleY ->
|
||||
val animationEnabled = animator.enabled
|
||||
if (animationEnabled) animator.enabled = false
|
||||
Log.d(TAG, "Zoom: Start")
|
||||
val factor = 0.05f
|
||||
if (scaleX != 0f && xAxis.zoomEnabled && xAxis.scrollEnabled) {
|
||||
Log.d(TAG, "ScaleX: scaleX")
|
||||
Log.d(TAG, "ScaleX: $scaleX")
|
||||
val xDistance = xAxis.getCurrentMax() - xAxis.getCurrentMin()
|
||||
Log.d(TAG, "ScaleX: xDistance * (scaleX * factor)")
|
||||
Log.d(TAG, "ScaleX: $xDistance * ${(scaleX * factor)}")
|
||||
val xTransformer = xDistance * (scaleX * factor)
|
||||
Log.d(TAG, "ScaleX: xTransformer")
|
||||
Log.d(TAG, "ScaleX: $xTransformer")
|
||||
val xMin = xAxis.getCurrentMin() - xTransformer
|
||||
val dataWidth = xAxis.getDataWidth() + xTransformer
|
||||
Log.d(TAG, "ScaleX: previousXMin, previousDataWidth")
|
||||
Log.d(TAG, "ScaleX: ${xAxis.getCurrentMin()}, ${xAxis.getDataWidth()}")
|
||||
Log.d(TAG, "ScaleX: xMin, dataWidth")
|
||||
Log.d(TAG, "ScaleX: $xMin, $dataWidth")
|
||||
xAxis.setCurrentMin(xMin)
|
||||
xAxis.dataWidth = dataWidth
|
||||
}
|
||||
if (scaleY != 0f && yAxis.zoomEnabled && yAxis.scrollEnabled) {
|
||||
Log.d(TAG, "ScaleY: scaleY")
|
||||
Log.d(TAG, "ScaleY: $scaleY")
|
||||
val yDistance = yAxis.getCurrentMax() - yAxis.getCurrentMin()
|
||||
Log.d(TAG, "ScaleY: yDistance * (scaleY * factor)")
|
||||
Log.d(TAG, "ScaleY: $yDistance * ${(scaleY * factor)}")
|
||||
val yTransformer = yDistance * (scaleY * factor)
|
||||
Log.d(TAG, "ScaleY: yTransformer")
|
||||
Log.d(TAG, "ScaleY: $yTransformer")
|
||||
val yMin = yAxis.getCurrentMin() - yTransformer
|
||||
val yMax = yAxis.getCurrentMax() + yTransformer
|
||||
Log.d(TAG, "ScaleY: previousYMin, previousYMax")
|
||||
Log.d(TAG, "ScaleY: ${yAxis.getCurrentMin()}, ${yAxis.getCurrentMax()}")
|
||||
Log.d(TAG, "ScaleY: yMin, yMax")
|
||||
Log.d(TAG, "ScaleY: $yMin, $yMax")
|
||||
yAxis.setCurrentMin(yMin)
|
||||
yAxis.setCurrentMax(yMax.coerceAtLeast(yMin + 1f))
|
||||
}
|
||||
// Log.d(TAG, "Zoom: Done")
|
||||
// Log.d(TAG, "[after] : tr: $scaleX, $scaleY, xMin: $xMin, xMax: $xMax, yMin: $yMin, yMax: $yMax")
|
||||
// zoom = (it * 1.2).toFloat()
|
||||
// refresh()
|
||||
// }
|
||||
refresh()
|
||||
if (animationEnabled) animator.enabled = true
|
||||
}
|
||||
}
|
||||
|
||||
// rect used for calculations
|
||||
|
119
library/src/main/java/com/dzeio/charts/axis/AxisInterface.kt
Normal file
119
library/src/main/java/com/dzeio/charts/axis/AxisInterface.kt
Normal file
@ -0,0 +1,119 @@
|
||||
package com.dzeio.charts.axis
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import com.dzeio.charts.Entry
|
||||
|
||||
interface AxisInterface<T> {
|
||||
/**
|
||||
* enable/disable the display of the xAxis
|
||||
*/
|
||||
var enabled: Boolean
|
||||
|
||||
/**
|
||||
* paint for the lines
|
||||
*/
|
||||
val linePaint: Paint
|
||||
|
||||
/**
|
||||
* text Paint
|
||||
*/
|
||||
val textPaint: Paint
|
||||
|
||||
/**
|
||||
* if global limits are sets to true [getCurrentMin] while never be `<` to [getMin] and same for max
|
||||
*/
|
||||
var keepGlobalLimits: Boolean
|
||||
|
||||
/**
|
||||
* indicate the number of labels displayed
|
||||
*/
|
||||
var labelCount: Int
|
||||
|
||||
var onValueFormat: (value: T) -> String
|
||||
|
||||
/**
|
||||
* is Horizontal Scrolling enabled
|
||||
*/
|
||||
var scrollEnabled: Boolean
|
||||
|
||||
/**
|
||||
* is horizontal zooming enabled
|
||||
*/
|
||||
var zoomEnabled: Boolean
|
||||
|
||||
/**
|
||||
* get the entry position on the rect
|
||||
*
|
||||
* @param entry the entry to place
|
||||
* @param drawableSpace the space it should go into
|
||||
*
|
||||
* @return the position of the entry on the defined axis
|
||||
*/
|
||||
fun getPositionOnRect(entry: Entry, drawableSpace: RectF): T
|
||||
|
||||
/**
|
||||
* get the entry position on the rect
|
||||
*
|
||||
* @param position the position on your point
|
||||
* @param drawableSpace the space it should go into
|
||||
*
|
||||
* @return the position of the entry on the defined axis
|
||||
*/
|
||||
fun getPositionOnRect(position: T, drawableSpace: RectF): T
|
||||
|
||||
/**
|
||||
* get the current/displayed minimum (inclusive)
|
||||
*/
|
||||
fun getCurrentMin(): T
|
||||
|
||||
/**
|
||||
* set the new min/max of the element
|
||||
*
|
||||
* if one or both can't be set to the value both won't be set
|
||||
*/
|
||||
fun setCurrent(min: T?, max: T?): Boolean
|
||||
|
||||
/**
|
||||
* set the new minimum displayed value of the axis (inclusive)
|
||||
*/
|
||||
fun setCurrentMin(value: T?)
|
||||
|
||||
/**
|
||||
* set the new maximum displayed value of the axis (inclusive)
|
||||
*/
|
||||
fun setCurrentMax(value: T?)
|
||||
|
||||
/**
|
||||
* get the current/displayed maximum (inclusive)
|
||||
*/
|
||||
fun getCurrentMax(): T
|
||||
|
||||
/**
|
||||
* get the maximum the axis can get to
|
||||
*/
|
||||
fun getMax(): T
|
||||
|
||||
/**
|
||||
* get the minimum value the axis can get to
|
||||
*/
|
||||
fun getMin(): T
|
||||
|
||||
/**
|
||||
* run when manually refreshing the system
|
||||
*
|
||||
* this is where the pre-logic is handled to make [onDraw] quicker
|
||||
*/
|
||||
fun refresh()
|
||||
|
||||
/**
|
||||
* 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 the width or height of the axis
|
||||
*/
|
||||
fun onDraw(canvas: Canvas, space: RectF): Float
|
||||
}
|
@ -2,7 +2,7 @@ package com.dzeio.charts.axis
|
||||
|
||||
import android.graphics.Paint
|
||||
|
||||
data class Line (
|
||||
data class Line(
|
||||
/**
|
||||
* is the bar dotted
|
||||
*/
|
||||
|
@ -15,24 +15,24 @@ class XAxis(
|
||||
) : XAxisInterface {
|
||||
|
||||
private companion object {
|
||||
const val TAG = "Charts/XAxis"
|
||||
const val TAG = "XAxis"
|
||||
}
|
||||
|
||||
override var x: Double = 0.0
|
||||
set(value) {
|
||||
val max = getXMax()
|
||||
val min = getXMin()
|
||||
|
||||
field = value.coerceIn(min, max.coerceAtLeast(min))
|
||||
}
|
||||
private var x: Double = 0.0
|
||||
|
||||
override var enabled = true
|
||||
|
||||
override val linePaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = Color.BLACK
|
||||
}
|
||||
|
||||
override var dataWidth: Double? = null
|
||||
|
||||
override var labelCount: Int = 2
|
||||
|
||||
override var scrollEnabled: Boolean = false
|
||||
override var zoomEnabled: Boolean = false
|
||||
|
||||
var spacing = 16.0
|
||||
|
||||
@ -46,6 +46,7 @@ class XAxis(
|
||||
private val rect = Rect()
|
||||
|
||||
private var height: Float? = null
|
||||
override var keepGlobalLimits = false
|
||||
|
||||
override fun getHeight(): Float? {
|
||||
return height
|
||||
@ -65,21 +66,68 @@ class XAxis(
|
||||
return drawableSpace.left + drawableSpace.width() * (position - x) / getDataWidth()
|
||||
}
|
||||
|
||||
override fun getXMax(): Double {
|
||||
return view.series.maxOf { serie ->
|
||||
if (serie.entries.isEmpty()) {
|
||||
return 0.0
|
||||
override fun getCurrentMin(): Double {
|
||||
return this.x
|
||||
}
|
||||
serie.entries.maxOf { entry -> entry.x }
|
||||
|
||||
override fun setCurrent(min: Double?, max: Double?): Boolean {
|
||||
val previousMin = getCurrentMin()
|
||||
val previousMax = getCurrentMax()
|
||||
setCurrentMin(min)
|
||||
if (previousMin != getCurrentMin()) {
|
||||
setCurrentMin(previousMin)
|
||||
return false
|
||||
}
|
||||
setCurrentMax(max)
|
||||
if (previousMax != getCurrentMax()) {
|
||||
setCurrentMax(previousMax)
|
||||
setCurrentMin(previousMin)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setCurrentMin(value: Double?) {
|
||||
if (keepGlobalLimits) {
|
||||
this.x = (value ?: 0.0).coerceIn(getMin(), getMax())
|
||||
return
|
||||
}
|
||||
this.x = value ?: 0.0
|
||||
}
|
||||
|
||||
override fun setCurrentMax(value: Double?) {
|
||||
if (value == null) {
|
||||
dataWidth = null
|
||||
return
|
||||
}
|
||||
var real = value
|
||||
if (keepGlobalLimits) {
|
||||
real = value.coerceIn(getMin(), getMax())
|
||||
}
|
||||
dataWidth = real.coerceAtLeast(this.x + 1) - this.x
|
||||
}
|
||||
|
||||
override fun getCurrentMax(): Double {
|
||||
return this.x + getDataWidth()
|
||||
}
|
||||
|
||||
override fun getMax(): Double {
|
||||
return view.series
|
||||
.maxOf { serie ->
|
||||
if (serie.entries.isEmpty()) {
|
||||
return@maxOf 0.0
|
||||
}
|
||||
return@maxOf serie.entries.maxOf { entry -> entry.x }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getXMin(): Double {
|
||||
return view.series.minOf { serie ->
|
||||
override fun getMin(): Double {
|
||||
return view.series
|
||||
.minOf { serie ->
|
||||
if (serie.entries.isEmpty()) {
|
||||
return 0.0
|
||||
return@minOf 0.0
|
||||
}
|
||||
serie.entries.minOf { entry -> entry.x }
|
||||
return@minOf serie.entries.minOf { entry -> entry.x }
|
||||
}
|
||||
}
|
||||
|
||||
@ -138,7 +186,7 @@ class XAxis(
|
||||
}
|
||||
|
||||
override fun getDataWidth(): Double {
|
||||
// TODO: handle the auto dataWidth better (still not sure it is good enough)
|
||||
return dataWidth ?: (getXMax() - getXMin() + 1)
|
||||
// TODO: handle the auto dataWidth better
|
||||
return dataWidth ?: (getMax() - getMin() + 1)
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,8 @@
|
||||
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
|
||||
sealed interface XAxisInterface : AxisInterface<Double> {
|
||||
|
||||
/**
|
||||
* the "width" of the graph
|
||||
@ -26,60 +13,6 @@ sealed interface XAxisInterface {
|
||||
*/
|
||||
var dataWidth: Double?
|
||||
|
||||
/**
|
||||
* text Paint
|
||||
*/
|
||||
val textPaint: Paint
|
||||
|
||||
/**
|
||||
* indicate the number of labels displayed
|
||||
*/
|
||||
var labelCount: Int
|
||||
|
||||
/**
|
||||
* is Horizontal Scrolling enabled
|
||||
*/
|
||||
var scrollEnabled: Boolean
|
||||
|
||||
var onValueFormat: (value: Double) -> String
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param entry the entry to place
|
||||
* @param drawableSpace the space it should go into
|
||||
*
|
||||
* @return the left side of the position of the entry
|
||||
*/
|
||||
fun getPositionOnRect(entry: Entry, drawableSpace: RectF): Double
|
||||
|
||||
/**
|
||||
* get the entry position on the rect
|
||||
*
|
||||
* @param position the position of your point
|
||||
* @param drawableSpace the space it should go into
|
||||
*
|
||||
* @return the left side of the position of the entry
|
||||
*/
|
||||
fun getPositionOnRect(position: Double, 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
|
||||
*
|
||||
@ -92,16 +25,6 @@ sealed interface XAxisInterface {
|
||||
*/
|
||||
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
|
||||
|
||||
/**
|
||||
* return the height of the XAxis (available after first draw)
|
||||
*/
|
||||
|
@ -18,7 +18,7 @@ class YAxis(
|
||||
|
||||
override var enabled = true
|
||||
|
||||
override val textLabel = Paint().apply {
|
||||
override val textPaint = Paint().apply {
|
||||
isAntiAlias = true
|
||||
color = Color.BLACK
|
||||
textSize = 30f
|
||||
@ -36,6 +36,8 @@ class YAxis(
|
||||
strokeWidth = 4f
|
||||
}
|
||||
|
||||
override var keepGlobalLimits = true
|
||||
|
||||
override var onValueFormat: (value: Float) -> String = { it -> it.roundToInt().toString() }
|
||||
|
||||
override var labelCount = 5
|
||||
@ -51,20 +53,125 @@ class YAxis(
|
||||
}
|
||||
|
||||
override var scrollEnabled: Boolean = false
|
||||
override var zoomEnabled: Boolean = false
|
||||
|
||||
private val rect = Rect()
|
||||
|
||||
override fun setYMin(yMin: Float?): YAxisInterface {
|
||||
min = yMin
|
||||
return this
|
||||
override fun setCurrent(min: Float?, max: Float?): Boolean {
|
||||
val previousMin = getCurrentMin()
|
||||
val previousMax = getCurrentMax()
|
||||
setCurrentMin(min)
|
||||
if (previousMin != getCurrentMin()) {
|
||||
setCurrentMin(previousMin)
|
||||
return false
|
||||
}
|
||||
setCurrentMax(max)
|
||||
if (previousMax != getCurrentMax()) {
|
||||
setCurrentMax(previousMax)
|
||||
setCurrentMin(previousMin)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override fun setYMax(yMax: Float?): YAxisInterface {
|
||||
max = yMax
|
||||
return this
|
||||
override fun setCurrentMin(value: Float?) {
|
||||
if (keepGlobalLimits) {
|
||||
min = (value ?: 0f).coerceIn(getMin(), getMax())
|
||||
return
|
||||
}
|
||||
min = value
|
||||
}
|
||||
|
||||
override fun getYMax(): Float {
|
||||
override fun setCurrentMax(value: Float?) {
|
||||
if (keepGlobalLimits) {
|
||||
max = (value ?: 0f).coerceIn(getMin(), getMax())
|
||||
return
|
||||
}
|
||||
max = value
|
||||
}
|
||||
|
||||
override fun getMax(): Float {
|
||||
val max = this.lines.keys.maxOrNull() ?: 0f
|
||||
|
||||
if (view.series.isEmpty()) {
|
||||
return max
|
||||
}
|
||||
|
||||
if (view.type == ChartType.STACKED) {
|
||||
val nList: ArrayList<Float> = arrayListOf()
|
||||
|
||||
for (serie in view.series) {
|
||||
val size = serie.entries.size
|
||||
while (nList.size < size) {
|
||||
nList.add(0f)
|
||||
}
|
||||
for (index in 0 until serie.entries.size) {
|
||||
val entry = serie.entries[index]
|
||||
if (sign(entry.y) <= 0f && nList[index] > 0f) {
|
||||
continue
|
||||
} else if (nList[index] < 0f && entry.y > 0f) {
|
||||
nList[index] = entry.y
|
||||
continue
|
||||
}
|
||||
nList[index] += entry.y
|
||||
}
|
||||
}
|
||||
|
||||
val localMax = nList.maxOf { it }
|
||||
return if (localMax > max) localMax else max
|
||||
}
|
||||
val seriesMax = view.series
|
||||
.maxOf { serie ->
|
||||
if (serie.entries.isEmpty()) {
|
||||
return@maxOf 0f
|
||||
}
|
||||
return@maxOf serie.entries.maxOf { entry -> entry.y }
|
||||
}
|
||||
return if (seriesMax > max) seriesMax else max
|
||||
}
|
||||
|
||||
override fun getMin(): Float {
|
||||
val min = this.lines.keys.minOrNull() ?: 0f
|
||||
|
||||
if (view.series.isEmpty()) {
|
||||
return min
|
||||
}
|
||||
|
||||
if (view.type == ChartType.STACKED) {
|
||||
val nList: ArrayList<Float> = arrayListOf()
|
||||
|
||||
for (serie in view.series) {
|
||||
val size = serie.entries.size
|
||||
while (nList.size < size) {
|
||||
nList.add(0f)
|
||||
}
|
||||
for (index in 0 until serie.entries.size) {
|
||||
val entry = serie.entries[index]
|
||||
if (sign(entry.y) >= 0f && nList[index] < 0f) {
|
||||
continue
|
||||
} else if (nList[index] > 0f && entry.y < 0f) {
|
||||
nList[index] = entry.y
|
||||
continue
|
||||
}
|
||||
nList[index] += entry.y
|
||||
}
|
||||
}
|
||||
|
||||
val localMin = nList.minOf { it }
|
||||
|
||||
return if (localMin < min) localMin else min
|
||||
}
|
||||
val localMin = view.series
|
||||
.minOf { serie ->
|
||||
if (serie.entries.isEmpty()) {
|
||||
return@minOf 0f
|
||||
}
|
||||
return@minOf serie.entries.minOf { entry -> entry.y }
|
||||
}
|
||||
return if (localMin < min) localMin else min
|
||||
}
|
||||
|
||||
override fun getCurrentMax(): Float {
|
||||
if (max != null) {
|
||||
return max!!
|
||||
}
|
||||
@ -110,7 +217,7 @@ class YAxis(
|
||||
return if (seriesMax > max) seriesMax else max
|
||||
}
|
||||
|
||||
override fun getYMin(): Float {
|
||||
override fun getCurrentMin(): Float {
|
||||
if (min != null) {
|
||||
return min!!
|
||||
}
|
||||
@ -162,8 +269,8 @@ class YAxis(
|
||||
return 0f
|
||||
}
|
||||
|
||||
val min = getYMin()
|
||||
val max = getYMax() - min
|
||||
val min = getCurrentMin()
|
||||
val max = getCurrentMax() - min
|
||||
var maxWidth = 0f
|
||||
|
||||
val valueIncrement = max / (labelCount - 1).coerceAtLeast(1)
|
||||
@ -176,7 +283,7 @@ class YAxis(
|
||||
}
|
||||
|
||||
val text = onValueFormat(min + (valueIncrement * index))
|
||||
textLabel.getTextBounds(text, 0, text.length, rect)
|
||||
textPaint.getTextBounds(text, 0, text.length, rect)
|
||||
maxWidth = maxWidth.coerceAtLeast(rect.width().toFloat())
|
||||
|
||||
val posY = getPositionOnRect(value, space)
|
||||
@ -185,7 +292,7 @@ class YAxis(
|
||||
text,
|
||||
space.width() - rect.width().toFloat(),
|
||||
(posY + rect.height() / 2).coerceAtLeast(rect.height().toFloat()),
|
||||
textLabel
|
||||
textPaint
|
||||
)
|
||||
canvas.drawLine(space.left, posY, space.right - maxWidth - 32f, posY, linePaint)
|
||||
}
|
||||
@ -193,7 +300,7 @@ class YAxis(
|
||||
for ((y, settings) in lines) {
|
||||
val pos = getPositionOnRect(y, space)
|
||||
val text = onValueFormat(y)
|
||||
textLabel.getTextBounds(text, 0, text.length, rect)
|
||||
textPaint.getTextBounds(text, 0, text.length, rect)
|
||||
if (settings.dotted) {
|
||||
canvas.drawDottedLine(
|
||||
0f,
|
||||
@ -216,7 +323,7 @@ class YAxis(
|
||||
text,
|
||||
space.width() - rect.width().toFloat(),
|
||||
(pos + rect.height() / 2).coerceAtLeast(rect.height().toFloat()),
|
||||
textLabel
|
||||
textPaint
|
||||
)
|
||||
}
|
||||
|
||||
@ -274,11 +381,11 @@ class YAxis(
|
||||
return getPositionOnRect(entry.y, drawableSpace)
|
||||
}
|
||||
|
||||
override fun getPositionOnRect(point: Float, drawableSpace: RectF): Float {
|
||||
val min = getYMin()
|
||||
val max = getYMax()
|
||||
override fun getPositionOnRect(position: Float, drawableSpace: RectF): Float {
|
||||
val min = getCurrentMin()
|
||||
val max = getCurrentMax()
|
||||
|
||||
return (1 - (point - min) / (max - min)) *
|
||||
return (1 - (position - min) / (max - min)) *
|
||||
drawableSpace.height() +
|
||||
drawableSpace.top
|
||||
}
|
||||
|
@ -1,97 +1,20 @@
|
||||
package com.dzeio.charts.axis
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.RectF
|
||||
import com.dzeio.charts.Entry
|
||||
|
||||
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
|
||||
sealed interface YAxisInterface : AxisInterface<Float> {
|
||||
|
||||
/**
|
||||
* Goal line paint
|
||||
*/
|
||||
val goalLinePaint: Paint
|
||||
|
||||
/**
|
||||
* is vertical scrolling enabled
|
||||
*/
|
||||
var scrollEnabled: Boolean
|
||||
|
||||
var onValueFormat: (value: Float) -> String
|
||||
|
||||
/**
|
||||
* do the Zero line gets drawn?
|
||||
*/
|
||||
@Deprecated("use the new global function", ReplaceWith("YAxisInterface.addLine"))
|
||||
var drawZeroLine: Boolean
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
@ -125,24 +48,4 @@ sealed interface YAxisInterface {
|
||||
* Remove every lines
|
||||
*/
|
||||
fun clearLines()
|
||||
|
||||
/**
|
||||
* get the position of an [entry] Y position in the [drawableSpace]
|
||||
*
|
||||
* if the chart type is stacked it will automatically calculate the position depending on it
|
||||
*
|
||||
* @param entry the entry to search to position
|
||||
* @param drawableSpace the space in which it should appear
|
||||
* @return the float position (can be out of the [drawableSpace])
|
||||
*/
|
||||
fun getPositionOnRect(entry: Entry, drawableSpace: RectF): Float
|
||||
|
||||
/**
|
||||
* get the position of a [point] in the [drawableSpace]
|
||||
*
|
||||
* @param point the point to search to position
|
||||
* @param drawableSpace the space in which it should appear
|
||||
* @return the float position (can be out of the [drawableSpace])
|
||||
*/
|
||||
fun getPositionOnRect(point: Float, drawableSpace: RectF): Float
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.dzeio.charts.components
|
||||
|
||||
import android.util.Log
|
||||
import android.view.MotionEvent
|
||||
import android.view.MotionEvent.INVALID_POINTER_ID
|
||||
import android.view.ScaleGestureDetector
|
||||
@ -9,7 +10,7 @@ import kotlin.math.abs
|
||||
/**
|
||||
* Class handling the scroll/zoom for the library
|
||||
*/
|
||||
class ChartScroll(view: View) {
|
||||
class ChartScroll(private val view: View) {
|
||||
|
||||
/**
|
||||
* Enabled the zoom/unzoom of datas
|
||||
@ -43,7 +44,7 @@ class ChartScroll(view: View) {
|
||||
onChartMoved = fn
|
||||
}
|
||||
|
||||
private var onZoomChanged: ((scale: Float) -> Unit)? = null
|
||||
private var onZoomChanged: ((scaleX: Float, scaleY: Float) -> Unit)? = null
|
||||
|
||||
/**
|
||||
* @param fn.scale Float starting from 100%
|
||||
@ -51,7 +52,7 @@ class ChartScroll(view: View) {
|
||||
* 99-% zoom out,
|
||||
* 101+% zoom in
|
||||
*/
|
||||
fun setOnZoomChanged(fn: (scale: Float) -> Unit) {
|
||||
fun setOnZoomChanged(fn: (scaleX: Float, scaleY: Float) -> Unit) {
|
||||
onZoomChanged = fn
|
||||
}
|
||||
|
||||
@ -59,29 +60,29 @@ class ChartScroll(view: View) {
|
||||
view.context,
|
||||
object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
||||
if (currentZoom != detector.scaleFactor) {
|
||||
currentZoom = detector.scaleFactor
|
||||
onZoomChanged?.invoke(lastZoom + -currentZoom + 1)
|
||||
}
|
||||
Log.d("ChartScroll", detector.scaleFactor.toString())
|
||||
val scaleX = processScaling(detector.currentSpanX, detector.previousSpanX)
|
||||
val scaleY = processScaling(detector.currentSpanY, detector.previousSpanY)
|
||||
onZoomChanged?.invoke(
|
||||
1 - scaleY,
|
||||
1 - scaleX,
|
||||
)
|
||||
|
||||
return super.onScale(detector)
|
||||
}
|
||||
|
||||
override fun onScaleEnd(detector: ScaleGestureDetector) {
|
||||
super.onScaleEnd(detector)
|
||||
|
||||
lastZoom += -currentZoom + 1
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
private fun processScaling(current: Float, previous: Float): Float {
|
||||
return if (previous > 0f) current / previous else 1f
|
||||
}
|
||||
|
||||
private var hasMoved = false
|
||||
|
||||
/**
|
||||
* Code mostly stolen from https://developer.android.com/training/gestures/scale#drag
|
||||
*/
|
||||
fun onTouchEvent(ev: MotionEvent): Boolean {
|
||||
|
||||
if (zoomEnabled) {
|
||||
scaleGestureDetector.onTouchEvent(ev)
|
||||
}
|
||||
@ -117,7 +118,6 @@ class ChartScroll(view: View) {
|
||||
posX += moveX
|
||||
posY += moveY
|
||||
|
||||
|
||||
if (scrollEnabled) {
|
||||
onChartMoved?.invoke(-posX, posY)
|
||||
}
|
||||
|
@ -33,8 +33,8 @@ sealed class BaseSerie(
|
||||
}
|
||||
|
||||
override fun getDisplayedEntries(): ArrayList<Entry> {
|
||||
val minX = view.xAxis.x
|
||||
val maxX = minX + view.xAxis.getDataWidth()
|
||||
val minX = view.xAxis.getCurrentMin()
|
||||
val maxX = view.xAxis.getCurrentMax()
|
||||
|
||||
val result: ArrayList<Entry> = arrayListOf()
|
||||
|
||||
|
@ -125,7 +125,7 @@ class ChartFragment : Fragment() {
|
||||
binding.sliderXAxisScroll.visibility = View.VISIBLE
|
||||
} else {
|
||||
chart.xAxis.dataWidth = null
|
||||
chart.xAxis.x = 0.0
|
||||
chart.xAxis.setCurrentMin(0.0)
|
||||
binding.sliderXAxisScroll.visibility = View.GONE
|
||||
}
|
||||
chart.refresh()
|
||||
|
@ -72,6 +72,11 @@ class MainFragment : Fragment() {
|
||||
LineSerie(this).apply {
|
||||
entries = Utils.generateRandomDataset(5)
|
||||
}
|
||||
this.xAxis.keepGlobalLimits = true
|
||||
this.xAxis.zoomEnabled = true
|
||||
this.xAxis.scrollEnabled = true
|
||||
this.yAxis.zoomEnabled = true
|
||||
this.yAxis.scrollEnabled = true
|
||||
MaterialUtils.materielTheme(this, requireView())
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ import com.google.android.material.color.MaterialColors
|
||||
object MaterialUtils {
|
||||
fun materielTheme(chart: ChartViewInterface, view: View) {
|
||||
chart.yAxis.apply {
|
||||
textLabel.color =
|
||||
textPaint.color =
|
||||
MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer)
|
||||
linePaint.color =
|
||||
MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer)
|
||||
@ -17,8 +17,13 @@ object MaterialUtils {
|
||||
MaterialColors.getColor(view, com.google.android.material.R.attr.colorError)
|
||||
}
|
||||
|
||||
chart.xAxis.textPaint.color =
|
||||
chart.xAxis.apply {
|
||||
textPaint.color =
|
||||
MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer)
|
||||
linePaint.color =
|
||||
MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer)
|
||||
}
|
||||
|
||||
chart.annotator.apply {
|
||||
backgroundPaint.color = MaterialColors.getColor(
|
||||
view,
|
||||
|
@ -32,7 +32,7 @@
|
||||
<com.dzeio.charts.ChartView
|
||||
android:id="@+id/linechart"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="100dp" />
|
||||
android:layout_height="500dp" />
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
|
Loading…
x
Reference in New Issue
Block a user