Android & Xamarin.Android Charting Documentation - SciChart Android Charts SDK v2.x
Tutorial 09 - Linking Multiple Charts

In our series of tutorials, up until now we have added a chart with two YAxis, one XAxis, two series, added tooltips, legends and zooming, panning behavior, and added some annotations. The previous tutorial can be found at this link: Tutorial 08 - Adding Multiple Axis.

Next, we are going to show you how to create multiple charts and link them together.

Revision

If you haven't already, you will need to review the following tutorials, as we are working straight from these:

Our code looks like this so far:

Copy Code
package com.example.tutorials.scichart.myapplication;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.LinearLayout;
import com.scichart.charting.model.dataSeries.XyDataSeries;
import com.scichart.charting.modifiers.ModifierGroup;
import com.scichart.charting.visuals.SciChartSurface;
import com.scichart.charting.visuals.annotations.HorizontalAnchorPoint;
import com.scichart.charting.visuals.annotations.TextAnnotation;
import com.scichart.charting.visuals.annotations.VerticalAnchorPoint;
import com.scichart.charting.visuals.axes.AxisAlignment;
import com.scichart.charting.visuals.axes.IAxis;
import com.scichart.charting.visuals.pointmarkers.EllipsePointMarker;
import com.scichart.charting.visuals.renderableSeries.IRenderableSeries;
import com.scichart.core.framework.UpdateSuspender;
import com.scichart.drawing.utility.ColorUtil;
import com.scichart.extensions.builders.SciChartBuilder;

import java.util.Collections;
import java.util.Timer;
import java.util.TimerTask;

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create a SciChartSurface
        final SciChartSurface surface = new SciChartSurface(this);

        // Get a layout declared in "activity_main.xml" by id
        final LinearLayout chartLayout = (LinearLayout) findViewById(R.id.chart_layout);

        // Add the SciChartSurface to the layout
        chartLayout.addView(surface);

        // Initialize the SciChartBuilder
        SciChartBuilder.init(this);

        // Obtain the SciChartBuilder instance
        final SciChartBuilder sciChartBuilder = SciChartBuilder.instance();

        // Create a numeric X axis
        final IAxis xAxis = sciChartBuilder.newNumericAxis()
                .withAxisTitle("X Axis Title")
                .withVisibleRange(-5, 15)
                .build();

        // Create a numeric axis
        final IAxis yAxisRight = sciChartBuilder.newNumericAxis()
                .withAxisTitle("Primary")
                .withAxisId("primaryYAxis")
                .withAxisAlignment(AxisAlignment.Right)
                .build();

        // Create another numeric axis
        final IAxis yAxisLeft = sciChartBuilder.newNumericAxis()
                .withAxisTitle("Secondary")
                .withAxisId("secondaryYAxis")
                .withAxisAlignment(AxisAlignment.Left)
                .withGrowBy(0.2, 0.2)
                .build();

        final int fifoCapacity = 500;
        final XyDataSeries lineData = sciChartBuilder.newXyDataSeries(Integer.class, Double.class)
                .withFifoCapacity(fifoCapacity)
                .build();

        final XyDataSeries scatterData = sciChartBuilder.newXyDataSeries(Integer.class, Double.class)
                .withFifoCapacity(fifoCapacity)
                .build();

        TimerTask updateDataTask = new TimerTask() {
            private int x = 0;

            @Override
            public void run() {
                UpdateSuspender.using(surface, new Runnable() {
                    @Override
                    public void run() {
                        lineData.append(x, Math.sin(x * 0.1));
                        scatterData.append(x, Math.cos(x * 0.1));

                        if (x % 100 == 0) {
                            boolean isEven = x % 2 == 0;
                            String yAxisId = isEven ? "primaryYAxis" : "secondaryYAxis";

                            TextAnnotation marker = sciChartBuilder.newTextAnnotation()
                                    .withYAxisId(yAxisId)
                                    .withIsEditable(false)
                                    .withText("Text")
                                    .withBackgroundColor(ColorUtil.Green)
                                    .withX1(x)
                                    .withY1(0.0)
                                    .withVerticalAnchorPoint(VerticalAnchorPoint.Center)
                                    .withHorizontalAnchorPoint(HorizontalAnchorPoint.Center)
                                    .withFontStyle(20, ColorUtil.White)
                                    .withZIndex(1)
                                    .build();

                            surface.getAnnotations().add(marker);

                            if (x > fifoCapacity) {
                                surface.getAnnotations().remove(0);
                            }
                        }

                        // Zoom series to fit the viewport
                        surface.zoomExtents();
                        ++x;
                    }
                });
            }
        };

        Timer timer = new Timer();
        long delay = 0;
        long interval = 10;
        timer.schedule(updateDataTask, delay, interval);

        // Create and configure a line series
        final IRenderableSeries lineSeries = sciChartBuilder.newLineSeries()
                .withDataSeries(lineData)
                .withYAxisId("primaryYAxis")
                .withStrokeStyle(ColorUtil.LightBlue, 2f, true)
                .build();

        // Create an Ellipse PointMarker for the Scatter Series
        EllipsePointMarker pointMarker = sciChartBuilder
                .newPointMarker(new EllipsePointMarker())
                .withFill(ColorUtil.LightBlue)
                .withStroke(ColorUtil.Green, 2f)
                .withSize(10)
                .build();

        // Create and configure a scatter series
        final IRenderableSeries scatterSeries = sciChartBuilder.newScatterSeries()
                .withDataSeries(scatterData)
                .withYAxisId("secondaryYAxis")
                .withPointMarker(pointMarker)
                .build();

        // Add a RenderableSeries onto the SciChartSurface
        surface.getRenderableSeries().add(scatterSeries);
        surface.getRenderableSeries().add(lineSeries);

        // Create and configure chart modifiers
        ModifierGroup chartModifiers = sciChartBuilder.newModifierGroup()
                .withZoomExtentsModifier().withReceiveHandledEvents(true).build()
                .withZoomPanModifier().withReceiveHandledEvents(true).build()
                .withPinchZoomModifier().withReceiveHandledEvents(true).build()
                .withCursorModifier().withReceiveHandledEvents(true).build()
                .build();

        // Create a TextAnnotation and specify the inscription and position for it
        TextAnnotation textAnnotation = sciChartBuilder.newTextAnnotation()
                .withX1(5.0)
                .withY1(55.0)
                .withText("Hello World! ^_^")
                .withHorizontalAnchorPoint(HorizontalAnchorPoint.Center)
                .withVerticalAnchorPoint(VerticalAnchorPoint.Center)
                .withFontStyle(20, ColorUtil.White)
                .build();

        // Add the chart modifiers to the SciChartSurface
        surface.getChartModifiers().add(chartModifiers);

        // Add the Y axis to the YAxes collection of the surface
        Collections.addAll(surface.getYAxes(), yAxisLeft, yAxisRight);

        // Add the X axis to the XAxes collection of the surface
        Collections.addAll(surface.getXAxes(), xAxis);

        // Add the annotation to the Annotations collection of the surface
        Collections.addAll(surface.getAnnotations(), textAnnotation);

        // Add the interactions to the ChartModifiers collection of the surface
        Collections.addAll(surface.getChartModifiers(), chartModifiers);

        surface.zoomExtents();
    }

Adding a Second Chart

Now we are going to do almost the same to create and configure the second chart. For example purposes, lets put aside the annotations part and modifiers part for now. Everything else will be the same. The code below adds one more SciChartSurface to the application:

Copy Code
final SciChartSurface surface2 = new SciChartSurface(this);

// Add the SciChartSurface to the layout
chartLayout.addView(surface2);

// Set layout parameters for both surfaces
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT, 1.0f);

surface.setLayoutParams(layoutParams);
surface2.setLayoutParams(layoutParams);

// Create a numeric X axis
final IAxis xAxis2 = sciChartBuilder.newNumericAxis()
        .withAxisTitle("X Axis Title")
        .withVisibleRange(-5, 15)
        //.withGrowBy(0, 0.1)
        .build();

// Create a numeric axis
final IAxis yAxisRight2 = sciChartBuilder.newNumericAxis()
        .withAxisTitle("Primary")
        .withAxisId("primaryYAxis")
        .withAxisAlignment(AxisAlignment.Right)
        .build();

// Create another numeric axis
final IAxis yAxisLeft2 = sciChartBuilder.newNumericAxis()
        .withAxisTitle("Secondary")
        .withAxisId("secondaryYAxis")
        .withAxisAlignment(AxisAlignment.Left)
        .withGrowBy(0.2, 0.2)
        .build();

// Add the Y axis to the YAxes collection of the surface
Collections.addAll(surface2.getYAxes(), yAxisLeft2, yAxisRight2);

// Add the X axis to the XAxes collection of the surface
Collections.addAll(surface2.getXAxes(), xAxis2);

With this addition, your application should now show a view with two charts on it:

 

Adding a Series to the Second Chart

Now we are ready to add a RenderableSeries to the second chart. For the versatility's sake, lets try out another RenderableSeries type, say, FastMountainRenderableSeries. We are going to attach an existing DataSeries to it so it appears scrolling just like the series on the first chart. Also, the new RenderableSeries is going to be attached to the right axis.

Remember, since there are two Y axes, they both must have unique IDs assigned to them. Those IDs can be used to register RenderableSeries and Annotations on a corresponding axis.

 So lets add these missing lines to the code:

Copy Code
// Create and configure an area series
final IRenderableSeries areaSeries = sciChartBuilder.newMountainSeries()
        .withDataSeries(scatterData)
        .withYAxisId("primaryYAxis")
        .withStrokeStyle(ColorUtil.LightBlue, 2f, true)
        .withAreaFillColor(ColorUtil.argb(ColorUtil.LightSteelBlue, 0.6f))
        .build();

// Add the area series to the RenderableSeries collection of the surface
Collections.addAll(surface2.getRenderableSeries(), areaSeries);

Now run the application again:

 

Synchronizing VisibleRanges on Axes

To make both charts showing same VisibleRanges on axes, you should just share the same IRange instance across the axes. In our case, there is no need to do this because both charts have the same data range. If it was different, we would need to synchronize VisibleRanges like follows:

Copy Code
// Create an IRange instance that will be shared across multiple charts
IRange sharedXRange = new DoubleRange(0d, 1d);

// Create an X axis and apply sharedXRange
final NumericAxis xAxis = sciChartBuilder.newNumericAxis()
        .withAxisTitle("X Axis Title")
        .withVisibleRange(sharedXRange)
        .build();

// Create another X axis and apply sharedXRange
final NumericAxis xAxis2 = sciChartBuilder.newNumericAxis()
        .withAxisTitle("X Axis Title")
        .withVisibleRange(sharedXRange)
        .build();

Synchronizing Chart Widths

Imagine a situation when you have a two charts with Y axes on opposite sides:

The bottom chart kind of sticks out to the left. In this situation, the most of people would like to be able to align the charts. In SciChart, a way to do this is to use a helper class called SciChartVerticalGroup for this. All you need to do is to add the SciChartSurfaces to it:

Copy Code
// Create a SciChartVerticalGroup
SciChartVerticalGroup verticalGroup = new SciChartVerticalGroup();

// Add two surfaces which widths are to be synchronized
verticalGroup.addSurfaceToGroup(surface);
verticalGroup.addSurfaceToGroup(surface2);

 And here it is:

Linking Cursor and Other Modifiers

The next thing we are going to do is to link chart modifiers. The first chart has an array of ChartModifiers set up to handle zooming, panning and tooltips. We are going to add some of these modifiers to the second chart:

Copy Code
// Create the second collection of chart modifiers
ModifierGroup chartModifiers2 = sciChartBuilder.newModifierGroup()
        .withZoomExtentsModifier().withReceiveHandledEvents(true).build()
        .withZoomPanModifier().withReceiveHandledEvents(true).build()
        .withPinchZoomModifier().withReceiveHandledEvents(true).build()
        .withCursorModifier().withReceiveHandledEvents(true).build()
        .build();

// Add the interactions to the ChartModifiers collection of the surface
Collections.addAll(surface2.getChartModifiers(), chartModifiers2);

If you run the applicaiton now, you will notice that you have zooming behaviour and tooltips on both charts, but the mouse events still aren't quite linked. You need to do a few more steps to fully link the charts together.

Sharing MotionEventGroup

We need to set MotionEventGroup on the ModifierGroups on both charts. Set this to a string value. It needs to be the same string on both charts to link the charts together.

Copy Code
// Create the second collection of chart modifiers
ModifierGroup chartModifiers2 = sciChartBuilder.newModifierGroup()
        // Setting MotionEventsGroup
        .withMotionEventsGroup("SharedMotionEvents")
        // Adding some modifiers
        .withZoomExtentsModifier().withReceiveHandledEvents(true).build()
        .withZoomPanModifier().withReceiveHandledEvents(true).build()
        .withPinchZoomModifier().withReceiveHandledEvents(true).build()
        .withCursorModifier().withReceiveHandledEvents(true).build()
        .build();
Hint! You can use a MouseEventGroup on more than two charts. You can also bind this value to a ViewModel property to make it dynamic.Type your Tip Box content here.

Run the application again. The Cursors and Tooltips are now synchronizing across the charts.

Further Reading

Our SciChart Android Examples Suite contains a couple of examples that show chart synchronization techniques. For instance, take a look at the Sync Multi Chart Example. You can download the full demo application from https://www.scichart.com/android-chart-examples/.

See Also