feat: Basic idea is present

This commit is contained in:
Florian Bouillon 2023-02-16 17:09:29 +01:00 committed by Avior
parent e1946e98d0
commit 3965ac2f81
14 changed files with 413 additions and 253 deletions

View File

@ -1,6 +1,6 @@
#Sun Aug 07 22:38:24 CEST 2022 #Sun Aug 07 22:38:24 CEST 2022
distributionBase=GRADLE_USER_HOME 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 distributionPath=wrapper/dists
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -9,6 +9,7 @@ import android.graphics.Paint
import android.graphics.Rect import android.graphics.Rect
import android.graphics.RectF import android.graphics.RectF
import android.util.AttributeSet import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import com.dzeio.charts.axis.XAxis import com.dzeio.charts.axis.XAxis
@ -82,16 +83,21 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
return@setOnChartMoved return@setOnChartMoved
} }
if (xAxis.scrollEnabled) { 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() lastMovementX = movementX.toDouble()
} }
if (yAxis.scrollEnabled) { if (yAxis.scrollEnabled) {
val currentYMax = yAxis.getYMax() val currentYMax = yAxis.getCurrentMax()
val currentYMin = yAxis.getYMin() val currentYMin = yAxis.getCurrentMin()
val change = (movementY - lastMovementY) * (currentYMax - currentYMin) / height val change = (movementY - lastMovementY) * (currentYMax - currentYMin) / height
yAxis.setYMax(currentYMax + change) yAxis.setCurrent(currentYMin + change, currentYMax + change)
yAxis.setYMin(currentYMin + change)
lastMovementY = movementY lastMovementY = movementY
} }
@ -99,7 +105,9 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
} }
setOnToggleScroll { setOnToggleScroll {
// note: true == no scroll // 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 -> setOnChartClick { x, y ->
if (getDataset().isEmpty()) { if (getDataset().isEmpty()) {
@ -132,11 +140,53 @@ class ChartView @JvmOverloads constructor(context: Context?, attrs: AttributeSet
} }
refresh() refresh()
} }
// setOnZoomChanged { setOnZoomChanged { scaleX, scaleY ->
// Log.d(TAG, "New Zoom: $it") 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() // zoom = (it * 1.2).toFloat()
// refresh() refresh()
// } if (animationEnabled) animator.enabled = true
}
} }
// rect used for calculations // rect used for calculations

View 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
}

View File

@ -15,24 +15,24 @@ class XAxis(
) : XAxisInterface { ) : XAxisInterface {
private companion object { private companion object {
const val TAG = "Charts/XAxis" const val TAG = "XAxis"
} }
override var x: Double = 0.0 private var x: Double = 0.0
set(value) {
val max = getXMax()
val min = getXMin()
field = value.coerceIn(min, max.coerceAtLeast(min))
}
override var enabled = true override var enabled = true
override val linePaint = Paint().apply {
isAntiAlias = true
color = Color.BLACK
}
override var dataWidth: Double? = null override var dataWidth: Double? = null
override var labelCount: Int = 2 override var labelCount: Int = 2
override var scrollEnabled: Boolean = false override var scrollEnabled: Boolean = false
override var zoomEnabled: Boolean = false
var spacing = 16.0 var spacing = 16.0
@ -46,6 +46,7 @@ class XAxis(
private val rect = Rect() private val rect = Rect()
private var height: Float? = null private var height: Float? = null
override var keepGlobalLimits = false
override fun getHeight(): Float? { override fun getHeight(): Float? {
return height return height
@ -65,21 +66,68 @@ class XAxis(
return drawableSpace.left + drawableSpace.width() * (position - x) / getDataWidth() return drawableSpace.left + drawableSpace.width() * (position - x) / getDataWidth()
} }
override fun getXMax(): Double { override fun getCurrentMin(): Double {
return view.series.maxOf { serie -> return this.x
if (serie.entries.isEmpty()) {
return 0.0
} }
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 { override fun getMin(): Double {
return view.series.minOf { serie -> return view.series
.minOf { serie ->
if (serie.entries.isEmpty()) { 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 { override fun getDataWidth(): Double {
// TODO: handle the auto dataWidth better (still not sure it is good enough) // TODO: handle the auto dataWidth better
return dataWidth ?: (getXMax() - getXMin() + 1) return dataWidth ?: (getMax() - getMin() + 1)
} }
} }

View File

@ -1,21 +1,8 @@
package com.dzeio.charts.axis package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RectF import android.graphics.RectF
import com.dzeio.charts.Entry
sealed interface XAxisInterface { sealed interface XAxisInterface : AxisInterface<Double> {
/**
* enable/disable the display of the xAxis
*/
var enabled: Boolean
/**
* set X position
*/
var x: Double
/** /**
* the "width" of the graph * the "width" of the graph
@ -26,60 +13,6 @@ sealed interface XAxisInterface {
*/ */
var dataWidth: Double? 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 * get the size of an entry in the graph
* *
@ -92,16 +25,6 @@ sealed interface XAxisInterface {
*/ */
fun getDataWidth(): Double 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) * return the height of the XAxis (available after first draw)
*/ */

View File

@ -18,7 +18,7 @@ class YAxis(
override var enabled = true override var enabled = true
override val textLabel = Paint().apply { override val textPaint = Paint().apply {
isAntiAlias = true isAntiAlias = true
color = Color.BLACK color = Color.BLACK
textSize = 30f textSize = 30f
@ -36,6 +36,8 @@ class YAxis(
strokeWidth = 4f strokeWidth = 4f
} }
override var keepGlobalLimits = true
override var onValueFormat: (value: Float) -> String = { it -> it.roundToInt().toString() } override var onValueFormat: (value: Float) -> String = { it -> it.roundToInt().toString() }
override var labelCount = 5 override var labelCount = 5
@ -51,20 +53,125 @@ class YAxis(
} }
override var scrollEnabled: Boolean = false override var scrollEnabled: Boolean = false
override var zoomEnabled: Boolean = false
private val rect = Rect() private val rect = Rect()
override fun setYMin(yMin: Float?): YAxisInterface { override fun setCurrent(min: Float?, max: Float?): Boolean {
min = yMin val previousMin = getCurrentMin()
return this 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 { override fun setCurrentMin(value: Float?) {
max = yMax if (keepGlobalLimits) {
return this 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) { if (max != null) {
return max!! return max!!
} }
@ -110,7 +217,7 @@ class YAxis(
return if (seriesMax > max) seriesMax else max return if (seriesMax > max) seriesMax else max
} }
override fun getYMin(): Float { override fun getCurrentMin(): Float {
if (min != null) { if (min != null) {
return min!! return min!!
} }
@ -162,8 +269,8 @@ class YAxis(
return 0f return 0f
} }
val min = getYMin() val min = getCurrentMin()
val max = getYMax() - min val max = getCurrentMax() - min
var maxWidth = 0f var maxWidth = 0f
val valueIncrement = max / (labelCount - 1).coerceAtLeast(1) val valueIncrement = max / (labelCount - 1).coerceAtLeast(1)
@ -176,7 +283,7 @@ class YAxis(
} }
val text = onValueFormat(min + (valueIncrement * index)) 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()) maxWidth = maxWidth.coerceAtLeast(rect.width().toFloat())
val posY = getPositionOnRect(value, space) val posY = getPositionOnRect(value, space)
@ -185,7 +292,7 @@ class YAxis(
text, text,
space.width() - rect.width().toFloat(), space.width() - rect.width().toFloat(),
(posY + rect.height() / 2).coerceAtLeast(rect.height().toFloat()), (posY + rect.height() / 2).coerceAtLeast(rect.height().toFloat()),
textLabel textPaint
) )
canvas.drawLine(space.left, posY, space.right - maxWidth - 32f, posY, linePaint) canvas.drawLine(space.left, posY, space.right - maxWidth - 32f, posY, linePaint)
} }
@ -193,7 +300,7 @@ class YAxis(
for ((y, settings) in lines) { for ((y, settings) in lines) {
val pos = getPositionOnRect(y, space) val pos = getPositionOnRect(y, space)
val text = onValueFormat(y) val text = onValueFormat(y)
textLabel.getTextBounds(text, 0, text.length, rect) textPaint.getTextBounds(text, 0, text.length, rect)
if (settings.dotted) { if (settings.dotted) {
canvas.drawDottedLine( canvas.drawDottedLine(
0f, 0f,
@ -216,7 +323,7 @@ class YAxis(
text, text,
space.width() - rect.width().toFloat(), space.width() - rect.width().toFloat(),
(pos + rect.height() / 2).coerceAtLeast(rect.height().toFloat()), (pos + rect.height() / 2).coerceAtLeast(rect.height().toFloat()),
textLabel textPaint
) )
} }
@ -274,11 +381,11 @@ class YAxis(
return getPositionOnRect(entry.y, drawableSpace) return getPositionOnRect(entry.y, drawableSpace)
} }
override fun getPositionOnRect(point: Float, drawableSpace: RectF): Float { override fun getPositionOnRect(position: Float, drawableSpace: RectF): Float {
val min = getYMin() val min = getCurrentMin()
val max = getYMax() val max = getCurrentMax()
return (1 - (point - min) / (max - min)) * return (1 - (position - min) / (max - min)) *
drawableSpace.height() + drawableSpace.height() +
drawableSpace.top drawableSpace.top
} }

View File

@ -1,97 +1,20 @@
package com.dzeio.charts.axis package com.dzeio.charts.axis
import android.graphics.Canvas
import android.graphics.Paint import android.graphics.Paint
import android.graphics.RectF
import com.dzeio.charts.Entry
sealed interface YAxisInterface { sealed interface YAxisInterface : AxisInterface<Float> {
/**
* whether or not this axis is displayed
*/
var enabled: Boolean
/**
* get/set the number of label of this Y axis
*
* the first/last labels are at the bottom/top of the chart
*/
var labelCount: Int
/**
* text label paint
*/
val textLabel: Paint
/**
* paint for the lines
*/
val linePaint: Paint
/** /**
* Goal line paint * Goal line paint
*/ */
val goalLinePaint: Paint val goalLinePaint: Paint
/**
* is vertical scrolling enabled
*/
var scrollEnabled: Boolean
var onValueFormat: (value: Float) -> String
/** /**
* do the Zero line gets drawn? * do the Zero line gets drawn?
*/ */
@Deprecated("use the new global function", ReplaceWith("YAxisInterface.addLine")) @Deprecated("use the new global function", ReplaceWith("YAxisInterface.addLine"))
var drawZeroLine: Boolean 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 * Add a Goal line
* *
@ -125,24 +48,4 @@ sealed interface YAxisInterface {
* Remove every lines * Remove every lines
*/ */
fun clearLines() 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
} }

View File

@ -1,5 +1,6 @@
package com.dzeio.charts.components package com.dzeio.charts.components
import android.util.Log
import android.view.MotionEvent import android.view.MotionEvent
import android.view.MotionEvent.INVALID_POINTER_ID import android.view.MotionEvent.INVALID_POINTER_ID
import android.view.ScaleGestureDetector import android.view.ScaleGestureDetector
@ -9,7 +10,7 @@ import kotlin.math.abs
/** /**
* Class handling the scroll/zoom for the library * Class handling the scroll/zoom for the library
*/ */
class ChartScroll(view: View) { class ChartScroll(private val view: View) {
/** /**
* Enabled the zoom/unzoom of datas * Enabled the zoom/unzoom of datas
@ -43,7 +44,7 @@ class ChartScroll(view: View) {
onChartMoved = fn 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% * @param fn.scale Float starting from 100%
@ -51,7 +52,7 @@ class ChartScroll(view: View) {
* 99-% zoom out, * 99-% zoom out,
* 101+% zoom in * 101+% zoom in
*/ */
fun setOnZoomChanged(fn: (scale: Float) -> Unit) { fun setOnZoomChanged(fn: (scaleX: Float, scaleY: Float) -> Unit) {
onZoomChanged = fn onZoomChanged = fn
} }
@ -59,29 +60,29 @@ class ChartScroll(view: View) {
view.context, view.context,
object : ScaleGestureDetector.SimpleOnScaleGestureListener() { object : ScaleGestureDetector.SimpleOnScaleGestureListener() {
override fun onScale(detector: ScaleGestureDetector): Boolean { override fun onScale(detector: ScaleGestureDetector): Boolean {
if (currentZoom != detector.scaleFactor) { Log.d("ChartScroll", detector.scaleFactor.toString())
currentZoom = detector.scaleFactor val scaleX = processScaling(detector.currentSpanX, detector.previousSpanX)
onZoomChanged?.invoke(lastZoom + -currentZoom + 1) val scaleY = processScaling(detector.currentSpanY, detector.previousSpanY)
} onZoomChanged?.invoke(
1 - scaleY,
1 - scaleX,
)
return super.onScale(detector) 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 private var hasMoved = false
/** /**
* Code mostly stolen from https://developer.android.com/training/gestures/scale#drag * Code mostly stolen from https://developer.android.com/training/gestures/scale#drag
*/ */
fun onTouchEvent(ev: MotionEvent): Boolean { fun onTouchEvent(ev: MotionEvent): Boolean {
if (zoomEnabled) { if (zoomEnabled) {
scaleGestureDetector.onTouchEvent(ev) scaleGestureDetector.onTouchEvent(ev)
} }
@ -117,7 +118,6 @@ class ChartScroll(view: View) {
posX += moveX posX += moveX
posY += moveY posY += moveY
if (scrollEnabled) { if (scrollEnabled) {
onChartMoved?.invoke(-posX, posY) onChartMoved?.invoke(-posX, posY)
} }

View File

@ -33,8 +33,8 @@ sealed class BaseSerie(
} }
override fun getDisplayedEntries(): ArrayList<Entry> { override fun getDisplayedEntries(): ArrayList<Entry> {
val minX = view.xAxis.x val minX = view.xAxis.getCurrentMin()
val maxX = minX + view.xAxis.getDataWidth() val maxX = view.xAxis.getCurrentMax()
val result: ArrayList<Entry> = arrayListOf() val result: ArrayList<Entry> = arrayListOf()

View File

@ -125,7 +125,7 @@ class ChartFragment : Fragment() {
binding.sliderXAxisScroll.visibility = View.VISIBLE binding.sliderXAxisScroll.visibility = View.VISIBLE
} else { } else {
chart.xAxis.dataWidth = null chart.xAxis.dataWidth = null
chart.xAxis.x = 0.0 chart.xAxis.setCurrentMin(0.0)
binding.sliderXAxisScroll.visibility = View.GONE binding.sliderXAxisScroll.visibility = View.GONE
} }
chart.refresh() chart.refresh()

View File

@ -72,6 +72,11 @@ class MainFragment : Fragment() {
LineSerie(this).apply { LineSerie(this).apply {
entries = Utils.generateRandomDataset(5) 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()) MaterialUtils.materielTheme(this, requireView())
} }

View File

@ -9,7 +9,7 @@ import com.google.android.material.color.MaterialColors
object MaterialUtils { object MaterialUtils {
fun materielTheme(chart: ChartViewInterface, view: View) { fun materielTheme(chart: ChartViewInterface, view: View) {
chart.yAxis.apply { chart.yAxis.apply {
textLabel.color = textPaint.color =
MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer) MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer)
linePaint.color = linePaint.color =
MaterialColors.getColor(view, com.google.android.material.R.attr.colorOnPrimaryContainer) 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) 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) 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 { chart.annotator.apply {
backgroundPaint.color = MaterialColors.getColor( backgroundPaint.color = MaterialColors.getColor(
view, view,

View File

@ -32,7 +32,7 @@
<com.dzeio.charts.ChartView <com.dzeio.charts.ChartView
android:id="@+id/linechart" android:id="@+id/linechart"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp" /> android:layout_height="500dp" />
<Button <Button
android:layout_width="match_parent" android:layout_width="match_parent"