How to: Use Series Hiding and Animation

This how-to guide shows you how to make use of the built-in animations and series hiding, to achieve attention-grabbing effects in your charts.

Series Hiding

Where multiple series are displayed on a chart, there are often cases where specific information can best be conveyed by temporarily hiding one or more series, making the remaining series stand out more clearly. While it’s possible to do this by removing and re-adding the series, this is not ideal, as the chart loses all knowledge of the series and incurs the expense of reloading the series data when it is re-added.

Similar to an Android View, a Series can be in one of three visibility states:

  • VISIBLE - the series is displayed in the plot area
  • INVISIBLE - the series is not displayed but space is left for it in the plot area
  • GONE - the series is not displayed and space is not left for it in the plot area

A hidden series, that is one that is INVISIBLE or GONE, has the following attributes:

  • Any associated labels are not visible.
  • It does not respond to selection gestures.
  • It does appear in a chart legend.

Series visibility is controlled by methods (a property in C#) on the Series class.

Java

public int getVisibility();
public void setVisibility(int visibility);

C#

public int Visibility { get; set; }

Setting the visibility of an existing series has an immediate effect, the is no need to redraw the chart explicitly.

Axis Ranges with Invisible and Gone Series

The concept of INVISIBLE and GONE series introduces some complexity into the notion of the range for an axis. To help with this we’ve added a visible range property to the axis, making the following list:

Current Displayed Range
The actual range currently displayed on the visible area of the chart.
Default Range
A range you can set that will be displayed when data is loaded into the chart.
Data Range
The total data range across all series (including INVISIBLE and GONE ones) on the axis, taking into account only the actual data values.
Visible Range
The range within which all the VISIBLE and INVISIBLE series on the axis will be displayed, taking into account baselines, bar/column widths etc.

Changing the visibility of a series to and from GONE will have an immediate effect on the visible range of an axis. This is because space for the series will not longer be left in the plot area. However, this is not always desirable and you can prevent it by using the Axis.setCurrentDisplayedRangePreservedOnUpdate method.

On the other hand, switching between VISIBLE and INVISIBLE will not affect the visible range of an axis because space will be left for the series in the plot area’s layout.

All of the above is controlled by methods on the Axis class. For the Xamarin.Android library, these are C# properties on the concrete NumberAxis and DateTimeAxis classes.

Java

public final Range<T> getCurrentDisplayedRange();
public final Range<T> getDefaultRange();
public final void setDefaultRange(Range<T> defaultRange);
public final Range<T> getDataRange();
public final Range<T> getVisibleRange();
public final boolean isCurrentDisplayedRangePreservedOnUpdate();
public final void setCurrentDisplayedRangePreservedOnUpdate(boolean preserved);

C#

public NumberRange CurrentDisplayedRange { get; }
public NumberRange DefaultRange { get; set; }
public NumberRange DataRange { get; }
public NumberRange VisibleRange { get; }
public bool CurrentDisplayedRangePreservedOnUpdate { get; set; }

Note: The above is for NumberAxis. DateTimeAxis is similar.

Entry and Exit Animations

Changing the visibility of a series is all very nice, but the visual effect can appear rather abrupt. You can ease this, and produce some eye-catching effects, by using animations.

While it is possible to create Animations manually and run them with an AnimationRunner, the SeriesAnimator can do a lot of the work for you. In fact, if you just want to add, remove or set the visibility of a Series using its default animations then it is as simple as the following:

Java

SeriesAnimator seriesAnimator = shinobiChart.getSeriesAnimator();
seriesAnimator.addSeries(series);
...
seriesAnimator.setVisibility(Series.INVISIBLE, series);
...
seriesAnimator.removeSeries(series);

C#

SeriesAnimator seriesAnimator = shinobiChart.SeriesAnimator;
seriesAnimator.AddSeries(series);
...
seriesAnimator.SetVisibility(Series.Invisible, series);
...
seriesAnimator.RemoveSeries(series);

If you want more control over how the Series are animated, you can also supply a SeriesAnimationCreator and a duration. The SeriesAnimationCreator defines an entry animation and an exit animation:

  • Entry animations occur when the chart is first shown, a new series is added, or an existing series is made VISIBLE.
  • Exit animations occur when a series is made INVISIBLE or GONE, or removed from the chart.

The shinobicharts library includes a number of SeriesAnimationCreator implementations that create a set of standard animations though, of course, you can create your own custom implementation to meet your specific needs. You can also obtain the default SeriesAnimationCreator for a particular Series type by using the DefaultSeriesAnimationCreatorFactory, or you can directly obtain the default entry and exit Animations for a Series from its createDefaultEntryAnimation and createDefaultExitAnimation methods.

All of these animations created by the included SeriesAnimationCreator implementations are purely visual effects (they do not alter data) and control scaling in the X and Y directions and the alpha color component.

Animation Notifications

The exit animations introduce a tricky little point, which is that if you, for instance, remove a series with an animation the series is still present and visible for the duration of the animation. The series is not actually removed until the animation completes.

To make this situation easier to handle in an application, you can query the SeriesAnimator as to whether a Series is currently animating, or you can add a listener to the SeriesAnimator so that you can be notified when an animation completes.

Java

public void addListener(@NonNull SeriesAnimator.Listener listener);

C#

public virtual void AddListener(SeriesAnimator.IListener listener);

If you are running an Animation directly with an AnimationRunner, in a similar way you can be notified of the start and end of the animation by adding an AnimationRunner.Listener to the AnimationRunner.

Java

public void addListener(@NonNull AnimationRunner.Listener<T> listener);

C#

public virtual void AddListener(AnimationRunner.IListener listener);

Putting it all together

To demonstrate this, were going to modify the Quick Start app. This app shows two sine waves overlaid one on top of the other. We’re going to make it show one of these, initially animated in with a vertical bounce animation, and then make the app perform a cross-fade between the two series in response to a tap gesture.

We want the first series to perform its default animation, growing vertically with a bounce, when the chart loads so we add it using the SeriesAnimator, which we obtain from the ShinobiChart.

Java

seriesAnimator = shinobiChart.getSeriesAnimator();
seriesAnimator.addSeries(series1);

C#

seriesAnimator = shinobiChart.SeriesAnimator;
seriesAnimator.AddSeries(series1);

The second series needs to initially be INVISIBLE, so we set its visibility and add it directly to the ShinobiChart.

Java

series2.setVisibility(Series.INVISIBLE);
shinobiChart.addSeries(series2);

C#

series2.Visibility = Series.Invisible;
shinobiChart.AddSeries(series2);

As we’ll need them later we’ve refactored series1 and series2 to be class fields rather than local variables. Similarly, we made the SeriesAnimator a class field.

Next we’re going to set a listener for user gestures:

Java

public class SeriesHidingAnimationActivity extends Activity implements ShinobiChart.OnGestureListener

C#

public class MainActivity : Activity, IShinobiChartOnGestureListener

Java

shinobiChart.setOnGestureListener(this);

C#

shinobiChart.SetOnGestureListener(this);

and provide an implementation of the onSingleTouchDown method that swaps the visibility of both series with a fade animation. Note, there is no need to check whether either series is currently animating before starting a new animation as the SeriesAnimator will prevent this from happening for you.

Java

@Override
public void onSingleTouchDown(ShinobiChart chart, PointF position) {
  if (series1.getVisibility() == Series.INVISIBLE) {
      seriesAnimator.setVisibility(Series.VISIBLE, new FadeAnimationCreator(), SeriesAnimator.DEFAULT_SERIES_ANIMATION_DURATION, series1);
      seriesAnimator.setVisibility(Series.INVISIBLE, new FadeAnimationCreator(), SeriesAnimator.DEFAULT_SERIES_ANIMATION_DURATION, series2);
  } else {
      seriesAnimator.setVisibility(Series.INVISIBLE, new FadeAnimationCreator(), SeriesAnimator.DEFAULT_SERIES_ANIMATION_DURATION, series1);
      seriesAnimator.setVisibility(Series.VISIBLE, new FadeAnimationCreator(), SeriesAnimator.DEFAULT_SERIES_ANIMATION_DURATION, series2);
  }
}

C#

public void OnSingleTouchDown (IShinobiChart chart, PointF position)
{
    if (series1.Visibility == Series.Invisible) {
        seriesAnimator.SetVisibility(Series.Visible, new FadeAnimationCreator(), SeriesAnimator.DefaultSeriesAnimationDuration, series1);
        seriesAnimator.SetVisibility(Series.Invisible, new FadeAnimationCreator(), SeriesAnimator.DefaultSeriesAnimationDuration, series2);
    } else {
        seriesAnimator.SetVisibility(Series.Invisible, new FadeAnimationCreator(), SeriesAnimator.DefaultSeriesAnimationDuration, series1);
        seriesAnimator.SetVisibility(Series.Visible, new FadeAnimationCreator(), SeriesAnimator.DefaultSeriesAnimationDuration, series2);
    }
}

It would be nice to be notified when the series animations have finished. To achieve this we will implement the onAnimationEnd method of the SeriesAnimator.Listener interface.

First, we make the Activity implement the SeriesAnimator.Listener interface:

Java

public class SeriesHidingAnimationActivity extends Activity implements ShinobiChart.OnGestureListener, SeriesAnimator.Listener

C#

public class MainActivity : Activity, IShinobiChartOnGestureListener, SeriesAnimator.IListener

Then we complete the onAnimationEnd method:

Java

private boolean loaded = false;

@Override
public void onAnimationEnd(SeriesAnimator seriesAnimator) {
    if (!loaded) {
        Toast.makeText(this, R.string.data_loaded, Toast.LENGTH_SHORT).show();
        loaded = true;
    } else {
        if (series1.getVisibility() == Series.INVISIBLE) {
            Toast.makeText(this, R.string.series1_invisible_series2_visible, Toast.LENGTH_SHORT).show();
        } else if (series2.getVisibility() == Series.INVISIBLE) {
            Toast.makeText(this, R.string.series1_visible_series2_invisible, Toast.LENGTH_SHORT).show();
        }
    }
}

C#

private bool loaded = false;

public void OnAnimationEnd(SeriesAnimator seriesAnimator)
{
    if (!loaded) {
       Toast.MakeText(this, Resource.String.data_loaded, ToastLength.Short).Show();
       loaded = true;
    } else {
       if (series1.Visibility = Series.Invisible) {
         Toast.MakeText(this, Resource.String.series1_invisible_series2_visible, ToastLength.Short).Show();
       } else if (series2.Visibility = Series.Invisible) {
         Toast.MakeText(this, Resource.String.series1_visible_series2_invisible, ToastLength.Short).Show();
       }
    }
}

Finally we must set the Activity as a listener on the SeriesAnimator.

Java

seriesAnimator.addListener(this);

C#

seriesAnimator.AddListener(this);

That’s all you need to know for simple animations. Please refer to the API documentation if you want to do more complex customizations, such as defining your own animation curves.

See related code sample: Series Hiding And Animation Sample, in the samples/series-hiding-animation folder of your product download (Xamarin.Android/samples/SeriesHidingAnimation if you’re using Xamarin.Android).