Quick Start Guide > Tutorials (Java Android) > Tutorial 09 - Linking Multiple Charts
Tutorial 09 - Linking Multiple Charts

The previous tutorial is: Tutorial 08 - Adding Multiple Axis

In these tutorials, so far we have added a chart with two YAxis, one XAxis, two series, tooltips, legends, zooming, panning, and annotations.

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

Source code for this tutorial can be found at our github.

Adding Multiple Charts

So far our code looks like this:

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 repeat the same procedure to configure the second chart, with some differences.

We will leave off annotations and modifiers . 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)

Your application should now show a view with two charts:

 

Adding a Series to the Second Chart

Now we are ready to add a RenderableSeries to the second chart. To try something new, lets t another RenderableSeries type, FastMountainRenderableSeries.

We are going to attach an existing DataSeries so it scrolls just like the series on the first chart. Also, we will attach the RenderableSeries 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.

Add these lines of 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 show the same VisibleRanges on both axes, you share the same IRange instance across the axes.

But in this case, there is no need to do that because both charts use the same data.

If it was different, we would need to synchronize VisibleRanges like this:

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 sticks out to the left because there is no second Y Axis. So we use the helper class SciChartVerticalGroup to line them up:

Copy Code
    // Create a SciChartVerticalGroup
    SciChartVerticalGroup verticalGroup = new SciChartVerticalGroup();
   
    // Add two surfaces which widths are to be synchronized verticalGroup.addSurfaceToGroup(surface);
    verticalGroup.addSurfaceToGroup(surface2);

 Results in this:

Linking Cursor and Other Modifiers

Next we are going 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 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.

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