Search code examples
xcodeswiftplotreal-timecore-plot

Plotting multiple scatter lines in different rate?


I used CorePlot to implement real-time plotting but I have two different data in different sampling rate.

My ECG sampling rate is 250Hz and Resp sampling rate is 50Hz. I want to do the real-time plotting in the same graph.

At first, I tried to modify the array in different count, but when it goes to numberForPlot function, the error happened. It seems like the recordIndex should be the same count.

How should I do? Should I modify the CorePlot method? Or just modify my code?

Thank you!

func plotChartTest(ecg:[Int], resp:[Int], packNum: Int){
    if (packNum == 1){

        ...

        self.newGraph.addPlot(respLinePlot)
        self.newGraph.addPlot(ecgLinePlot)

        // Add some initial data

        for i in 0 ..< tempEcgArray.count {
            let x = Double(i) * 0.01
            let y = 0.0
            //let y = Double(fecg[i])/65535.0 + 0.05
            let dataPoint: plotDataType = [.X: x, .Y:y]
            self.ecgDataForPlot.append(dataPoint)
            //ecgContentArray.append(dataPoint)
        }

        //self.ecgDataForPlot = ecgContentArray

        for i in 0 ..< tempRespArray.count {
            let x = Double(i) * 0.01
            let y = 0.0
            let dataPoint: plotDataType = [.X: x, .Y: y]
            self.respDataForPlot.append(dataPoint)
        }
        // self.respDataForPlot = respContentArray
    }

    if (currentIndex % 50 == 0){
        let i = 0
        respDataForPlot.removeAtIndex(currentIndex+i)
        respDataForPlot.insert([.X: Double(currentIndex+i)*0.01, .Y: Double(resp[i])/65535.0], atIndex: currentIndex+i)
        respLinePlot.deleteDataInIndexRange(NSMakeRange(currentIndex+i, 1))
        respLinePlot.insertDataAtIndex(UInt(currentIndex+i), numberOfRecords: 1)
    }

    for i in 0 ..< 5{
        ecgDataForPlot.removeAtIndex(currentIndex+i)
        ecgDataForPlot.insert([.X: Double(currentIndex+i)*0.01, .Y: Double(ecg[i])/65535.0], atIndex: currentIndex+i)
        ecgLinePlot.deleteDataInIndexRange(NSMakeRange(currentIndex+i, 1))
        ecgLinePlot.insertDataAtIndex(UInt(currentIndex+i), numberOfRecords: 1)
    }
    currentIndex = currentIndex + 5
}


// MARK: - Plot Data Source Methods
//Set the number of data points the plot has
func numberOfRecordsForPlot(plot: CPTPlot) -> UInt
{
    let plotID = plot.identifier as! String
    if (plotID == "ECG"){
        return UInt(self.ecgDataForPlot.count)
    }
    else if (plotID == "RESP"){
        return UInt(self.respDataForPlot.count)
    }
    return 0
}

//Returns the data point for each plot by using the plot identifier set above
func numberForPlot(plot: CPTPlot, field: UInt, recordIndex: UInt) -> AnyObject?
{
    let plotField = CPTScatterPlotField(rawValue: Int(field))
    let plotID    = plot.identifier as! String
    print("ID: \(plotID), Index: \(recordIndex)")
    if let respNum = self.respDataForPlot[Int(recordIndex)][plotField!], let ecgNum = self.ecgDataForPlot[Int(recordIndex)][plotField!]{
        if (plotField! == .Y) && (plotID == "RESP") {
            return respNum as NSNumber
        }
        else {
            return ecgNum as NSNumber
        }
    }
    else {
        return nil
    }
}

Solution

  • In the datasource numberForPlot() function, only unwrap the datapoint for a single plot. Since the data arrays have different lengths, the lookup will fail for the shorter array when plotting the longer one.

    if (plotID == "ECG") {
        if let ecgNum = self.ecgDataForPlot[Int(recordIndex)][plotField!] {
            return ecgNum as NSNumber
        }
        else {
            return 0 as NSNumber
        }
    }
    else if (plotID == "RESP") {
        if let respNum = self.respDataForPlot[Int(recordIndex)][plotField!] {
            return respNum as NSNumber
        }
        else {
            return 0 as NSNumber
        }
    }
    else {
        return 0 as NSNumber
    }