Skip to content

Commit

Permalink
Update CartesianChartModelProducer to prevent unexpected `Cartesian…
Browse files Browse the repository at this point in the history
…ChartModel` changes

Co-authored-by: Patryk Goworowski <[email protected]>
  • Loading branch information
patrickmichalik and Gowsky committed Dec 21, 2023
1 parent c36a11b commit 9d84cc9
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 70 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public fun CartesianChartModelProducer.collectAsState(
var finalAnimationFrameJob: Job? = null
var isAnimationRunning: Boolean
var isAnimationFrameGenerationRunning = false
var chartValues: ChartValues = ChartValues.Empty
val startAnimation: (transformModel: suspend (key: Any, fraction: Float) -> Unit) -> Unit = { transformModel ->
if (animationSpec != null && !isInPreview &&
(modelWrapperState.value.model != null || runInitialAnimation)
Expand Down Expand Up @@ -136,9 +135,9 @@ public fun CartesianChartModelProducer.collectAsState(
mutableChartValues.toImmutable()
} else {
ChartValues.Empty
}.also { values -> chartValues = values }
}
},
) { model ->
) { model, chartValues ->
modelWrapperState.set(model, chartValues)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,11 @@ public class CartesianChartModel {
}

/**
* Creates a copy of this [CartesianLayerModel] with the given [CartesianLayerModel]s and [ExtraStore].
* Creates a copy of this [CartesianChartModel] with the given [ExtraStore], which is also applied to the
* [CartesianLayerModel]s.
*/
public fun copy(
models: List<CartesianLayerModel>,
extraStore: ExtraStore,
): CartesianChartModel = CartesianChartModel(models, id, width, xDeltaGcd, extraStore)
public fun copy(extraStore: ExtraStore): CartesianChartModel =
CartesianChartModel(models.map { it.copy(extraStore) }, id, width, xDeltaGcd, extraStore)

/**
* Creates an immutable copy of this [CartesianChartModel].
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,36 +69,29 @@ public class CartesianChartModelProducer private constructor(dispatcher: Corouti
return completableDeferred
}

private fun getModel(extraStore: ExtraStore? = null) =
private fun getModel(extraStore: ExtraStore) =
if (partials.isEmpty()) {
null
} else {
val mergedExtraStore = this.extraStore.let { if (extraStore != null) it + extraStore else it }
val mergedExtraStore = this.extraStore + extraStore
cachedModel
?.let { chartModel ->
chartModel.copy(
chartModel.models.map { layerModel -> layerModel.copy(mergedExtraStore) },
mergedExtraStore,
)
?.copy(mergedExtraStore)
?: CartesianChartModel(partials.map { it.complete(mergedExtraStore) }, mergedExtraStore).also {
cachedModel = it
}
?: CartesianChartModel(partials.map { it.complete(mergedExtraStore) }, mergedExtraStore)
.also { cachedModel = it }
}

/**
* Creates an intermediate [CartesianChartModel] for difference animations. [fraction] is the balance between the
* initial and target [CartesianChartModel]s.
*/
public suspend fun transformModel(
private suspend fun transformModel(
key: Any,
fraction: Float,
model: CartesianChartModel?,
chartValues: ChartValues,
) {
with(updateReceivers[key] ?: return) {
transform(extraStore, fraction)
val internalModel = getModel(extraStore.copy())
currentCoroutineContext().ensureActive()
onModelCreated(internalModel)
}
val updateReceiver = updateReceivers[key] ?: return
updateReceiver.transform(updateReceiver.extraStore, fraction)
val transformedModel = model?.copy(extraStore + updateReceiver.extraStore.copy())
currentCoroutineContext().ensureActive()
updateReceiver.onModelCreated(transformedModel, chartValues)
}

/**
Expand All @@ -113,12 +106,12 @@ public class CartesianChartModelProducer private constructor(dispatcher: Corouti
public fun registerForUpdates(
key: Any,
cancelAnimation: () -> Unit,
startAnimation: (transformModel: suspend (chartKey: Any, fraction: Float) -> Unit) -> Unit,
startAnimation: (transformModel: suspend (key: Any, fraction: Float) -> Unit) -> Unit,
prepareForTransformation: (CartesianChartModel?, MutableExtraStore, ChartValues) -> Unit,
transform: suspend (MutableExtraStore, Float) -> Unit,
extraStore: MutableExtraStore,
updateChartValues: (CartesianChartModel?) -> ChartValues,
onModelCreated: (CartesianChartModel?) -> Unit,
onModelCreated: (CartesianChartModel?, ChartValues) -> Unit,
) {
UpdateReceiver(
cancelAnimation,
Expand Down Expand Up @@ -201,17 +194,19 @@ public class CartesianChartModelProducer private constructor(dispatcher: Corouti

private inner class UpdateReceiver(
val cancelAnimation: () -> Unit,
val startAnimation: (transformModel: suspend (chartKey: Any, fraction: Float) -> Unit) -> Unit,
val onModelCreated: (CartesianChartModel?) -> Unit,
val startAnimation: (transformModel: suspend (key: Any, fraction: Float) -> Unit) -> Unit,
val onModelCreated: (CartesianChartModel?, ChartValues) -> Unit,
val extraStore: MutableExtraStore,
val prepareForTransformation: (CartesianChartModel?, MutableExtraStore, ChartValues) -> Unit,
val transform: suspend (MutableExtraStore, Float) -> Unit,
val updateChartValues: (CartesianChartModel?) -> ChartValues,
) {
fun handleUpdate() {
cancelAnimation()
prepareForTransformation(getModel(), extraStore, updateChartValues(getModel()))
startAnimation(::transformModel)
val model = getModel(extraStore)
val chartValues = updateChartValues(model)
prepareForTransformation(model, extraStore, chartValues)
startAnimation { key, fraction -> transformModel(key, fraction, model, chartValues) }
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import com.patrykandpatrick.vico.views.extension.dpInt
import com.patrykandpatrick.vico.views.extension.isLtr
import com.patrykandpatrick.vico.views.extension.specMode
import com.patrykandpatrick.vico.views.extension.specSize
import com.patrykandpatrick.vico.views.extension.start
import com.patrykandpatrick.vico.views.extension.verticalPadding
import com.patrykandpatrick.vico.views.gestures.ChartScaleGestureListener
import com.patrykandpatrick.vico.views.gestures.MotionEventHandler
Expand Down Expand Up @@ -136,7 +137,6 @@ public open class CartesianChartView
ValueAnimator.ofFloat(Animation.range.start, Animation.range.endInclusive).apply {
duration = Animation.DIFF_DURATION.toLong()
interpolator = FastOutSlowInInterpolator()
addUpdateListener { transformModelForAnimation(it.animatedFraction) }
}

private val scrollValueAnimator: ValueAnimator =
Expand Down Expand Up @@ -169,8 +169,6 @@ public open class CartesianChartView

private var wasZoomOverridden = false

private var chartValues: ChartValues = ChartValues.Empty

private var horizontalDimensions = MutableHorizontalDimensions()

private val themeHandler: ThemeHandler = ThemeHandler(context, attrs)
Expand Down Expand Up @@ -251,19 +249,7 @@ public open class CartesianChartView
isAnimationRunning = false
isAnimationFrameGenerationRunning = false
},
startAnimation = { transformModel ->
if (model != null || runInitialAnimation) {
handler?.post {
isAnimationRunning = true
animator.start()
}
} else {
finalAnimationFrameJob =
coroutineScope?.launch(dispatcher) {
transformModel(this@CartesianChartView, Animation.range.endInclusive)
}
}
},
startAnimation = ::startAnimation,
prepareForTransformation = { model, extraStore, chartValues ->
chart?.prepareForTransformation(model, extraStore, chartValues)
},
Expand All @@ -276,10 +262,9 @@ public open class CartesianChartView
mutableChartValues.toImmutable()
} else {
ChartValues.Empty
}.also { values -> chartValues = values }
}
},
) { model ->
val chartValues = chartValues
) { model, chartValues ->
post {
setModel(model = model, updateChartValues = false)
measureContext.chartValues = chartValues
Expand Down Expand Up @@ -518,25 +503,37 @@ public open class CartesianChartView
}
}

private fun transformModelForAnimation(fraction: Float) {
when {
!isAnimationRunning -> return
!isAnimationFrameGenerationRunning -> {
isAnimationFrameGenerationRunning = true
animationFrameJob =
coroutineScope?.launch(dispatcher) {
modelProducer?.transformModel(this@CartesianChartView, fraction)
isAnimationFrameGenerationRunning = false
}
}
fraction == 1f -> {
finalAnimationFrameJob =
coroutineScope?.launch(dispatcher) {
animationFrameJob?.cancelAndJoin()
modelProducer?.transformModel(this@CartesianChartView, fraction)
isAnimationFrameGenerationRunning = false
private fun startAnimation(transformModel: suspend (key: Any, fraction: Float) -> Unit) {
if (model != null || runInitialAnimation) {
handler?.post {
isAnimationRunning = true
animator.start { fraction ->
when {
!isAnimationRunning -> return@start
!isAnimationFrameGenerationRunning -> {
isAnimationFrameGenerationRunning = true
animationFrameJob =
coroutineScope?.launch(dispatcher) {
transformModel(this@CartesianChartView, fraction)
isAnimationFrameGenerationRunning = false
}
}
fraction == 1f -> {
finalAnimationFrameJob =
coroutineScope?.launch(dispatcher) {
animationFrameJob?.cancelAndJoin()
transformModel(this@CartesianChartView, fraction)
isAnimationFrameGenerationRunning = false
}
}
}
}
}
} else {
finalAnimationFrameJob =
coroutineScope?.launch(dispatcher) {
transformModel(this@CartesianChartView, Animation.range.endInclusive)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2023 by Patryk Goworowski and Patrick Michalik.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.patrykandpatrick.vico.views.extension

import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator

internal fun ValueAnimator.start(block: (Float) -> Unit) {
val updateListener = ValueAnimator.AnimatorUpdateListener { block(it.animatedFraction) }
addUpdateListener(updateListener)
addListener(
object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
removeUpdateListener(updateListener)
removeListener(this)
}

override fun onAnimationCancel(animation: Animator) {
onAnimationEnd(animation)
}
},
)
start()
}

0 comments on commit 9d84cc9

Please sign in to comment.