Search Results for

    Show / Hide Table of Contents

    Animations API - Animate Updated Point

    SciChart library has several built-in animations which you can use to animate your Renderable Series.

    Note

    Please refer to the Animations API article for more details.

    Also, you can create a custom animation and have complete control over data appearing on the screen. This tutorial shows how to animate Y-Value changes of StackedColumnRenderableSeries in real-time.

    To achieve that we'd need to perform 2 steps:

    • Create a new transformation - a class that implements a IRenderPassDataTransformation protocol.
    • Animate your series using previously created transformation

    Note

    A complete project of the Animated Stacked Column Series example you can find in the SciChart Android Examples Suite as well as on GitHub:

    • Native Example

    Create transformation

    Creating transformation is fairly simple. We have to create a class that implements an IRenderPassDataTransformation protocol and pass an ISeriesRenderPassData type suitable for your Renderable Series. In our case we will subclass an abstract class BaseRenderPassDataTransformation<TRenderPassData>, pass StackedColumnRenderPassData type and implement few required methods. The code would look like follows:

    • Java
    • Java with Builders API
    • Kotlin
    class UpdatedPointTransformation extends BaseRenderPassDataTransformation<StackedColumnRenderPassData> {
        private final FloatValues previousYCoordinates = new FloatValues();
        private final FloatValues previousPrevSeriesYCoordinates = new FloatValues();
    
        private final FloatValues originalYCoordinates = new FloatValues();
        private final FloatValues originalPrevSeriesYCoordinates = new FloatValues();
    
        protected UpdatedPointTransformation() {
            super(StackedColumnRenderPassData.class);
        }
    
        @Override
        protected void saveOriginalData() {
            if (!renderPassData.isValid()) return;
    
            TransformationHelpers.copyData(renderPassData.yCoords, originalYCoordinates);
            TransformationHelpers.copyData(renderPassData.prevSeriesYCoords, originalPrevSeriesYCoordinates);
        }
    
        @Override
        protected void applyTransformation() {
            if (!renderPassData.isValid()) return;
    
            int count = renderPassData.pointsCount();
            float currentTransformationValue = getCurrentTransformationValue();
    
            if (previousPrevSeriesYCoordinates.size() != count ||
                    previousYCoordinates.size() != count ||
                    originalYCoordinates.size() != count ||
                    originalPrevSeriesYCoordinates.size() != count) return;
    
            for (int i = 0; i < count; i++) {
                float startYCoord = previousYCoordinates.get(i);
                float originalYCoordinate = originalYCoordinates.get(i);
                float additionalY = startYCoord + (originalYCoordinate - startYCoord) * currentTransformationValue;
    
                float startPrevSeriesYCoords = previousPrevSeriesYCoordinates.get(i);
                float originalPrevSeriesYCoordinate = originalPrevSeriesYCoordinates.get(i);
                float additionalPrevSeriesY = startPrevSeriesYCoords + (originalPrevSeriesYCoordinate - startPrevSeriesYCoords) * currentTransformationValue;
    
                renderPassData.yCoords.set(i, additionalY);
                renderPassData.prevSeriesYCoords.set(i, additionalPrevSeriesY);
            }
        }
    
        @Override
        protected void discardTransformation() {
            TransformationHelpers.copyData(originalYCoordinates, renderPassData.yCoords);
            TransformationHelpers.copyData(originalPrevSeriesYCoordinates, renderPassData.prevSeriesYCoords);
        }
    
        @Override
        protected void onInternalRenderPassDataChanged() {
            applyTransformation();
        }
    
        @Override
        public void onAnimationEnd() {
            super.onAnimationEnd();
    
            TransformationHelpers.copyData(originalYCoordinates, previousYCoordinates);
            TransformationHelpers.copyData(originalPrevSeriesYCoordinates, previousPrevSeriesYCoordinates);
        }
    }
    
    class UpdatedPointTransformation extends BaseRenderPassDataTransformation<StackedColumnRenderPassData> {
        private final FloatValues previousYCoordinates = new FloatValues();
        private final FloatValues previousPrevSeriesYCoordinates = new FloatValues();
    
        private final FloatValues originalYCoordinates = new FloatValues();
        private final FloatValues originalPrevSeriesYCoordinates = new FloatValues();
    
        protected UpdatedPointTransformation() {
            super(StackedColumnRenderPassData.class);
        }
    
        @Override
        protected void saveOriginalData() {
            if (!renderPassData.isValid()) return;
    
            TransformationHelpers.copyData(renderPassData.yCoords, originalYCoordinates);
            TransformationHelpers.copyData(renderPassData.prevSeriesYCoords, originalPrevSeriesYCoordinates);
        }
    
        @Override
        protected void applyTransformation() {
            if (!renderPassData.isValid()) return;
    
            int count = renderPassData.pointsCount();
            float currentTransformationValue = getCurrentTransformationValue();
    
            if (previousPrevSeriesYCoordinates.size() != count ||
                    previousYCoordinates.size() != count ||
                    originalYCoordinates.size() != count ||
                    originalPrevSeriesYCoordinates.size() != count) return;
    
            for (int i = 0; i < count; i++) {
                float startYCoord = previousYCoordinates.get(i);
                float originalYCoordinate = originalYCoordinates.get(i);
                float additionalY = startYCoord + (originalYCoordinate - startYCoord) * currentTransformationValue;
    
                float startPrevSeriesYCoords = previousPrevSeriesYCoordinates.get(i);
                float originalPrevSeriesYCoordinate = originalPrevSeriesYCoordinates.get(i);
                float additionalPrevSeriesY = startPrevSeriesYCoords + (originalPrevSeriesYCoordinate - startPrevSeriesYCoords) * currentTransformationValue;
    
                renderPassData.yCoords.set(i, additionalY);
                renderPassData.prevSeriesYCoords.set(i, additionalPrevSeriesY);
            }
        }
    
        @Override
        protected void discardTransformation() {
            TransformationHelpers.copyData(originalYCoordinates, renderPassData.yCoords);
            TransformationHelpers.copyData(originalPrevSeriesYCoordinates, renderPassData.prevSeriesYCoords);
        }
    
        @Override
        protected void onInternalRenderPassDataChanged() {
            applyTransformation();
        }
    
        @Override
        public void onAnimationEnd() {
            super.onAnimationEnd();
    
            TransformationHelpers.copyData(originalYCoordinates, previousYCoordinates);
            TransformationHelpers.copyData(originalPrevSeriesYCoordinates, previousPrevSeriesYCoordinates);
        }
    }
    
    internal class UpdatedPointTransformation : BaseRenderPassDataTransformation<StackedColumnRenderPassData>(StackedColumnRenderPassData::class.java) {
        private val previousYCoordinates = FloatValues()
        private val previousPrevSeriesYCoordinates = FloatValues()
    
        private val originalYCoordinates = FloatValues()
        private val originalPrevSeriesYCoordinates = FloatValues()
    
        override fun saveOriginalData() {
            if (!renderPassData.isValid) return
    
            TransformationHelpers.copyData(renderPassData.yCoords, originalYCoordinates)
            TransformationHelpers.copyData(renderPassData.prevSeriesYCoords, originalPrevSeriesYCoordinates)
        }
    
        override fun applyTransformation() {
            if (!renderPassData.isValid) return
    
            val count = renderPassData.pointsCount()
            val currentTransformationValue = currentTransformationValue
            if (previousPrevSeriesYCoordinates.size() != count ||
                previousYCoordinates.size() != count ||
                originalYCoordinates.size() != count ||
                originalPrevSeriesYCoordinates.size() != count) return
    
            for (i in 0 until count) {
                val startYCoord = previousYCoordinates[i]
                val originalYCoordinate = originalYCoordinates[i]
                val additionalY = startYCoord + (originalYCoordinate - startYCoord) * currentTransformationValue
    
                val startPrevSeriesYCoords = previousPrevSeriesYCoordinates[i]
                val originalPrevSeriesYCoordinate = originalPrevSeriesYCoordinates[i]
                val additionalPrevSeriesY = startPrevSeriesYCoords + (originalPrevSeriesYCoordinate - startPrevSeriesYCoords) * currentTransformationValue
    
                renderPassData.yCoords[i] = additionalY
                renderPassData.prevSeriesYCoords[i] = additionalPrevSeriesY
            }
        }
    
        override fun discardTransformation() {
            TransformationHelpers.copyData(originalYCoordinates, renderPassData.yCoords)
            TransformationHelpers.copyData(originalPrevSeriesYCoordinates, renderPassData.prevSeriesYCoords)
        }
    
        override fun onInternalRenderPassDataChanged() {
            applyTransformation()
        }
    
        override fun onAnimationEnd() {
            super.onAnimationEnd()
    
            TransformationHelpers.copyData(originalYCoordinates, previousYCoordinates)
            TransformationHelpers.copyData(originalPrevSeriesYCoordinates, previousPrevSeriesYCoordinates)
        }
    }
    

    Animate series

    With the transformation created above, all we need to do is just animate our series. It's easily achievable with AnimationsHelper APIs like below:

    • Java
    • Java with Builders API
    • Kotlin
    // Since we have two Renderable Series in our `VerticallyStackedColumnsCollection`, we need to create separate animators for each series. Please refer to a complete example for more details.
    Animator animator1 = createAnimator(rSeries1);
    Animator animator2 = createAnimator(rSeries2);
    
    private Animator createAnimator(StackedColumnRenderableSeries rSeries) {
        return AnimationsHelper.createAnimator(
                rSeries,
                new UpdatedPointTransformation(),
                ANIMATION_DURATION,
                0,
                new DecelerateInterpolator(),
                new FloatEvaluator(),
                0f, 1f
        );
    }
    
    // this method is called in real time based on timer.
    private void refreshData() {
        requireActivity().runOnUiThread(() -> {
            //cancel animators in case they are in progress
            animator1.cancel();
            animator2.cancel();
    
            UpdateSuspender.using(binding.surface, () -> {
                for (int i = 0; i < X_VALUES_COUNT; i++) {
                    dataSeries1.updateYAt(i, getRandomYValue());
                    dataSeries2.updateYAt(i, getRandomYValue());
                }
            });
    
            // start animation
            animator1.start();
            animator2.start();
        });
    }
    
    
    // Since we have two Renderable Series in our `VerticallyStackedColumnsCollection`, we need to create separate animators for each series. Please refer to a complete example for more details.
    Animator animator1 = createAnimator(rSeries1);
    Animator animator2 = createAnimator(rSeries2);
    
    private Animator createAnimator(StackedColumnRenderableSeries rSeries) {
        return AnimationsHelper.createAnimator(
                rSeries,
                new UpdatedPointTransformation(),
                ANIMATION_DURATION,
                0,
                new DecelerateInterpolator(),
                new FloatEvaluator(),
                0f, 1f
        );
    }
    
    // this method is called in real time based on timer.
    private void refreshData() {
        requireActivity().runOnUiThread(() -> {
            //cancel animators in case they are in progress
            animator1.cancel();
            animator2.cancel();
    
            UpdateSuspender.using(binding.surface, () -> {
                for (int i = 0; i < X_VALUES_COUNT; i++) {
                    dataSeries1.updateYAt(i, getRandomYValue());
                    dataSeries2.updateYAt(i, getRandomYValue());
                }
            });
    
            // start animation
            animator1.start();
            animator2.start();
        });
    }
    
    
    // Since we have two Renderable Series in our `VerticallyStackedColumnsCollection`, we need to create separate animators for each series. Please refer to a complete example for more details.
    var animator1 = createAnimator(rSeries1)
    var animator2 = createAnimator(rSeries2)
    
    private fun createAnimator(rSeries: StackedColumnRenderableSeries?): Animator {
        return AnimationsHelper.createAnimator(
            rSeries,
            UpdatedPointTransformation(),
            ANIMATION_DURATION,
            0,
            DecelerateInterpolator(),
            FloatEvaluator(),
            0f, 1f
        )
    }
    
    // this method is called in real time based on timer.
    private fun refreshData() {
        requireActivity().runOnUiThread {
            //cancel animators in case they are in progress
            animator1.cancel()
            animator2.cancel()
    
            UpdateSuspender.using(binding.surface) {
                for (i in 0 until X_VALUES_COUNT) {
                    dataSeries1.updateYAt(i, getRandomYValue())
                    dataSeries2.updateYAt(i, getRandomYValue())
                }
            }
    
            // start animation
            animator1.start()
            animator2.start()
        }
    }
    
    Note

    You may also take a look at the Animations API - Animate Appended Point article to find out how to animate an appended point.

    Back to top © 2011-2025 SciChart. All rights reserved. | sitemap.xml