Question

I just found core-plot the other day and have been trying to implement it in our current app (ARC is used) for a real-time line graph with multiple data lines. However, I can't seem to get the graph to scroll to the right as new data points are found and added. Instead, I see the data lines travel off the right side of the graph. I've tried to follow the Plot Gallery app (Real Time Plot); to no avail. I've looked at the other questions here that resemble my problem, but none of the answers seems to be fixing my problem.

Here's what I'm doing:

I have a custom UIViewController attached to an object in my storyboard. Here is the relevant code:

@interface RealTimeSignalsViewController : UIViewController <CPTPlotDataSource, CPTLegendDelegate, MCPacketProtocol>
{
    //Key equals the ID of the line, value is MCPlotDataObject.
    NSMutableDictionary* plotData;
    BOOL newPacketReceived;
    CPTGraphHostingView* hostView;
}

.m

#define SIG_A_ID    @"SIG_A"
#define SIG_B_ID    @"SIG_B"

-(void)viewWillAppear:(BOOL)animated
{
    newPacketReceived = NO;
    NSString* curDev = [[Model instance] cur_deviceSpeakingWith];
    plotData = [[NSMutableDictionary alloc] init];
    [plotData setObject:[[MCPlotDataObject alloc] initWithID:[NSString stringWithFormat:SIG_A_ID]] forKey:[NSString stringWithFormat:SIG_A_ID]];
    //If a second channel is detected
    {
        [plotData setObject:[[MCPlotDataObject alloc] initWithID:[NSString stringWithFormat:SIG_B_ID]] forKey:[NSString stringWithFormat:SIG_B_ID]];
    }
    [self configureHost];
    [self configureGraph];
    [self configurePlots];
}

- (void)viewDidLoad
{
    [[DeviceHandler sharedInstance] addPacketDelegate:self];
    [self checkForNewPackets];
    [super viewDidLoad];
    // Do any additional setup after loading the view.
}

-(void)configureHost
{
    CGRect hvRect = [[self view] frame];
    hvRect.size.height -= 150;
    hvRect.size.width -= 50;
    hvRect.origin.x += 25;
    hvRect.origin.y += 75;
    hostView = [(CPTGraphHostingView *) [CPTGraphHostingView alloc] initWithFrame:hvRect];
    [[self view] addSubview:hostView];
}

-(void)configureGraph
{
    CPTGraph* graph = [[CPTXYGraph alloc] initWithFrame:[hostView bounds]];
    [graph applyTheme:[CPTTheme themeNamed:kCPTSlateTheme]];
    [hostView setHostedGraph:graph];

    graph.plotAreaFrame.paddingBottom = 40;
    graph.plotAreaFrame.paddingLeft = 40;
    graph.plotAreaFrame.paddingRight = 40;
    graph.plotAreaFrame.paddingTop = 40;
    CPTXYPlotSpace* space = (CPTXYPlotSpace*)[graph defaultPlotSpace];

    CPTMutableLineStyle *majorGridLineStyle = [CPTMutableLineStyle lineStyle];
    [majorGridLineStyle setLineWidth:0.75];
    [majorGridLineStyle setLineColor:[[CPTColor colorWithGenericGray:0.2] colorWithAlphaComponent:0.75]];

    CPTMutableLineStyle *minorGridLineStyle = [CPTMutableLineStyle lineStyle];
    [minorGridLineStyle setLineWidth:0.25];
    [minorGridLineStyle setLineColor:[[CPTColor whiteColor] colorWithAlphaComponent:0.1]];

    // Axes
    // X axis
    CPTXYAxisSet* axisSet = (CPTXYAxisSet *)[graph axisSet];
    CPTXYAxis* x = [axisSet xAxis];
    [x setLabelingPolicy:CPTAxisLabelingPolicyNone];
    [x setMajorGridLineStyle:majorGridLineStyle];
    [x setMinorGridLineStyle:minorGridLineStyle];
    [x setPlotSpace:[graph defaultPlotSpace]];

    // Y axis
    NSSet *majorTickLocations = [NSSet setWithObjects:[NSDecimalNumber zero],
                             [NSDecimalNumber numberWithUnsignedInteger:10],
                             [NSDecimalNumber numberWithUnsignedInteger:20],
                             [NSDecimalNumber numberWithUnsignedInteger:30],
                             [NSDecimalNumber numberWithUnsignedInteger:40],
                             [NSDecimalNumber numberWithUnsignedInteger:50],
                             [NSDecimalNumber numberWithUnsignedInteger:60],
                             [NSDecimalNumber numberWithUnsignedInteger:70],
                             [NSDecimalNumber numberWithUnsignedInteger:80],
                             [NSDecimalNumber numberWithUnsignedInteger:90],
                             [NSDecimalNumber numberWithUnsignedInteger:100],
                             nil];
    CPTXYAxis *y = [axisSet yAxis];
    [y setPlotSpace:[graph defaultPlotSpace]];
    [y setLabelingPolicy:CPTAxisLabelingPolicyNone];
    [y setOrthogonalCoordinateDecimal:CPTDecimalFromUnsignedInteger(0)];
    [y setMajorGridLineStyle:majorGridLineStyle];
    [y setMinorGridLineStyle:minorGridLineStyle];
    [y setMinorTicksPerInterval:4];
    [y setLabelOffset:5.0];
    [y setMajorTickLocations:majorTickLocations];
    [y setAxisConstraints:[CPTConstraints constraintWithLowerOffset:0.0]];

    CPTMutableTextStyle *axisTitleTextStyle = [CPTMutableTextStyle textStyle];
    [axisTitleTextStyle setColor:[CPTColor blackColor]];
    axisTitleTextStyle.fontName = @"Helvetica-Bold";
    axisTitleTextStyle.fontSize = 14.0;
    NSMutableSet *newAxisLabels = [NSMutableSet set];
    for(id n in majorTickLocations)
    {
        CPTAxisLabel *newLabel = [[CPTAxisLabel alloc] initWithText:[NSString stringWithFormat:@"%lu", (unsigned long)[n unsignedIntegerValue]] textStyle:axisTitleTextStyle];

        NSDecimal loc = CPTDecimalFromUnsignedInt([n unsignedIntegerValue]);        [newLabel setTickLocation:loc];
        [newLabel setOffset:[y labelOffset] + [y majorTickLength] / 2.0];
        if(newLabel)
            [newAxisLabels addObject:newLabel];
    }
    [y setAxisLabels:newAxisLabels];
    [space setYRange:[CPTPlotRange plotRangeWithLocation:CPTDecimalFromUnsignedInt(0) length:CPTDecimalFromUnsignedInt(100)]];
    space.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromUnsignedInteger(0) length:CPTDecimalFromUnsignedInteger([MCPlotDataObject getMaxDataPoints] - 2)];
}

-(void)configurePlots
{
    CPTGraph* graph = [hostView hostedGraph];

    //Keep track of the colors used for data lines

    NSMutableArray* unusedKeys = [[NSMutableArray alloc] init];
    for(NSString* keyID in plotData)
    {
        CPTScatterPlot* plot = (CPTScatterPlot*)[graph plotWithIdentifier:keyID];
        if(plot)
        {
            //Mark off color used
        }
        else
        {
            [unusedKeys addObject:keyID];
        }
    }

    for(NSString* keyID in unusedKeys)
    {
        CPTScatterPlot* linePlot = [[CPTScatterPlot alloc] init];
        [linePlot setIdentifier:keyID];
        [linePlot setCachePrecision:CPTPlotCachePrecisionDouble];

        CPTMutableLineStyle *lineStyle = [linePlot.dataLineStyle mutableCopy];
        [lineStyle setLineWidth:1.0];

        //Assign a unique color
        [linePlot setDataLineStyle:lineStyle];
        [linePlot setDataSource:self];
        [graph addPlot:linePlot];
    }

    CPTLegend* legend = [CPTLegend legendWithGraph:graph];
    [legend setFill:[[graph plotAreaFrame] fill]];
    [legend setBorderLineStyle:[[graph plotAreaFrame] borderLineStyle]];
    [legend setCornerRadius:5.0];
    [legend setSwatchSize:CGSizeMake(25.0, 25.0)];
    [legend setSwatchCornerRadius:5.0];

    [graph setLegendAnchor:CPTRectAnchorBottom];
    [graph setLegendDisplacement:CGPointMake(0.0, 0.0)];
    [graph setLegend:legend];
    [[graph legend] setDelegate:self];
}

-(void)checkForNewPackets
{
    if(newPacketReceived)
    {
        [self updateGraph];
        newPacketReceived = false;
    }
    [self performSelector:@selector(checkForNewPackets) withObject:nil afterDelay:.01];
}

-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
{
    NSString* plotID = (NSString*)[plot identifier];
    return [[(MCPlotDataObject*)[plotData objectForKey:plotID] points] count];
}

-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
    NSNumber *num = nil;

    NSString* plotID = (NSString*)[plot identifier];
    MCPlotDataObject* mcpd = (MCPlotDataObject*)[plotData objectForKey:plotID];
    switch(fieldEnum)
    {
        case CPTScatterPlotFieldX:
            num = [NSNumber numberWithUnsignedInteger:index + [mcpd currrentIndex] - [[mcpd points] count]];
            break;
        case CPTScatterPlotFieldY:
            num = [[mcpd points] objectAtIndex:index];
            break;
        default:
            break;
    }

    return num;
}

-(void)updateGraph
{
    CPTGraph* graph = [hostView hostedGraph];
    CPTXYPlotSpace* plotSpace = (CPTXYPlotSpace*)[graph defaultPlotSpace];
    NSUInteger curIndex = 0;
    NSUInteger maxPoints = [MCPlotDataObject getMaxDataPoints];
    NSUInteger location = 0;

    //Delete the unnecessary points from each plot line, get the curIndex, maxPoints, and location using the first keyID
    for(NSString* keyID in plotData)
    {
        MCPlotDataObject* mcpd = (MCPlotDataObject*)[plotData objectForKey:keyID];
        CPTPlot* linePlot = [graph plotWithIdentifier:keyID];
        if([mcpd removePlotPoint])
        {
             [linePlot deleteDataInIndexRange:NSMakeRange(0, 1)];
        }

        if(curIndex == 0)
        {
             curIndex = [mcpd currrentIndex];
             location = (curIndex >= maxPoints ? curIndex - maxPoints + 2 : 0 );
        }
    }

    //Animate based on the found location/max points
    CPTPlotRange *newRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromUnsignedInteger(location) length:CPTDecimalFromUnsignedInteger(maxPoints - 2)];


    [CPTAnimation animate:plotSpace property:@"xRange" fromPlotRange:plotSpace.xRange toPlotRange:newRange duration:CPTFloat(0.01f) animationCurve:CPTAnimationCurveDefault delegate:nil];
    NSLog(@"Old range:%@\nNew range:%@", plotSpace.xRange, newRange);

    //Get the newest data
    [self updateUIToPacketDataForDevice:[[Model instance] cur_deviceSpeakingWith]];
    //Add it to the plot
    for(NSString* keyID in plotData)
    {
        MCPlotDataObject* mcpd = (MCPlotDataObject*)[plotData objectForKey:keyID];
        CPTPlot* linePlot = [graph plotWithIdentifier:keyID];
        [linePlot insertDataAtIndex:[[mcpd points] count] - 1 numberOfRecords:1];
    }
}

-(void)updateToNewPacketDataForDevice:(NSString *)deviceName
{
    //Gets called from a different thread, so notify object it can update data and animations will happen on main thread.
    newPacketReceived = YES;
}

-(void)updateUIToPacketDataForDevice:(NSString*)deviceName
{
    //Get latest packet data
}

And the corresponding object holding onto the data points and the index, MCPlotDataObject.h

@interface MCPlotDataObject : NSObject
{
     NSUInteger numPointsRemoved;
}

@property (readonly) NSUInteger currrentIndex;
@property (readonly) NSMutableArray* points;
@property (readonly) NSString* ID;

/** 
  This function adds a plot point for a plot line and then increments the currentIndex.
  @param newPlotPoint The new NSNumber value for the line to plot and follow.
 */
-(void)addPlotPoint:(NSNumber*)newPlotPoint;

/**
 This function attempts to remove a plot point.  It will return YES if [points count] >= maxDataPoints.
 @return NO if no points were removed
 @return OR
 @return YES if a plot point was removed.
 */
-(BOOL)removePlotPoint;

/**
 This function returns the max data points value that each MCPDO has.
 @return The maximum number of data points that are allowed in points array.
 */
+(NSUInteger)getMaxDataPoints;

@end

And corresponding .m

static const NSUInteger kMaxDataPoints = 102;

-(void)addPlotPoint:(NSNumber *)newPlotPoint
{
    currrentIndex++;
    [points addObject:newPlotPoint];
}

-(BOOL)removePlotPoint
{
    if([points count] >= kMaxDataPoints)
    {
        [points removeObjectAtIndex:0];
        numPointsRemoved++;
        return YES;
    }
    else
        return NO;
}

+(NSUInteger)getMaxDataPoints
{
    return kMaxDataPoints;
}

Can anyone point me in the right direction as to why the plotSpace in updateGraph() is not being moved?

Was it helpful?

Solution

After a day of debugging this, I found out that my problem was the animation duration. 1/100 second was too quick for it. When I went to 1/25 second like what was in the Plot_Gallery example project, the graph scrolled.

Edit:

After a little more debugging, 60 fps seems to be the maximum.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top