I'm trying to implement a real-time scatter plot using CorePlot 1.0 on an iPad with iOS 5.1. Things are working well with a couple of issues and one major exception - axis redraw.
When enough data is collected, I adjust the range in the plotSpace thus:
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.graph.defaultPlotSpace;
plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(self.graphMinX)
length:CPTDecimalFromFloat(self.graphRangeX)];
When I do this, the plots on the graph adjust as if the axis had changed, but the axis doesn't adjust - so the plots of the data display correctly against an incorrect axis. The axis will correctly update 5 seconds after I stop the data source.
I have reviewed the code in RealTimePlot (RTP) from the CorePlot iOS Plot Gallery and I can't find any significant difference (though one surely exists).
One difference between my code and RTP:
I capture the new data in a background GCD queue, which is then "distributed" by attaching it to a custom notification in [NSNotificationCenter defaultCenter]
Update: A simplified view of the architecture hierarchy is something like this:
- SplitViewController
- DetailViewController
TreatmentGraph
object (managing theCPTXYGraph
)
- [Collection of]
TreatmentChannel
objects (each managing aCPTXYPlot
)
- [Collection of]
The DetailViewController
has an observer for the data notification which looks like this:
- (void)dataArrived:(NSNotification *)notification
{
FVMonitoredSignal *sig = [notification object];
NSValue *currValue = [sig.dataPoints lastObject];
CGPoint point = [currValue CGPointValue];
[self.treatmentGraph addPoint:point toChannelWithIdentifier:sig.signalName];
dispatch_async(dispatch_get_main_queue(), ^{
[self.graphHostingView.hostedGraph reloadData];
});
return;
}
(Note that I force a reload of the data using a GCD post to the UI queue - the example in RTP did not seem to require this) This is a red flag, but of what?
Within the TreatmentGraph
we check whether a X Axis adjustment is needed and the data is dispatched to the appropriate TreatmentChannel
.
- (void)addPoint:(CGPoint)point toChannelWithIdentifier:(NSString *)identifier
{
// Check for a graph shift
if (point.x >= (self.graphMinX + self.graphRangeX))
{
[self shiftGraphX];
}
FVTreatmentChannel *channel = [self.channels objectForKey:identifier];
[channel addPoint:point];
return;
}
- (void)shiftGraphX
{
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.graph.defaultPlotSpace;
plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(self.graphMinX) length:CPTDecimalFromFloat(self.graphRangeX)];
}
My guess is the axis isn't updated until the main queue is idle, but since I'm already forcing a reload when new data arrives, I'm perplexed why the axis redraw doesn't happen then.
The TreatmentChannel
accepts the new data like this:
- (void)addPoint:(CGPoint)point
{
[self.plotData addObject:[NSValue valueWithCGPoint:point]]; // cache it
[self.plot insertDataAtIndex:self.plotData.count-1 numberOfRecords:1];
[self.plot reloadData];
}
Note that I'm using -insertDataAtIndex:numberOfRecords:
to add just the new data and calling -reloadData
specifically on the CPTXYPlot
. This is not causing a display update - it's not until the -reloadData
is called in the data notification handler in the DetailViewController
that I get a display update.
Questions:
- What can I do to cause my axis to update in a more timely manner?
- Any clues why I don't get plots on my graph unless I force a reload when data arrives?
Item 1 was addressed by making sure any updates to the axis and/or plotspace were wrapped to put them back on the GCD main queue.
Item 2 was addressed by wrapping the calls to -insertDataAtIndex:numberOfRecords:
allowed the removal of many of the -reloadData
calls that were bothering me.
Moral of the story: consider interaction with CorePlot equivalent to UIKit calls - in terms of making sure they all happen on the main queue.