Realtime Graph choppy

821 views
Skip to first unread message

chrislhardin

unread,
Apr 17, 2011, 9:11:09 AM4/17/11
to coreplot-discuss

I'm trying to use Core-plot to display a realtime graph. I get data
rather frequently...200 datapoints every second. I've tried several
combinations of adding and deleting the data from the datasource. I've
tried not calling reload graph and just calling insert and delete to
add datapoints to the end and remove them from the beginning, but the
animation is still a bit choppy. I want to use Core-Plot for this
project, but it seems that the frequency of my updates is a little too
much for Coreplot to handle.

Also, I also noticed that I didn't get much of a different between
calling insert/delete on the graph as opposed to just calling
reloadData.

Any advice anyone can give would be greatly appreciated. Would I be
better off just using a EAGLView and animating it instead of trying to
use Coreplot for this type/frequency of data?

Also, can someone advise me on how I can simply clear a the presently
painted graph. I don't want to empty my array datasource and call
reload data in order to clear it.

I can post some of my code if it helps, but all I am doing is adding
data to the datasource array as it comes in and calling either reload
graph or insert/delete. I really want to make it look smooth like it
is rolling across the screen.

This is for an EKG type application is anyone is wondering.... lots of
datapoints...really really fast.

I appreciate any advice and really think Core-Plot is awesome....

Drew McCormack

unread,
Apr 17, 2011, 11:15:39 AM4/17/11
to coreplot...@googlegroups.com
Hi,

It does sound like you require very high refresh rates.
The bottleneck is likely to simply be the drawing. Each time you update the data, the whole plot is redrawn. This is probably why insert/delete doesn't improve much over reload data: the data loading is a small part of the total time. The quartz drawing is the slow part.
So, it may be that doing the drawing yourself in your own view may not be that much faster. If Quartz is the bottleneck, that is.
To make a very high performance scope-like view, you probably have to move away from the quartz vector drawing, and just go to the bitmap level, drawing paths yourself.
Or, if you do use quartz, you may be better off drawing the path in individual line segments, rather than what Core Plot does, which is generate a complete path for the plot before rendering it.
A few other things to consider: fills are expensive, so if you are using a fill, you probably should turn that off.
Also, it may be worth experimenting with reducing the refresh rate of data reloading. This may actually improve rendering performance because it won't be overwhelmed.
In other words, don't update every time new data is available, but instead at some fixed interval of time (a few times a second).
To clear the drawing, just set a flag in your data source (eg BOOL clearDrawing), and in your numberOfRecords... data source method, return 0.

Drew

> --
> You received this message because you are subscribed to the Google Groups "coreplot-discuss" group.
> To post to this group, send email to coreplot...@googlegroups.com.
> To unsubscribe from this group, send email to coreplot-discu...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/coreplot-discuss?hl=en.
>

Chris Hardin

unread,
Apr 17, 2011, 12:29:19 PM4/17/11
to coreplot...@googlegroups.com
Thanks Drew.. I appreciate the feedback immensely...

One other thing... I abandoned the insert/delete strategy in favor of just calling reloadData on the graph periodically. I couldn't make the inserts and deletes work properly, but I think I am doing it wrong..

-(void)pointReceived:(id)point{


[self.snapshot enqueue:point]; //Add a point to the end of our array

[dataSourceLinePlot  insertDataAtIndex:self.snapshot.count -1 numberOfRecords:1];

if ([self.snapshot count] > MAX_POINTS){

   

[dataSourceLinePlot deleteDataInIndexRange:NSMakeRange (0, 1)];

[self.snapshot removeLastObject];

}}




What i am attempting to do here is roll old points off the end and add new points to the beginning... The net effect it appears that the new points are getting drawn off screen or something and the graph doesn't slide with the updates. 

Eric

unread,
Apr 17, 2011, 4:55:25 PM4/17/11
to coreplot-discuss
You probably need to update the plot range to make the graph scroll
with the new data.

Eric

On Apr 17, 12:29 pm, Chris Hardin <chrislhar...@gmail.com> wrote:
> Thanks Drew.. I appreciate the feedback immensely...
>
> One other thing... I abandoned the insert/delete strategy in favor of just
> calling reloadData on the graph periodically. I couldn't make the inserts
> and deletes work properly, but I think I am doing it wrong..
>
>    -(void)pointReceived:(id)point{
>
>    [self.snapshot enqueue:point]; //Add a point to the end of our array
>
> [dataSourceLinePlot  insertDataAtIndex:self.snapshot.count -1
> numberOfRecords:1];
>
>     if ([self.snapshot count] > MAX_POINTS){
>
>   [dataSourceLinePlot deleteDataInIndexRange:NSMakeRange (0, 1)];
>
>  [self.snapshot removeLastObject];
>
>  }}
>
> What i am attempting to do here is roll old points off the end and add new
> points to the beginning... The net effect it appears that the new points are
> getting drawn off screen or something and the graph doesn't slide with the
> updates.
>

Chris Hardin

unread,
Apr 17, 2011, 7:07:33 PM4/17/11
to coreplot...@googlegroups.com
I have a reconfigure method that reconfigures the plot range... I've tried 100 different ways today to get the insert and delete to work to no avail. Does anyone have a working example of rolling points off as you insert points at the same time.... Ideally, I want to start removing 100 points from the end of the graph as I add 100 to the beginning. An add method would have been easier than an insert as I continually have issues with indexes shifting... Below is the code I am using... parson the messy nature of it. Note that [self reconfigure]; does the range.


-(void)pointReceived:(id)point{

[self.snapshot addObject:point]; //Add new point to the end of the array

if (waitBuffer > MAX_POINTS /2) {

  [dataSourceLinePlot insertDataAtIndex:self.snapshot.count-101 numberOfRecords:100]; //tell the Graph to reload said point

  [dataSourceLinePlot deleteDataInIndexRange:NSMakeRange (0, 100)];//Tell the graph to delete said point

  [self reconfigure];

waitBuffer = 0;

}

if (self.snapshot.count > MAX_POINTS)

    [self.snapshot dequeue]; //remove the First Item in the Array

 

waitBuffer++;

 

 

 

 


-(void)reconfigure

{

 

if(!graph)

    {

    graph = [[CPXYGraph alloc] initWithFrame: self.view.bounds];

//self.view = [[CPGraphHostingView alloc]initWithFrame:[UIScreen mainScreen].bounds];

//self.graphHost = (CPGraphHostingView *)self.view;

self.graphHost.collapsesLayers = NO;

self.graphHost.hostedGraph = graph;

graph.delegate = self;   

        CPTheme *theme = [CPTheme themeNamed:kCPDarkGradientTheme];

 

        [graph applyTheme:theme];

       // graph.paddingTop = 30.0;

        //graph.paddingBottom = 30.0;

        //graph.paddingLeft = 50.0;

        //graph.paddingRight = 50.0;

        dataSourceLinePlot = [[[CPScatterPlot alloc] initWithFrame:graph.bounds] autorelease];

        dataSourceLinePlot.identifier = @"Data Source Plot";

        

        CPMutableLineStyle *lineStyle = [[dataSourceLinePlot.dataLineStyle mutableCopy] autorelease];

        lineStyle.lineWidth = 1.f;

        lineStyle.lineColor = [CPColor greenColor];

        dataSourceLinePlot.dataLineStyle = lineStyle;

        

        dataSourceLinePlot.dataSource = self;

        [graph addPlot:dataSourceLinePlot];

if (plotTimer == nil){

 

//DLog(@"Timer Interval: %f", timerInterval);

//plotTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(rollPoints) userInfo:nil repeats:YES];

}

if([[self.graphHost.layer sublayers] indexOfObject:graph] == NSNotFound)

[self.graphHost.layer addSublayer:graph];

    }

    

  

    CPXYPlotSpace *plotSpace = (CPXYPlotSpace *)graph.defaultPlotSpace;

    

     NSDecimalNumber *high = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithFloat:[dataPuller overallHigh]] decimalValue]];

NSDecimalNumber *low = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithFloat:[dataPuller overallLow]] decimalValue]];

NSDecimalNumber *length = [high decimalNumberBySubtracting:low];

    

    //NSLog(@"high = %@, low = %@, length = %@", high, low, length);

     plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromDouble(0.0) length:CPDecimalFromUnsignedInteger(MAX_POINTS)];

     plotSpace.yRange = [CPPlotRange plotRangeWithLocation:[low decimalValue] length:[length decimalValue]];

// Create grid line styles

  /*  CPMutableLineStyle *majorGridLineStyle = [CPMutableLineStyle lineStyle];

    majorGridLineStyle.lineWidth = 1.0f;

    majorGridLineStyle.lineColor = [[CPColor redColor] colorWithAlphaComponent:0.75];

    CPMutableLineStyle *minorGridLineStyle = [CPMutableLineStyle lineStyle];

    minorGridLineStyle.lineWidth = 1.0f;

    minorGridLineStyle.lineColor = [[CPColor redColor] colorWithAlphaComponent:0.25];  */

    // Axes

    CPXYAxisSet *axisSet = (CPXYAxisSet *)graph.axisSet;

    

    CPXYAxis *x = axisSet.xAxis;

    x.majorIntervalLength = CPDecimalFromDouble(10.0);

    x.orthogonalCoordinateDecimal = CPDecimalFromInteger(0);

    //x.minorTicksPerInterval = 1;

//x.majorGridLineStyle = majorGridLineStyle;

//x.minorGridLineStyle = minorGridLineStyle;

//x.visibleRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(-0.5f) length:CPDecimalFromFloat(10.0f)];

//x.gridLinesRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0.0f) length:CPDecimalFromFloat(100.0f)];

    

    CPXYAxis *y = axisSet.yAxis;

    NSDecimal six = CPDecimalFromInteger(6);

    y.majorIntervalLength = CPDecimalDivide([length decimalValue], six);

y.majorTickLineStyle = nil;

   // y.minorTicksPerInterval = 4;

y.minorTickLineStyle = nil;

    y.orthogonalCoordinateDecimal = CPDecimalFromInteger(0);

//y.majorGridLineStyle = majorGridLineStyle;

//y.minorGridLineStyle = minorGridLineStyle;

//y.gridLinesRange =  [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(-0.1f) length:CPDecimalFromFloat(.3f)];

//y.visibleRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0.0f) length:CPDecimalFromFloat(.4f)];

 

//y.alternatingBandFills = [NSArray arrayWithObjects:[[CPColor whiteColor] colorWithAlphaComponent:0.1], [NSNull null], nil];

Eric

unread,
Apr 17, 2011, 8:38:54 PM4/17/11
to coreplot-discuss
I'm going to make a few assumptions in my answer:
1. MAX_POINTS = 200
2. Your datasource always returns the data index for the x coordinate
(0 through self.snapshot.count - 1).

Comments/observations:
1. Do you really need NSDecimal precision for the graph? Using doubles
for your data would make a difference. Set the cachePrecision on your
plot to CPPlotCachePrecisionDouble.

2. Your plot range calculations look ok.

3. Move the axis setup code inside the one-time graph setup block.
None of those properties change based on the plot data. I don't think
this is a big performance hit, but it's not helping.

4. Try something like this for the -pointReceived: method:

-(void)pointReceived:(id)point
{
[self.snapshot addObject:point]; //Add new point to the end of the
array
if (self.snapshot.count > MAX_POINTS)
[self.snapshot dequeue]; //remove the First Item in the Array
waitBuffer++;

if (waitBuffer > MAX_POINTS / 2) {
// delete old data first so fewer numbers need to be moved
around in memory; also reduces the max memory requirement
if ( dataSourceLinePlot.cachedDataCount > MAX_POINTS / 2 ) {
[dataSourceLinePlot deleteDataInIndexRange:NSMakeRange(0,
MAX_POINTS / 2)];
}
[dataSourceLinePlot
insertDataAtIndex:dataSourceLinePlot.cachedDataCount
numberOfRecords:MAX_POINTS / 2];
[self reconfigure];
waitBuffer = 0;
}
}

Eric

Chris Hardin

unread,
Apr 18, 2011, 9:01:38 AM4/18/11
to coreplot...@googlegroups.com
Eric,

All of the suggestions improved the speed tremendously... I still have a problem though... with MAX_POINTS/2 the graph walks it's way off the screen because of positioning of the offset of the elements.... so I set the MAX_POINTS raw and I even tried MAX_POINTS -1... The graph walks it's way around and crashes from the cachedDataCount... This is the problem I had yesterday almost all day... I've tried many variations but no joy... below is the updated code... 

Oh, I also set the length of the graph to be MAX_POINTS as well....



int waitBuffer = 0;


-(void)pointReceived:(id)point

{

[self.snapshot addObject:point]; //Add new point to the end of the array

if (self.snapshot.count > MAX_POINTS +5)

[self.snapshot dequeue]; //remove the First Item in the Array

waitBuffer++;

if (waitBuffer > MAX_POINTS) {

// delete old data first so fewer numbers need to be moved around in memory; also reduces the max memory requirement

if ( dataSourceLinePlot.cachedDataCount > MAX_POINTS) {

[dataSourceLinePlot deleteDataInIndexRange:NSMakeRange(0, MAX_POINTS)];

}

[dataSourceLinePlot insertDataAtIndex:dataSourceLinePlot.cachedDataCount numberOfRecords:MAX_POINTS];

[self reconfigure];

waitBuffer = 0;

}

}





#define MAX_POINTS 800

NSTimer *plotTimer;


-(void)reconfigure

{

CPXYPlotSpace *plotSpace;

NSDecimalNumber *length;

NSDecimalNumber *high;

NSDecimalNumber *low;

if (!graph){ //One time setup

 

   

    graph = [[CPXYGraph alloc] initWithFrame: self.view.bounds];

//self.view = [[CPGraphHostingView alloc]initWithFrame:[UIScreen mainScreen].bounds];

//self.graphHost = (CPGraphHostingView *)self.view;

self.graphHost.collapsesLayers = NO;

self.graphHost.hostedGraph = graph;

graph.delegate = self;   

        CPTheme *theme = [CPTheme themeNamed:kCPDarkGradientTheme];

    [graph applyTheme:theme];

        // graph.paddingTop = 30.0;

        //graph.paddingBottom = 30.0;

        //graph.paddingLeft = 50.0;

        //graph.paddingRight = 50.0;

        dataSourceLinePlot = [[[CPScatterPlot alloc] initWithFrame:graph.bounds] autorelease];

        dataSourceLinePlot.identifier = @"Data Source Plot";

dataSourceLinePlot.cachePrecision = CPPlotCachePrecisionDouble;

        

        CPMutableLineStyle *lineStyle = [[dataSourceLinePlot.dataLineStyle mutableCopy] autorelease];

        lineStyle.lineWidth = 1.f;

        lineStyle.lineColor = [CPColor greenColor];

        dataSourceLinePlot.dataLineStyle = lineStyle;

        

        dataSourceLinePlot.dataSource = self;

        [graph addPlot:dataSourceLinePlot];

if (plotTimer == nil){

 

//DLog(@"Timer Interval: %f", timerInterval);

//plotTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(refreshGraph) userInfo:nil repeats:YES];

}

if([[self.graphHost.layer sublayers] indexOfObject:graph] == NSNotFound)

[self.graphHost.layer addSublayer:graph];

plotSpace = (CPXYPlotSpace *)graph.defaultPlotSpace;

high = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithFloat:[dataPuller overallHigh]] decimalValue]];

low = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithFloat:[dataPuller overallLow]] decimalValue]];

length = [high decimalNumberBySubtracting:low];

//NSLog(@"high = %@, low = %@, length = %@", high, low, length);

plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromDouble(0.0) length:CPDecimalFromUnsignedInteger(MAX_POINTS)];

plotSpace.yRange = [CPPlotRange plotRangeWithLocation:[low decimalValue] length:[length decimalValue]];

// Axes

CPXYAxisSet *axisSet = (CPXYAxisSet *)graph.axisSet;

CPXYAxis *x = axisSet.xAxis;

x.majorIntervalLength = CPDecimalFromDouble(10.0);

x.orthogonalCoordinateDecimal = CPDecimalFromInteger(0);

//x.minorTicksPerInterval = 1;

//x.majorGridLineStyle = majorGridLineStyle;

//x.minorGridLineStyle = minorGridLineStyle;

//x.visibleRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(-0.5f) length:CPDecimalFromFloat(10.0f)];

//x.gridLinesRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0.0f) length:CPDecimalFromFloat(100.0f)];

CPXYAxis *y = axisSet.yAxis;

NSDecimal six = CPDecimalFromInteger(6);

y.majorIntervalLength = CPDecimalDivide([length decimalValue], six);

y.majorTickLineStyle = nil;

// y.minorTicksPerInterval = 4;

y.minorTickLineStyle = nil;

y.orthogonalCoordinateDecimal = CPDecimalFromInteger(0);

//y.majorGridLineStyle = majorGridLineStyle;

//y.minorGridLineStyle = minorGridLineStyle;

//y.gridLinesRange =  [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(-0.1f) length:CPDecimalFromFloat(.3f)];

//y.visibleRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0.0f) length:CPDecimalFromFloat(.4f)];

//y.alternatingBandFills = [NSArray arrayWithObjects:[[CPColor whiteColor] colorWithAlphaComponent:0.1], [NSNull null], nil];

    } else {

plotSpace = (CPXYPlotSpace *)graph.defaultPlotSpace;

high = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithFloat:[dataPuller overallHigh]] decimalValue]];

low = [NSDecimalNumber decimalNumberWithDecimal:[[NSNumber numberWithFloat:[dataPuller overallLow]] decimalValue]];

length = [high decimalNumberBySubtracting:low];

Eric

unread,
Apr 18, 2011, 7:49:10 PM4/18/11
to coreplot-discuss
I'm confused about the purpose of MAX_POINTS. Is it the maximum size
of your data buffer or the number of points to wait between updating
the graph? If it's both, then don't bother with the delete and insert--
just call -[dataSourceLinePlot reloadData].

Eric
>          dataSourceLinePlot = [[[CPScatterPlot alloc] initWithFrame:graph.
> bounds] autorelease];
>
>         dataSourceLinePlot.identifier = @"Data Source Plot";
>
> > > selector:@selector(rollPoints)...
>
> read more »
Reply all
Reply to author
Forward
0 new messages