Control Overview

The ShinobiChart provides a powerful and flexible way to represent data visually. The chart comes with a number of interactions such as pan and zoom that are optimized for touch interfaces. The highly performant nature of the chart lends itself to the mobile environment and provides a super smooth user experience.

This section describes the features and concepts which constitute a chart.

The Anatomy of the Chart

There are a number of core components which make up a chart. Several of these are highlighted below:


Anatomy of a chart


  • Title: Each chart can have a single title which may be displayed at the top of the chart.
  • Plot Area: The main area of the chart in which the data is rendered is referred to as the plot area.
  • Chart Series: The chart renders the data provided to it via one or more chart series. These are components which determine the visual representation of the data. For example, a line series will render the data as a line chart, a column series will render it as vertical bars, and so on. Multiple different chart series may be added to the same chart simultaneously. These are rendered in the plot area - the above image shows several bar series.
  • Axes: A cartesian chart must have at least one X and one Y axis. In the premium version, a chart may have an arbitrary number of either, on either side of the chart. In the standard version only one X and one Y axis is permitted, though these can be placed either side of the chart. Axes border the plot area and an x-axis is highlighted in the image above.
  • Legend: A chart may optionally display a legend associating a series with a title for additional information. There are several options for position.
  • Annotations: shinobicharts supports additional visual cues being overlaid on the chart through the use of annotations. These may take the form of shapes, markers, text or other visual elements. These are only supported in the premium version of shinobicharts and display in the plot area - either above or below the data.
  • DataAdapter: Each series has a data adapter. This is responsible for storing the data to be shown by the series, and notifying the chart when a series has changed. DataAdapter can be overridden to provide custom functionality for things like streaming chart data, or batching updates together.

In order to use the chart you will typically perform the following tasks:

  • Add a ChartFragment or ChartView to a layout.
  • Retrieve the ShinobiChart from the fragment/view.
  • Create some axes and add them to the chart.
  • Create some series, add data to them via a DataAdapter and add the series to the chart.

Data Adapters

In order to render the data within a series you must provide it with a DataAdapter by calling series.setDataAdapter(). The data adapter maintains a collection of all the data points for the series. It is also responsible for telling the chart when the series has been changed and must be redrawn.

The DataAdapter class is generic and is parametrized by the types of the X-axis values and the Y-axis values. These types should match the types that parametrize the X and Y values in your data points.

DataAdapter is also abstract and so cannot be directly instantiated. In itself, it only handles the storage of data points. It is the responsibility of the concrete data adapter implementation that you use to decide when to inform the chart of changes to its data. shinobicharts provides a default implementation of a DataAdapter called SimpleDataAdapter. This adapter will tell the chart to update every time a point, or set of points, is added or removed.

This may not always be appropriate, for example, if you are adding small amounts of data very frequently. Instead, you may subclass DataAdapter, providing your own implementation.

If you want the parent class to handle the storage of data points for you, make sure you call the super implementation of any methods you override. Then it is just a case of deciding when to tell the chart to update. This is done by calling fireUpdateHandler().

DataAdapter exposes a method called getDataPointsForDisplay(). This returns a list of all the data points which are to be displayed on the chart. This method is called by the chart framework in response to fireUpdateHandler(). It is not normally called by client code, though there is no harm in doing so. In the base DataAdapter implementation it simply provides access to the ArrayList that backs the class. However, in some subclassing scenarios, such as sampling, windowing or smoothing, you may wish to override this method and optionally provide your own backing data store.

See How to: Create a custom DataAdapter for an example of a custom DataAdapter that only updates the chart every 10 data points that are added.

In some cases it may be desirable to display a modified set of data points, for example in sampling scenarios. You could of course modify the data itself but your use case may dictate that the data remain unmodified. A simple way to achieve this is to create your own DataAdapter class which returns the modified set of data points for display. We do of course offer a concrete SimpleDataAdapter class which meets the requirements of most users. If you wish to utilize the convenience of this class but wish to display a modified set of data points, you can extend its functionality either by sub-classing it, or by ‘wrapping’ it using a ‘decorator pattern’ approach.

Data

A data adapter requires you to pass in data points that contain appropriate data for a series. shinobicharts comes with two types of data point:

  • DataPoint for simple, single value x and y values (e.g. Line, Bar)
  • MultiValueDataPoint for series where each point represents a set of related values (e.g. OHLC, Candlestick and Band)

Using DataPoint is as simple as creating an instance, supplying the x and y values in the constructor. For series that are expecting multiple values at a point, you should use MultiValueDataPoint and add data via the appropriate constructor for the series type.

It is also possible to use your own objects as data points by implementing the Data interface. This is useful as existing model objects can be made to implement the interface and then given directly to the chart to avoid duplication of memory. The interface ensures that you return an x and y value. There is also the MultiValueData interface which is used for defining your own multi value data points.

The Axes

Axes are used to control the view of the data and a chart must have at least one x-axis (horizontal) and one y-axis (vertical). The exception to this is a chart containing PieSeries or DonutSeries; in this case axes are not required.

Axes on a ShinobiChart are instances of the Axis base class. There are multiple types of axis that can be used on a chart. These are:

  • NumberAxis - A standard axis for displaying a linear range of numbers. NumberAxis is appropriate for any numeric data which is representable using java.lang.Number or its subclasses, and will accept Data objects parametrized to those types.
  • CategoryAxis - An axis that works with discrete data points that have no linear correlation between their values (often strings).
  • DateTimeAxis - An axis that works with time-based data points. DateTimeAxis is appropriate for any temporal data which is representable using java.util.Date or its subclasses.

When you create a chart, you should specify the primary x-axis and primary y-axis. To create a pair of x/y axes and attach them to a chart you could use the following code:

Java

chart.setXAxis(new NumberAxis());
chart.setYAxis(new NumberAxis());

C#

chart.XAxis = new NumberAxis();
chart.YAxis = new NumberAxis();

You can also use the chart.addXAxis and chart.addYAxis methods to add axes to a chart. If the chart does not already have an x or y-axis then the axis added will automatically be set as the primary x or y-axis accordingly.

As the axes are rendered, the chart will assign enough room to visually accommodate them at their current range. This will vary depending upon the length of the tick lines and the labels, etc. As the axis range changes, the width of the axis may also vary as the tickmarks labels change length to accommodate different values.

The axes can have a title which is a label positioned adjacent to the axis. To set the title text simply use the setTitle method on the axis. You can style the title via the AxisTitleStyle object in the axis’ AxisStyle object.

Rendering Data

The axes are responsible for mapping the data values you provide into pixel values for display on the device screen. It is sometimes useful to make use of this mapping for adding additional elements on top of your chart. We highly recommend using our Annotation feature for this, as it fully supports panning and zooming. However, the axis.getPixelValueForUserValue and axis.getUserValueForPixelValue methods support the conversion between data and pixel values.

Data Ranges

There are several “ranges of data” associated with a chart axis at any one time.

  • displayed range: the current range displayed on the axis. This will be changed by panning and zooming actions or programmatic range changes.
  • data range: the absolute minimum and maximum values across all data series represented by this axis.
  • defaultRange: the range displayed after initial load. Set to the dataRange by default but can also be set when creating the axis.

The ranges represent a minimum and maximum value, with NumberAxis and DateTimeAxis each having their own range type: NumberRange and DateRange respectively. CategoryAxis uses NumberRange where the range is applied to the integer indexes of the items.

By default, an axis will display the whole data range when it initially renders. You can also create an axis with a range if you would like a different initial range. An example of this is shown below, using NumberAxis:

Java

NumberRange range = new NumberRange(0.0, 100.0);
NumberAxis axis = new NumberAxis(range);

C#

NumberRange range = new NumberRange(0.0, 100.0);
NumberAxis axis = new NumberAxis(range);

Once the chart has rendered, if you wish to explicitly set the range on a chart axis you can call one of the requestCurrentDisplayedRange methods available on the Axis API.

Tip: A CategoryAxis will accept a non-integer number range to allow precise adjustments to the displayed data.

Tick Marks

The chart axis can display a set of tick marks and their labels that show the current range of data. There are two levels of tickmark: major and minor; both can be auto-calculated.

To configure the display and style of tickmarks on the chart, use the methods on the TickStyle object - this can be obtained from the Axis’ AxisStyle object. The following code, for example, will hide the tick lines for all major tickmarks:

Java

axis.getStyle().getTickStyle().setMajorTicksShown(false);

C#

axis.Style.TickStyle.MajorTicksShown = false;

To prevent auto-calculation and provide your own frequencies for the tickmarks, use the following methods: axis.setMajorTickFrequency and axis.setMinorTickFrequency. If the major tick frequency is set the chart will only display a major tick mark at this frequency, regardless of zoom level. The same pattern is followed for minor tick marks if a minor tick frequency is set. Once the major tick frequency has been set (or auto-calculated), tickmarks with regular spacing will be generated.

Once the tickmarks are placed on the chart, a suitable behavior near to the edge of the axis can be configured. The axis.setTickMarkClippingModeHigh and axis.setTickMarkClippingModeLow methods control how the tickmarks appear and disappear at the edge of the axis range, which will change during pan and zoom operations.

Multiple Axes

Whilst the chart must have a minimum of one x-axis and one y-axis, in the premium version of shinobicharts there is no maximum limit. Additional X-axes can be added along the bottom (default) or top of the plot area. Y-axes can be added on the left (default) or right edge of the plot area. If an axis already exists in this location, the chart will stack the axes with the first axis innermost and the last axis outermost.

In the standard version you are restricted to exactly one x-axis and one y-axis. However, the positioning options are still the same as in the premium version.

Add an additional y-axis on the right side of the chart as follows:

Java

NumberAxis rightY = new NumberAxis();
rightY.setPosition(Axis.Position.REVERSE);
chart.addYAxis(rightY);

C#

NumberAxis rightY = new NumberAxis();
rightY.AxisPosition = Axis.Position.Reverse;
chart.AddYAxis(rightY);

After you have added an additional axis, you will want to assign a particular series to it (the default assignment for a series is the primary axes). The chart.addSeries(Series, Axis, Axis) method allows you to add a series to a chart and assign particular axes to it all in one go. If the series has already been added to the chart it should be removed first before adding it again. Furthermore, if the axes passed into this method do not already belong to the chart they will be automatically added.

The chart.getXAxis and chart.getYAxis methods return the primary axes. To get a secondary axis use the chart.getAllXAxes and chart.getAllYAxes methods to obtain a list of all the x or y-axes and then call the get(int) method on these lists, specifying the index of the desired axis. Primary axes are always at position 0. The secondary axes are then indexed by the order they are added.

Discontinuous Axes

NOTE: Discontinuous Axes are a premium version only feature.

You can instruct a NumberAxis or a DateTimeAxis to skip over certain ranges. This can be useful when you do not want to display any gaps in your data. For example, if your data set is only relevant for weekdays you may wish to skip over weekends.

The discontinuous functionality is not applicable for a CateoryAxis and it will just ignore any skip ranges that it is given.

Tip: Unlike the iOS version, shinobicharts for Android does not distinguish between discontinuous and non-discontinuous axes - if you want a discontinuous axis you just add some skip ranges.

In order to add a range to be skipped by the axis, create an appropriate Range object: for NumberAxis create a NumberRange and for DateTimeAxis create a DateRange. Then simply call the addSkipRange method on the axis passing in the Range you created. Any data points that fall within a skipped range will not be displayed. Skipped ranges can also be removed using the removeSkipRange method.

When a skip range is added or removed from an Axis a number of internal calculations must be made. If you are adding or removing a large number of skip ranges then this may impact on your app’s performance. In order to minimize the number of internal calculations made, you can add a whole list of ranges to be skipped which will be much more efficient.

Another performance aspect to be aware of is that adding or removing a skip range will also instruct the ShinobiChart to reload all of its data. Again, when adding a large number of skip ranges, it is more efficient to do this before the Axis is given to the ShinobiChart, or if necessary when you have temporarily removed the Axis from the ShinobiChart.

For the DateTimeAxis you can also add repeated skip ranges, meaning you do not have to specify each individual skip range up front. To add a repeated skip range, create a RepeatedTimePeriod object and pass it into the addRepeatedSkipRange method on the DateTimeAxis. Like with normal skip ranges, adding a repeated skip range will instruct the ShinobiChart to reload its data.

Zooming and Panning

The current display range of each axis defines a window of data on the chart. This window can grow smaller or larger using zoom operations or translated using pan operations. A pinch gesture on the plot area will result in a zoom and a swipe gesture a pan. Gestures can be combined, just like they are on popular mapping kits, to produce combinations of pans and zooms. shinobicharts comes fully equipped with a comprehensive set of gestures for manipulating axis ranges.

Panning and zooming is enabled on a per axis basis, allowing full control over the window view of the data. The following code enables both panning and zooming for an x-axis:

Java

chart.getXAxis().enableGesturePanning(true);
chart.getXAxis().enableGestureZooming(true);

C#

chart.XAxis.GesturePanningEnabled = true;
chart.YAxis.GestureZoomingEnabled = true;

To give the pan and zoom gestures a more natural feel, you should enable the momentum effect. This will trigger a deceleration at the end of any one or combination of gestures:

Java

chart.getXAxis().enableMomentumPanning(true);
chart.getXAxis().enableMomentumZooming(true);

C#

chart.XAxis.MomentumPanningEnabled = true;
chart.XAxis.MomentumZoomingEnabled = true;

The ShinobiChart.OnAxisMotionStateChangeListener and Axis.OnRangeChangeListener callback interfaces provide methods that are called during and after pan and zoom operations. You should use these methods to manage any fixed zoom and pan limits.

Panning and zooming gestures will be consumed by the chart, preventing any parent views from acting on them, if they are enabled for both X and Y axes. If they are only enabled on one axis then a parent view may be allowed to use the gesture. For example, if a ListView is populated with charts with gestures enabled on the X axis then the chart will allow the ListView to scroll if the user pans vertically.

Panning and Zooming Programmatically

The fundamental effect of pan and zoom operations is a range change on one or more axes. Therefore, you should use one of the requestCurrentDisplayedRange methods available on the Axis API to set the ranges to achieve the desired effect.

The Chart’s Series

The series on a chart define how the data should be visually represented on the plot area of the chart. You will have one or more series displayed on the chart at one time; all of which will be a subclass of Series.

Series fall into two groups - those that use an (x, y) co-ordinate space and require axes (CartesianSeries), and those that do not need any axes (PieDonutSeries). Examples of cartesian series are LineSeries and ColumnSeries.

The two concrete series in the PieDonutSeries grouping, PieSeries and DonutSeries, are special cases. Their data points only have an angle and they have a fixed radius. The X value of a datapoint given to a Pie/Donut Series is used as the name of the slice, and the Y value is used as its magnitude (converted to an angle in relation to the other data in the same series).

Series Types

There are many series types available:

  • Cartesian:
    • LineSeries draws data points and connects them, in the order given, using a line. The display of individual points may be enabled or disabled as required. Similarly the area fill under the line may be enabled or disabled as required to form Area Series.
    • ColumnSeries draws data points as vertical columns on the chart.
    • BarSeries draws data points as horizontal bars on the chart.
    • BandSeries draws two lines (high and low) and can shade the area between them (premium version only).
    • CandlestickSeries draws candlestick data points (premium version only).
    • OHLCSeries draws Open High Low Close data points (premium version only).
  • PieDonut:
    • PieSeries draws data points as pie slices around a central point.
    • DonutSeries draws data points as donut slices around a central point.

Most of the series will only require a single x value and a single y value for each data point to render. For these data points you should use DataPoint or provide your own implementation of the Data interface.

BandSeries, CandlestickSeries and OHLCSeries are advanced, financial series and require multiple values on one axis to render. For these series types you should use MultiValueDataPoint or provide your own implementation of the MultiValueData interface.

Styling the Series

The series dictate how the data will be rendered on the chart’s plot area. The type of series defines the structure of the series - but the style properties define the look. Each series has a style and selectedStyle property that contains all of the properties used to alter the look of the series when rendered. The style property is used when the series is not selected and the selectedStyle is used when the series has been selected.

For example, the following code will set the line color and thickness for a LineSeries:

Java

lineSeries.getStyle().setLineColor(Color.RED);
lineSeries.getStyle().setLineWidth(2.0f);

C#

lineSeries.Style.LineColor = Color.Red;
lineSeries.Style.LineWidth = 2.0f;

Some series types offer more fine-grained styling options. BarSeries, ColumnSeries, LineSeries, CandlestickSeries and OHLCSeries all offer the customization of the look and feel of individual data points. In most cases, the approach to style an individual data point is the same. SeriesStyle objects to be applied to each data point within a series are provided by the series’ SeriesStyleProvider. In all cases, the default implementation of the SeriesStyleProvider will simply return the SeriesStyle object from the series’ style or selectedStyle as appropriate. To style individual data points, one must provide a custom SeriesStyleProvider. This will return a SeriesStyle object containing your chosen style properties for the data point specified by you. For example, if you would like to style an individual data point of a BarSeries you may chose to implement a SeriesStyleProvider something like this:

Java

public class StyleBarAtIndexOne implements SeriesStyleProvider<BarSeriesStyle> {

    @Override
    public <S extends Series<BarSeriesStyle>> BarSeriesStyle provide(Data<?, ?> data, int index, S series) {
        final SeriesStyleProvider<BarSeriesStyle> defaultSeriesStyleProvider = series.createDefaultSeriesStyleProvider();
        BarSeriesStyle providedStyle = defaultSeriesStyleProvider.provide(data, index, series);

        if (index == 1) {
            providedStyle = new BarSeriesStyle(providedStyle);
            providedStyle.setAreaColor(Color.CYAN);
            providedStyle.setFillStyle(SeriesStyle.FillStyle.FLAT);
            providedStyle.setLineColor(Color.YELLOW);
        }

        return providedStyle;
    }
}

C#

public class StyleBarAtIndexOne : Java.Lang.Object, ISeriesStyleProvider {

    public Java.Lang.Object Provide(IData data, int index, Java.Lang.Object series) {
        ISeriesStyleProvider defaultSeriesStyleProvider = ((BarSeries)series).CreateDefaultSeriesStyleProvider();
        BarSeriesStyle providedStyle = (BarSeriesStyle)defaultSeriesStyleProvider.Provide(data, index, series);

        if (index == 1) {
            providedStyle = new BarSeriesStyle(providedStyle);
            providedStyle.AreaColor = Color.Cyan;
            providedStyle.FillStyle = SeriesStyle.FillStyle.Flat;
            providedStyle.LineColor = Color.Yellow;
        }

        return providedStyle;
    }
}

As you can see in this example, a number of useful objects are passed into the provide method. Here we use the index parameter to decide if we wish to style the data point in a different way to the others. In this case we wish to style the data point at index position 1. You will notice that for any other data point, we return the SeriesStyle object obtained from the default SeriesStyleProvider.

When we detect that the index of our data point is 1, we take a different approach. We create a new SeriesStyle object, passing in the default style for the series. This gives us a nicely initialized style object which we can safely modify without affecting the look and feel of any other data point. As the code above shows, we make changes to the style such as setting the colors, and return this modified style object. These styling properties will only be applied to the data point passed in to the provide method of the SeriesStyleProvider which in this case is the data point at index position 1. You could of course be more adventurous with both your decision logic and your styling options. For example, you could style data points using different colors dependent upon their data values.

Once we have implemented our SeriesStyleProvider we must not forget to set it on our Series. The code for this is very simple:

Java

barSeries.setSeriesStyleProvider(myCustomSeriesStyleProvider);

C#

barSeries.SeriesStyleProvider = myCustomSeriesStyleProvider;

Tip: Although for simplicity in the example above we are creating new objects inside the provide method, allocations and computationally expensive operations performed here will increase render time. Therefore, it is recommended that you create all the required SeriesStyle objects in advance where possible, rather than instantiating them in this method.

Similarly, to remove any custom point-styling behavior and restore defaults, we use similar code, but we pass in an instance of the default variant, which we get with the help of the handy method on our series:

Java

seriesStyleProvider = barSeries.createDefaultSeriesStyleProvider();
barSeries.setSeriesStyleProvider(seriesStyleProvider);

C#

ISeriesStyleProvider seriesStyleProvider = barSeries.CreateDefaultSeriesStyleProvider();
barSeries.SeriesStyleProvider = seriesStyleProvider;

It is worth noting that some properties within a SeriesStyle object apply to individual data points, but some apply to the series as a whole. Please see the API docs of the various subclasses of SeriesStyle for more information. This is particularly relevant for a LineSeries, which has a slightly different styling object hierarchy. In this case each LineSeriesStyle has within it, a PointStyle object for both the points and selected points. When setting the custom style properties of the data point you would do so by retrieving the appropriate PointStyle from the series style object, and setting the properties, such as the radius.

Also, bear in mind that whilst a LineSeriesStyle object has both a pointStyle and a selectedPointStyle, if the series itself is selected, then during styling only the selectedPointStyle will be used. This is because all data points within a selected LineSeries are indeed themselves selected.

To see individual point styling in action take a look at our How to: Style Individual Data Points guide and related sample app.

Shaping the Series

In some scenarios it may be desirable to change the visible shape of your series, for example you may wish to identify tendencies to the user rather than present them with ‘noisy’ data that track every anomaly. The LineSeries provides the means to draw the line based on an interpolated set of data points, whilst leaving the original source data un-touched.

A common use case for this is that of line smoothing. You may for example wish the line to consider additional synthetic data points, which comprise of an interpolation of two ‘real’ data points. This is achieved with the DataValueInterpolator interface. Here you will find one method, getDataValuesForDisplay which requires a List of Data items and returns a new list comprising the interpolated items based on the original data points. You can of course implement this interface in any way you wish. You can simply set your DataValueInterpolator onto your LineSeries using one line of code:

Java

lineSeries.setLinePathInterpolator(linePathInterpolator);

C#

lineSeries.LinePathInterpolator = linePathInterpolator;

Using this technique, when the ShinobiChart is next asked to draw the line of your LineSeries it will base the line on those data values returned by the DataValueInterpolator.

By default the Crosshair will track along the line of a LineSeries, interpolating between data points as it goes. This behavior is no different when a DataValueInterpolator is set on a LineSeries; in this case the Crosshair will smoothly travel along the modified line. When the Crosshair has interpolation for LineSeries disabled this means it will jump from data point to data point. In this situation only the original data points retrieved from the LineSeriesDataAdapter are considered and the DataValueInterpolator’s values are ignored.

Curved Bars and Columns

Using the setCornerRatio method on BarSeries or ColumnSeries it is possible to control how curved the tips of each bar and column are by applying a corner radius to the tips’ corners. The corner radius is calculated by multiplying the cornerRatio value with half the bar or column width. The cornerRatio is a value between 0 and 1.

For example, the following code will set the corner ratio for a ColumnSeries to 1 to show fully curved columns:

Java

columnSeries.getStyle().setCornerRatio(1.0f);

C#

columnSeries.CornerRatio = 1.0f;

Additionally, the smoothness, or resolution, of the rounded corners can be altered by using the setNumberOfCornerEdges method on BarSeries and ColumnSeries. The value passed in to this method is the number of edges per corner and must be greater than or equal to 1. Therefore, a value of 1 along with a cornerRatio of 1.0f, will result in a triangular bar or column tip; that is two edges on the tip because each tip has two corners.

This setting does not normally need to be changed from the default value of 15 but can be used to optimize memory usage; the smaller than value the less memory used but the less smooth the rounded corner.

Stacking Series

By default, series are independent of each other and will render without any regard for the other series on the plot area. This is due to each series being given their own unique StackingToken upon creation. The effect of this on how the series is laid out depends on the particular series type. LineSeries and BandSeries are simply plotted at their data points with the last series to be added overlaid on previously added series. For ColumnSeries, CandlestickSeries and OHLCSeries, the data points for each series are laid out side-by-side at each X value. BarSeries are similar but lay data points out side-by-side at each Y value.

Certain series types however can be grouped together so that their values can overlap, or in some cases be summed cumulatively. In order to group several series into a stack, you must provide the same object for each series’ stackingToken property. The stacking token comes in two flavors, cumulative and overlapping. To set a stacking token on a CartesianSeries you would call its setStackingToken method, passing in the appropriate StackingToken. Generating a StackingToken is a simple case of calling the relevant static creator method. For example, to create an overlapping stacking token, simply call newOverlappingStackingToken on the StackingToken class.

CartesianSeries all support overlapped stacking. In this case each series in the same stacking group, with a DataPoint at the same ordinal will be drawn with these DataPoints overlapped. This is no different to the default behavior for LineSeries and BandSeries but prevents the other series types from being laid out side-by-side.

LineSeries, BarSeries and ColumnSeries also support cumulative stacking. In this case each series in the same stacking group, with a DataPoint at the same ordinal will have these DataPoints drawn cumulatively stacked. For example, in the case of a ColumnSeries, the first series added to the chart is placed at the bottom, starting at the baseline, with subsequent series offset so that they begin at the top of the previous series.

Tip: If you attempt to set a cumulative stacking token on a CartesianSeries type that does not support it, the request will be ignored.

It is important to remember that only CartesianSeries of the same type can belong in a stacking group.

To display cumulatively stacked LineSeries reliably, their data should have either all positive or all negative data values. BarSeries and ColumnSeries have the same requirement, but on a per-ordinal basis. In addition to this, your data should be ordered along the ordinal axis. So for LineSeries and ColumnSeries it should be ordered along the X axis and for BarSeries along the Y axis.

Selection

Series, and their individual points, can be switched from a de-selected state to a selected state, and back again. Call the series.setGestureSelectionMode method with a value from the Series.GestureSelectionMode enum to choose the particular behavior. Series selection will ensure that the whole series will be selected, whereas point mode will only set the nearest point as selected. If the intention is for only a single Series to be in the selected state at any one time, you should call chart.setSeriesSelectionSingle(true).

Selecting a Series or individual point will, by default, trigger a change in appearance. This is because the default implementation of each SeriesSeriesStyleProvider will provide either the style returned by series.getStyle or by series.getSelectedStyle depending on the selection mode of the Series and the selection state of either the Series or a particular point.

The ShinobiChart.OnSeriesSelectionListener callback interface can provide a means of being notified when a Series or point is selected or de-selected. Selection is triggered by a single touch event on the plot area with the Series with the nearest data point being selected or de-selected. If you have set an OnSeriesSelectionListener on your chart, it will be notified of any selection events.

PieSeries and DonutSeries have additional properties pertaining to selection events. Upon selection the PieSeries or DonutSeries can rotate the selected slice to a particular angle specified using the pieSeries.setSelectedPosition method (in radians).

Legend Entries

If series.setShownInLegend is called with true, the series will display in the legend. The series.setTitle method should be used to control the text description of the series in the legend.

Animation

shinobicharts includes a flexible Animation API that can be used to animate any kind of property. This includes the various properties of a Series, allowing you to create compelling visual effects for your charts. For example, it is possible to create animations for the various style properties of a Series, such as the areaColor of a LineSeries.

Additionally, the Series class has five properties that are optimized for use with our OpenGL rendering engine:

  • scaleX
  • scaleY
  • pivotX
  • pivotY
  • alpha

The first two allow the Series’ displayed size to be manipulated without affecting the axes. These properties are particularly useful for creating animations where the Series grows or shrinks.

The pivot properties are used in conjunction with the scale properties to define the point around which the Series should be scaled.

Finally, the alpha property can be used to create fade-style animations. Note that the alpha is applied to the Series as a whole.

The Animation API should look familiar to those who have used the Android Property Animation API. This has been done intentionally to reduce the learning curve for our users and indeed it is entirely possible to use the Android Property Animation API to animate a Series. However, there are a few key differences in the two systems:

shinobicharts Animation API Android Property Animation API
Supported API Level Android 2.3 (API Level 9) Android 3.0 (API Level 11)
Main Driving Factor Anything (time, axis range, SeekBar progress…) Time
Uses Generics Yes No
Manually Creating Animations

The general idea is to take some value and continually modify it based on some driving factor, such as time. A specific property of an object is then updated every time the value is changed.

The Animation class is the starting point for any animation. An Animation instance should be parametrized by a type appropriate for the type of the value being animated. For example, if you wanted to animate the scaleY property of a Series you would create an Animation<Float>.

Animations work in terms of the general concept of progress rather than time. Progress is a value between 0.0 and 1.0 where 0.0 represents the point at the beginning of the animation and 1.0 represents the point at the end of the animation. To set the current progress value for an Animation you can use the Animation.setProgress(float) method.

When creating an Animation two things are required: an AnimationCurve and an Evaluator.

The AnimationCurve determines how the value changes throughout the progress of the animation. Given a progress, it returns a proportion that is a normalized value of the animated quantity between its beginning and end positions where 0.0 is the start state and 1.0 is, nominally, the end state. While the progress will always be between 0.0 and 1.0, it is perfectly valid for the returned proportion to be less than 0.0 or greater than 1.0. This is particularly useful for creating bounce and overshoot animation effects.

The shinobicharts library comes with a number of pre-made AnimationCurve implementations, such as LinearAnimationCurve, EaseInOutAnimationCurve and BounceAnimationCurve, though it is possible to create your own custom curve. For a full list of the available implementations please take a look at the API docs.

It is the job of the Evaluator to map the normalized proportion to an actual value. In order to do this the Evaluator used must be parametrized by the same type as that of the Animation. The value that the proportion is mapped to will depend on the particular implementation. It may, for example, map a proportion to a value between specified start and end values such as the FloatEvaluator does. The generic nature of the Evaluator interface means, with a suitable implementation, you can create one for any type you wish. Included in the library are three implementations: FloatEvaluator, DoubleEvaluator and IntEvaluator.

So now that a value is being animated, how do we react to changes in that value? This is where the Animation.Listener comes in.

An Animation can be given any number of Animation.Listeners, provided the listeners are parametrized by the same type as the Animation, that is, the same type as the property being animated. The Animation.Listener interface has a sole method on it, onProgressChanged. Every time the progress of an Animation changes, it will call the onProgressChanged method of each of its listeners.

To obtain the current value for the animated quantity you can call the Animation.getAnimatedValue method; the Animation is provided in the parameters list for the onProgressChanged method so it will always be readily available. This allows the listener to update an object’s property with the new value. For convenience we provide three Animation.Listener implementations with the library: SeriesScaleXAnimationListener, SeriesScaleYAnimationListener and SeriesAlphaAnimationListener. These classes simply set the current animated value on the scaleX, scaleY and alpha properties of a Series respectively.

Creating a new fade-in Animation is therefore as simple as the following:

Java

Animation<Float> animation = new Animation<>(new LinearAnimationCurve(), new FloatEvaluator(0.0f, 1.0f));
animation.addListener(new SeriesAlphaAnimationListener(series));

C#

Animation animation = new Animation(new LinearAnimationCurve(), new FloatEvaluator(0.0f, 1.0f));
animation.AddListener(new SeriesAlphaAnimationListener(series));

Here we are steadily changing the value of the Seriesalpha property from 0.0, which translates to fully transparent to 1.0, which means fully opaque.

Finally, there are situations where you wish to animate more than one property at a time. To make a Series grow from a central point, both in the X and Y directions, the scaleX and scaleY properties will need to be modified. One way of achieving this is to create an Animation.Listener that sets both properties at the same time, and this is a perfectly acceptable solution.

However, if you want to animate two properties with different animation curves, or that are of differing types, you can use an AnimationSet to hold multiple Animations and run them all in parallel. As it is an Animation itself it can be run the same way as any ordinary Animation is.

As an example, to animate a SeriesscaleX value with a BounceDelayAnimationCurve and its scaleY value with a DelayBounceAnimationCurve, both from 0.0 to 1.0 to make it grow, you would do the following:

Java

Animation<Float> scaleXAnimation = new Animation<>(new BounceDelayAnimationCurve(), new FloatEvaluator(0.0f, 1.0f));
scaleXAnimation.addListener(new SeriesScaleXAnimationListener(series));

Animation<Float> scaleYAnimation = new Animation<>(new DelayBounceAnimationCurve(), new FloatEvaluator(0.0f, 1.0f));
scaleYAnimation.addListener(new SeriesScaleYAnimationListener(series));

AnimationSet<Float> animationSet = new AnimationSet<>();
animationSet.add(scaleXAnimation);
animationSet.add(scaleYAnimation);

C#

Animation scaleXAnimation = new Animation(new BounceDelayAnimationCurve(), new FloatEvaluator(0.0f, 1.0f));
scaleXAnimation.AddListener(new SeriesScaleXAnimationListener(series));

Animation scaleYAnimation = new Animation(new DelayBounceAnimationCurve(), new FloatEvaluator(0.0f, 1.0f));
scaleYAnimation.AddListener(new SeriesScaleYAnimationListener(series));

AnimationSet animationSet = new AnimationSet();
animationSet.Add(scaleXAnimation);
animationSet.Add(scaleYAnimation);

The AnimationSet class is also generic in nature and needs to be parametrized by a suitable type for the types of Animations it will accept. Note that it is possible to add Animations of differing types to the same AnimationSet as long as its generic type is a superclass of all the Animation types added to it.

Creating Animations with the SeriesAnimationCreator

In addition to creating them manually, Animations specifically for Series can be created by using a SeriesAnimationCreator. Some examples of the implementations included in the library are FadeAnimationCreator, GrowAnimationCreator and HorizontalTelevisionAnimationCreator; for a full list please refer to the API docs. These various implementations all create the necessary Animations and add suitable Animation.Listeners to achieve the desired result.

As the FadeAnimationCreator uses a LinearAnimationCurve, the above example of creating a fade-in animation could be rewritten as follows:

Java

Animation<Float> animation = new FadeAnimationCreator().createEntryAnimation(series);

C#

Animation animation = new FadeAnimationCreator().CreateEntryAnimation(series);

The SeriesAnimationCreator defines how to create both an entry and an exit Animation - an entry animation is one that brings a Series into view whereas an exit animation is one that takes a Series out of view. Therefore, when creating your own implementations of the SeriesAnimationCreator you need to supply two generic parameters: the first is the type of the property being animated in the entry animation and the second is the type of the property being animated in the exit animation. More often than not these will be the same though there is no requirement for them to be.

In the previous shinobicharts Animation API, a Series would be given its entry and exit animations directly. However, this has now been separated out and the responsibility passed on to the SeriesAnimationCreator. However, there are times when you just want to use the default entry and exit animations for a given Series type. This can be done by using the DefaultSeriesAnimationCreatorFactory or by using the Series.createDefaultEntryAnimation and Series.createDefaultExitAnimation methods.

Running Animations with the AnimationRunner

As has been mentioned above, one of the main advantages of the shinobicharts Animation API is that it is not tied down to time. That said, a very common use-case is to run an Animation over a set period of time. In order to do this you can use an AnimationRunner.

Java

AnimationRunner<Float> animationRunner = new AnimationRunner<>(animation, 2000L);

C#

AnimationRunner animationRunner = new AnimationRunner(animation, 2000L);

To create an AnimationRunner you need to supply the Animation to run and a duration, in milliseconds. The AnimationRunner should be parametrized by the same type as that of the Animation it is running. The default duration for the Animations created by the various implementations of the SeriesAnimationCreator is defined by the constant SeriesAnimator.DEFAULT_SERIES_ANIMATION_DURATION.

If you wish to be informed about certain events during the course of an Animation, such as the start, end or if it has been cancelled, you can add an AnimationRunner.Listener to the AnimationRunner.

To start the Animation running you simply call start on the AnimationRunner:

Java

animationRunner.start();

C#

animationRunner.Start();

Tip: There is no restriction on running multiple Animations on the same Series concurrently so if they are competing with each other or are out of sync then you may get some unexpected results. Using the SeriesAnimator reduces the amount of boilerplate code required and ensures that only one Animation is run on a Series at any time.

Using the SeriesAnimator

The SeriesAnimator is a convenience class that handles adding, removing and setting the visibility of a Series, with time-based animations, on a ShinobiChart. Via a SeriesAnimationCreator, it handles creating the necessary Animations and Animation.Listeners for the Series reducing a lot of the work for the user.

The SeriesAnimator can be obtained from the ShinobiChart:

Java

SeriesAnimator seriesAnimator = shinobiChart.getSeriesAnimator();

C#

SeriesAnimator seriesAnimator = shinobiChart.SeriesAnimator;

Where a Series is stacked, the SeriesAnimator will animate all of the Series in the same stacking group out of view first, perform the required operation (such as setting the visibility of a Series) and then animate the remaining Series back into view.

Importantly, the SeriesAnimator will ensure that only one Animation is running on a Series at any one time.

At its most basic usage, the SeriesAnimator can be told to add or remove a single Series or, set the visibility of multiple Series. In these cases it will use the default SeriesAnimationCreator for the given Series type and the DEFAULT_SERIES_ANIMATION_DURATION. Therefore, adding a Series with the default animation is as simple as:

Java

seriesAnimator.addSeries(series);

C#

seriesAnimator.AddSeries(series);

If you want more control you can also supply a SeriesAnimationCreator, to define how the Series is to be animated in and out of view, and a custom duration. If you want to be notified of the beginning and end of an Animation created by the SeriesAnimator you can give it a SeriesAnimator.Listener.

To see the SeriesAnimator in action take a look at the included Series Hiding And Animation Sample.

Legend

The chart’s legend is a view that displays a representation of the series on the chart (series can be configured not to appear in the legend). Enable the legend by setting its visibility:

Java

chart.getLegend().setVisibility(View.VISIBLE);

C#

chart.Legend.Visibility = ViewStates.Visible;

The legend can be rendered in a number of locations on the chart using the legend.setPosition method. The position can be made to be relative to either the plot area or the chart by using the legend.setPlacement method.

For Pie and Donut charts, an entry for each slice (data point) rather than each series is added to the legend. Only the first series is displayed.

Crosshair

The crosshair on the chart is used to identify specific values on cartesian series; it is not applicable for PieDonutSeries. It is enabled using a long touch gesture on the plot area and will track the nearest series for which tracking is enabled. The crosshair is enabled per series using the cartesianSeries.setCrosshairEnabled method.

The Crosshair provides a small circle target with lines that extend to the axes and is enabled with a tap-and-hold gesture. The crosshair will appear on the nearest series and will remain on this series during drag gestures. The crosshair will move smoothly between data points, interpolating as it goes, when tracking a line series. On all other series types it will snap between data points.

The tooltip accompanies the lines and target circle and displays information about the current data point. There are a number of styling options available through the crosshair’s CrosshairStyle object.

When the crosshair is enabled on any series in a chart, the chart will consume any panning and zooming gestures that are made on it even if the crosshair is not currently active. This will prevent parent views from using those gestures.

The time at which the chart considers a touch gesture to be a long touch gesture can be customized using the setLongTouchTimeout method on the ShinobiChart. This will affect how quickly the crosshair will appear. Setting this to a value lower than 60 will simply toggle the crosshair on and off on a long touch gesture. Also please note, a value lower than 100 may detract from the user experience.

Annotations

NOTE: Annotations are a premium version only feature.

Annotations can be useful to add visual indicators to your chart. They can be ‘pinned’ to an (x, y) location on the chart and as a result will move as the user pans and zooms so they remain at that (x, y) location. Annotations can also be set to span specific ranges on both the X and Y axis. This causes the annotation to be stretched/deformed as the user zooms in and out.

Annotations cannot be directly created. Instead, the AnnotationsManager, which can be obtained from the ShinobiChart, provides the necessary methods to create and add them to your chart. Essentially, an annotation is a holder for a View and these methods create an annotation with an appropriate View for the intended use. It is also possible to create an annotation with your own view by using the addViewAnnotation method.

Each add method returns the annotation it has just created enabling the user to retain a reference to it and customize its visual appearance. The AnnotationsManager also provides access to all of the chart’s annotations.

As with the other visual elements on a ShinobiChart, each annotation has a style object which can be modified to change the annotation’s appearance. Depending on the type of annotation, only certain properties of the style object will be used.

Additionally, annotations can be placed in front of or behind the plotted data on the chart by using the setPosition method and the values in the Annotation.Position enum.

Text Annotations

The addTextAnnotation method will create, and add to your chart, an annotation with a TextView containing the supplied text. By default the TextView will be set to wrap its content. It will make use of all of the properties in its AnnotationStyle object.

Line Annotations

The addHorizontalLineAnnotation and addVerticalLineAnnotation methods create annotations that span the plot area centered around a specified data value: horizontal line annotations span the width of the plot area and are centered on a Y axis value, while vertical line annotations span the height of the plot area and are centered on an X axis value. Line annotations are given a thickness, in device independent pixels, and will remain at this thickness regardless of how much the user zooms in and out.

The only style property relevant to a line annotation is the background color - as it has no text it just ignores the text color, size and typeface values.

Band and Box Annotations

The addHorizontalBandAnnotation, addVerticalBandAnnotation and addBoxAnnotation methods create annotations that span a range on an axis. Consequently, as the user zooms in and out of the chart the band annotation will grow and shrink accordingly. Horizontal band annotations span the width of the plot area and their given Y axis range, while vertical band annotations span the height of the plot area and their given X axis range. Box annotations are simply band annotations that span both an X and a Y axis range.

Like line annotations, the only relevant style property is the background color.

Custom View Annotations

While text, line and band annotations will cover the majority of use cases there may be times when you want more control. The addViewAnnotation creates an annotation with a user supplied View. This can be any type of View and because of this the annotation’s style object is completely ignored.

Advanced Customization

The view that the annotation holds is accessible via the getView method. This allows direct modification of the view for more complex customization situations. It also allows access to the view’s LayoutParams. These can be modified to change the behavior of the annotation.

Setting the width and height of the view’s layout params to WRAP_CONTENT or a specific value will ensure the annotation remains at a fixed size during zoom gestures. Setting the width or height of the layout params to MATCH_PARENT will cause it to stretch across the whole of the width or height of the plot area. Finally, if an X range or Y range has been set on the annotation, using setXRange or setYRange, then setting the width or height of the layout params to 0 will cause the annotation to stretch to that range on the relevant axis and will scale accordingly during zoom gestures. Modifying the layout params in this way makes it possible to, for example, turn a text annotation into a line annotation that includes some text.

Callbacks

There are a number of callback interfaces defined on ShinobiChart which allow you to react to certain events. These include:

  • crosshair movement
  • selection of series or points
  • layout completion
  • general touch gestures

Similarly, the Axis has a callback interface defined on it which allows you to react to changes in its range.

Appropriate listeners which implement these callback interfaces should be set on the ShinobiChart or in the case of axis range changes, the Axis.

Common Use Cases
  • Axis.OnRangeChangeListener.onAxisRangeChange: This is called when the displayed range on an axis changes. A common use of this method is to restrict the amount the chart can be zoomed in by, by checking the span of the displayed range and if necessary resetting it to a valid value.
  • OnSeriesSelectionListener.onSeriesSelectionStateChanged: This is called when a series is either selected or de-selected. A common use of this method is to display a particular set of data depending on which series in a chart has been selected.
  • OnInternalLayoutListener.onInternalLayout: This is called when the chart has been laid out. At this point, calls to the axis.getPixelValueForUserValue and axis.getUserValueForPixelValue methods will return meaningful values and can be used to position other views in your layout.

Adding a Chart to an Application

There are a couple of different ways of adding a ShinobiChart to your Android application. You can either add a ChartFragment or a ChartView to your XML layouts or via code. A SupportChartFragment is also provided for compatibility with older versions of Android; you will also have to include the Android Support Library in your app (see http://developer.android.com/tools/extras/support-library.html).

The ChartFragment handles the forwarding of lifecycle callbacks from the Activity for you. It is also retained across Activity re-creation and therefore retains its ShinobiChart; in Android an Activity is destroyed and re-created on configuration changes such as when you rotate your device. Using ChartFragment directly or subclassing it as necessary is the route we would expect most users to go.

When using a ChartView it is up to you to manage this yourself, forwarding any Activity lifecycle callbacks to the ChartView and deciding how you want to persist the ShinobiChart across configuration changes. See How to: Manage Chart Lifecycles for more detail on this.

The code snippet below shows how to add a ChartFragment in XML and retrieve it, and its ShinobiChart, in code:

XML Layout

<fragment
    android:id="@+id/chart"
    class="com.shinobicontrols.charts.ChartFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Activity

FragmentManager manager = getFragmentManager();
chartFragment  = (ChartFragment) manager.findFragmentById(R.id.chart);
ShinobiChart shinobiChart = chartFragment.getShinobiChart();

The next code snippet shows a simple case of using a ChartView:

XML Layout

<com.shinobicontrols.charts.ChartView
    android:id="@+id/chart"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

Activity

ChartView chartView = (ChartView) findViewById(R.id.chart);
ShinobiChart shinobiChart = chartView.getShinobiChart();

If you have downloaded a trial version of shinobicharts you will have been issued with a trial key. You will need to give this key to the chart in order for it to be displayed. You can do this as follows:

shinobiChart.setTrialKey("<trial_key_here>");

Make sure you change <trial_key_here> to the trial key you were given!

When creating a cartesian chart (for example, a Line, Bar or Column chart but not a Pie chart), you must supply some axes - one X axis and one Y axis - before it can render a series. The axes must be set before the series is rendered.

Java

NumberAxis xAxis = new NumberAxis();
shinobiChart.setXAxis(xAxis);

NumberAxis yAxis = new NumberAxis();
shinobiChart.setYAxis(yAxis);

ColumnSeries series = new ColumnSeries();

shinobiChart.addSeries(series); // This series will use xAxis and yAxis as its axes.

C#

NumberAxis xAxis = new NumberAxis();
shinobiChart.XAxis = xAxis;

NumberAxis yAxis = new NumberAxis();
shinobiChart.YAxis = yAxis;

ColumnSeries series = new ColumnSeries();

shinobiChart.AddSeries(series); // This series will use xAxis and yAxis as its axes.

Rendering the Chart

When making changes to a chart (for example, styling), the chart will not redraw straight away. This is to avoid multiple redraws occurring in a row when making lots of individual changes. In order to redraw the chart, simply call the redrawChart() method on a ShinobiChart.

Note that when a DataAdapter calls fireUpdateHandler(), the chart will be redrawn. If you are using the supplied SimpleDataAdapter this occurs each time a data point/set of data points is added or removed.

Reloading Chart Data

The data contained within the chart is only recalculated as needed. It is up to the data adapter to inform the chart that its data has changed. This tells the chart that it will need to refresh its data the next time it renders.

Using the supplied SimpleDataAdapter will result in the chart being redrawn every time a data point/set of data points is added or removed. Alternatively, you can create your own data adapter, that extends the abstract DataAdapter class, and choose when to inform the chart of changes to its data, via the fireUpdateHandler() method.

Hardware Acceleration

In order to display a chart, some devices need to be explicitly told that the application requires hardware acceleration. To do this simple add android:hardwareAccelerated="true" to the application element of your app’s AndroidManifest.xml file.

Using ProGuard

The shinobicharts library contains a consumer ProGuard file that will automatically be applied if you are making use of the .aar file. This will ensure that when running your app through ProGuard, the shinobicharts library’s classes will not be removed.

If you prefer to do this manually, or you are not using the .aar file directly you can put the following into your app’s ProGuard file:

-keep class com.shinobicontrols.* { *; }

Styling a Chart

Every UI element in a ShinobiChart has a style object, including the chart itself, the axes, each series type, the crosshair, the legend, the gridlines and the title. These style objects can be used to style a chart and customize the look and feel by using their various different methods.

When making changes to these style objects the chart will not redraw straight away. This is to avoid multiple redraws occurring in a row when making lots of individual changes. To make the changes take effect and to redraw the chart, simply call the redrawChart() method on ShinobiChart.

Some elements may have more than one style object. For example, a series has a style and a selectedStyle. These contain the same properties but are used in the appropriate context.

Check the style classes in the API documentation for detailed descriptions of each style and the methods you can use to customize them.

Lifecycle of a Chart

In this section we will describe what happens when you add a chart to your application.

The first time a chart renders, or if you have told the chart to reload its data, it will query each series to determine if a recalculation is required or not (this is the case when the data has changed, or a style has changed).

If recalculations are required, then on the next render, it will pull the data from each DataAdapter, to be passed through to the OpenGL renderer.

Once the data is loaded, and the axes have been updated, the chart renders its plot area.