iOS Charting Documentation - SciChart
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.
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're working straight from these:
Tutorial 07 - Adding Annotations
Tutorial 08 - Adding Multiple Axis
Or, if you're in a hurry, skim through the tutorials and copy paste the code into a new project.

Tutorials Repository

As it was mentioned previously - we've created Git repository with all Tutorials. So you can clone/download the appropriate project you need!

Once you've done that, we are ready to get started.

Adding a Second SciChartSurface

There is no restriction on the number of SciChartSurfaces you can have in an application. In this tutorial we are going to add a second SciChartSurface.
Let's start by adding a second SciChartSurface:

Second chart properties
Copy Code
import UIKit
import SciChart
class ViewController: UIViewController {
    
    var sciChartSurfaceTop: SCIChartSurface?
    var sciChartSurfaceBottom: SCIChartSurface?
    ...

Next, let's initialize these properties and layout our surface views in ViewController's view just like in the following code. After all, our viewDidLoad method should have following view:

Initializing chart surfaces and views
Copy Code
override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view, typically from a nib.
        sciChartSurfaceTop = SCIChartSurface()
        sciChartSurfaceTop?.translatesAutoresizingMaskIntoConstraints = false
        sciChartSurfaceBottom = SCIChartSurface()
        sciChartSurfaceBottom?.translatesAutoresizingMaskIntoConstraints = false
        
        self.view.addSubview(sciChartSurfaceTop!)
        self.view.addSubview(sciChartSurfaceBottom!)
        
        let layoutDictionary = ["SciChart1" : sciChartSurfaceTop!, "SciChart2" : sciChartSurfaceBottom!]
        
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|-(0)-[SciChart1]-(0)-|",
                                                                options: NSLayoutFormatOptions(),
                                                                metrics: nil,
                                                                views: layoutDictionary))
        
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "|-(0)-[SciChart2]-(0)-|",
                                                                options: NSLayoutFormatOptions(),
                                                                metrics: nil,
                                                                views: layoutDictionary))
        
        self.view.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|-(0)-[SciChart1(SciChart2)]-(0)-[SciChart2(SciChart1)]-(0)-|",
                                                                options: NSLayoutFormatOptions(),
                                                                metrics: nil,
                                                                views: layoutDictionary))
        
        
        
        addAxes(surface: sciChartSurfaceTop!)
        addAxes(surface: sciChartSurfaceBottom!)
        
        createDataSeries()
        createRenderableSeries()
        addModifiers()
        
        // set chartSurface's annotation property to annotationGroup
        sciChartSurfaceTop?.annotations = annotationGroup
    }

You probably noticed AddAxes() method - to make code a little bit easier to read - we moved adding axes logic to that method:

addAxes() method
Copy Code
    func addAxes(surface: SCIChartSurface){
        let xAxis = SCINumericAxis()
        xAxis.growBy = SCIDoubleRange(min: SCIGeneric(0.1), max: SCIGeneric(0.1))
        surface.xAxes.add(xAxis)
        
        // adding some paddding for Y axis
        let yAxis = SCINumericAxis()
        yAxis.growBy = SCIDoubleRange(min: SCIGeneric(0.1), max: SCIGeneric(0.1))
        yAxis.axisId = "firstYAxis"
        surface.yAxes.add(yAxis)
        
        let yLeftAxis = SCINumericAxis()
        yLeftAxis.axisId = "secondaryYAxis"
        yLeftAxis.axisAlignment = .left
        yLeftAxis.visibleRange = SCIDoubleRange(min: SCIGeneric(-2), max: SCIGeneric(2))
        yLeftAxis.autoRange = .never
        surface.yAxes.add(yLeftAxis)
    }

If you run the application at this point, you will see our first Realtime chart is displayed at the top of the application, and the second chart 'chartSurfaceBottom' is static, and displayed underneath.

Adding Series to the Second Chart

The second chart needs a series, so we are going to add a Mountain Series. Add the following series to the chart in createRenderableSeries() method :

createRenderableSeries() method
Copy Code
    func createRenderableSeries(){
        lineRenderableSeries = SCIFastLineRenderableSeries()
        lineRenderableSeries.dataSeries = lineDataSeries
        lineRenderableSeries.yAxisId = "firstYAxis"
        
        scatterRenderableSeries = SCIXyScatterRenderableSeries()
        scatterRenderableSeries.dataSeries = scatterDataSeries
        scatterRenderableSeries.yAxisId = "secondaryYAxis"
        
        mountainRenderableSeries = SCIFastMountainRenderableSeries()
        mountainRenderableSeries.dataSeries = lineDataSeries
        mountainRenderableSeries.yAxisId = "firstYAxis"
        
        sciChartSurfaceTop?.renderableSeries.add(lineRenderableSeries)
        sciChartSurfaceTop?.renderableSeries.add(scatterRenderableSeries)
        sciChartSurfaceBottom?.renderableSeries.add(mountainRenderableSeries)
    }

Now run the application. It doesn't look right, does it? The top chart is scrolling but the bottom chart is not scrolling.

There is a trick to linking the two charts and it is updating the VisibleRanges of the two XAxis together. We are going to do this below.

Add updating X axis for both charts in updatingDataPoints() method:

updatingDataPoints() method
Copy Code
    
    func updatingDataPoints(timer:Timer){
        
        i += 1
        
        // appending new data points into the line and scatter data series
        lineDataSeries.appendX(SCIGeneric(i), y: SCIGeneric(sin(Double(i)*0.1 + phase)))
        scatterDataSeries.appendX(SCIGeneric(i), y: SCIGeneric(cos(Double(i)*0.1 + phase)))
        
        let xAxis = sciChartSurfaceTop!.xAxes.item(at: 0).visibleRange as! SCIDoubleRange
        xAxis.min = SCIGeneric( SCIGenericDouble(xAxis.min) + 1.0)
        xAxis.max = SCIGeneric( SCIGenericDouble(xAxis.max) + 1.0)
        
        let xAxisBottom = sciChartSurfaceBottom!.xAxes.item(at: 0).visibleRange as! SCIDoubleRange
        xAxisBottom.min = SCIGeneric( SCIGenericDouble(xAxisBottom.min) + 1.0)
        xAxisBottom.max = SCIGeneric( SCIGenericDouble(xAxisBottom.max) + 1.0)
        
        phase += 0.01

Now run the example again. That's better!

Syncing charts

The next thing we are going to do is to synchronise both charts. We would need one additional property:
SCIMultiSurfaceModifier property
Copy Code
class ViewController: UIViewController {
   
    var szem: SCIMultiSurfaceModifier!
    ...

As well as its initialization:

SCIMultiSurfaceModifier property initialization
Copy Code
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Do any additional setup after loading the view, typically from a nib.
        sciChartSurfaceTop = SCIChartSurface()
        sciChartSurfaceTop?.translatesAutoresizingMaskIntoConstraints = false
        sciChartSurfaceBottom = SCIChartSurface()
        sciChartSurfaceBottom?.translatesAutoresizingMaskIntoConstraints = false
        
        szem = SCIMultiSurfaceModifier(modifierType: SCIZoomExtentsModifier.self)
        
        self.view.addSubview(sciChartSurfaceTop!)

        ...

And finally, we have to update the method where we add chart modifiers:

updated addModifiers()
Copy Code
    func addModifiers(){
        
        let xDragModifierSync : SCIMultiSurfaceModifier = SCIMultiSurfaceModifier(modifierType: SCIXAxisDragModifier.self)
        let pinchZoomModifierSync : SCIMultiSurfaceModifier = SCIMultiSurfaceModifier(modifierType: SCIPinchZoomModifier.self)
        let panZoomModifierSync : SCIMultiSurfaceModifier = SCIMultiSurfaceModifier(modifierType: SCIZoomPanModifier.self)
        let xAxisDragmodifier : SCIXAxisDragModifier = xDragModifierSync.modifier(forSurface: self.sciChartSurfaceTop) as! SCIXAxisDragModifier
        xAxisDragmodifier.modifierName = "xAxisDragModifierName"
        xAxisDragmodifier.dragMode = .scale
        xAxisDragmodifier.clipModeX = .none
        let pinchZoomModifier : SCIPinchZoomModifier = pinchZoomModifierSync.modifier(forSurface: self.sciChartSurfaceTop) as! SCIPinchZoomModifier
        pinchZoomModifier.modifierName = "pinchZoomModifierName"
        
        let groupModifier = SCIChartModifierCollection(childModifiers: [xDragModifierSync, pinchZoomModifierSync, self.szem!, panZoomModifierSync])
        self.sciChartSurfaceTop?.chartModifiers = groupModifier
        
        let xAxisDragmodifier2  : SCIXAxisDragModifier = xDragModifierSync.modifier(forSurface: self.sciChartSurfaceBottom) as! SCIXAxisDragModifier
        xAxisDragmodifier2.modifierName = "xAxisDragModifierName2"
        xAxisDragmodifier2.dragMode = .scale
        xAxisDragmodifier2.clipModeX = .none
        let pinchZoomModifier2 : SCIPinchZoomModifier = pinchZoomModifierSync.modifier(forSurface: self.sciChartSurfaceBottom) as! SCIPinchZoomModifier
        pinchZoomModifier2.modifierName = "pinchZoomModifierName2"
        let groupModifier2 = SCIChartModifierCollection(childModifiers: [xDragModifierSync, pinchZoomModifierSync, self.szem!, panZoomModifierSync])
        self.sciChartSurfaceBottom?.chartModifiers = groupModifier2
        sciChartSurfaceTop?.chartModifiers = groupModifier
        sciChartSurfaceBottom?.chartModifiers = groupModifier
    }