thomasguenzel
thomasguenzel

Reputation: 670

CorePlot scrolling realtime plot

I want to program a plot that scrolls automatically, so the actual time is on the right side. (marked red)

enter image description here

Data is added irregular with a timeinterval. How can i achieve this? I already have a scrolling graph (based on the example code provided by coreplot). But it's not completely accurate and quite cpu consuming. Also the labels should be "round" times like 14:52:00 14:52:20 and so on.

This is my code so far:

TestWindowController.h

@interface TestWindowController : NSWindowController<CPTPlotDataSource> {
    NSMutableArray *plotData;
    double currentIndex;
    NSDate *referenceDate;
    NSTimer *scrollTimer;
    NSTimer *newDataTimer;
}
@property (weak) IBOutlet CPTGraphHostingView *graphView;

TestWindowController.m

NSString *kPlotIdentifier       = @"Data Source Plot";
const double kTimePeriod = 60.0; // 1 Minute
const double kFrameRate         = 10.0;  // frames per second
const double kAlpha             = 0.25; // smoothing constant

@interface DataObj : NSObject {
    NSTimeInterval timeInterval;
    double testValue;
}
@property(readwrite) NSTimeInterval timeInterval;
@property(readwrite) double testValue;
@end

@implementation DataObj
@synthesize timeInterval;
@synthesize testValue;
@end


@implementation TestWindowController
@synthesize graphView;

- (id)initWithWindow:(NSWindow *)window
{
    self = [super initWithWindow:window];
    if (self) {
        // Initialization code here.
    }

    return self;
}

- (void)windowDidLoad
{
    // Set a reference Date
    NSCalendar *gregorian = [[NSCalendar alloc]
                             initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *dateComponents = [gregorian components:(NSHourCalendarUnit | NSMinuteCalendarUnit)
                                                    fromDate:[NSDate date]];
    [dateComponents setSecond:0];
    referenceDate = [gregorian dateFromComponents:dateComponents];
    currentIndex = 0.0;

    // Core Plot
    CPTGraph *graph = [[CPTXYGraph alloc] initWithFrame:CGRectZero];
    graphView.hostedGraph = graph;
    [graph applyTheme:[CPTTheme themeNamed:kCPTPlainBlackTheme]];

    //[self setTitleDefaultsForGraph:graph withBounds:bounds];
    //[self setPaddingDefaultsForGraph:graph withBounds:bounds];

    graph.plotAreaFrame.paddingTop    = 15.0;
    graph.plotAreaFrame.paddingRight  = 15.0;
    graph.plotAreaFrame.paddingBottom = 70.0;
    graph.plotAreaFrame.paddingLeft   = 55.0;

    // Grid line styles
    CPTMutableLineStyle *majorGridLineStyle = [CPTMutableLineStyle lineStyle];
    majorGridLineStyle.lineWidth = 0.75;
    majorGridLineStyle.lineColor = [[CPTColor whiteColor] colorWithAlphaComponent:0.5];

    CPTMutableLineStyle *minorGridLineStyle = [CPTMutableLineStyle lineStyle];
    minorGridLineStyle.lineWidth = 0.25;
    minorGridLineStyle.lineColor = [[CPTColor whiteColor] colorWithAlphaComponent:0.1];

    // Axes
    // X axis
    CPTXYAxisSet *axisSet = (CPTXYAxisSet *)graph.axisSet;
    CPTXYAxis *x          = axisSet.xAxis;
    x.labelingPolicy              = CPTAxisLabelingPolicyAutomatic;
    x.orthogonalCoordinateDecimal = CPTDecimalFromUnsignedInteger(0);
    x.majorGridLineStyle          = majorGridLineStyle;
    x.minorGridLineStyle          = minorGridLineStyle;
    x.minorTicksPerInterval       = 9;
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateStyle = kCFDateFormatterNoStyle;
    dateFormatter.timeStyle = kCFDateFormatterMediumStyle;
    CPTTimeFormatter *timeFormatter = [[CPTTimeFormatter alloc] initWithDateFormatter:dateFormatter];
    timeFormatter.referenceDate = referenceDate;
    x.labelFormatter            = timeFormatter;

    // Y axis
    CPTXYAxis *y = axisSet.yAxis;
    y.labelingPolicy              = CPTAxisLabelingPolicyAutomatic;
    y.orthogonalCoordinateDecimal = CPTDecimalFromUnsignedInteger(0);
    y.majorGridLineStyle          = majorGridLineStyle;
    y.minorGridLineStyle          = minorGridLineStyle;
    y.minorTicksPerInterval       = 3;
    y.labelOffset                 = 5.0;
    y.axisConstraints             = [CPTConstraints constraintWithLowerOffset:0.0];

    // Rotate the labels by 45 degrees, just to show it can be done.
    x.labelRotation = M_PI * 0.25;

    // Create the plot
    CPTScatterPlot *dataSourceLinePlot = [[CPTScatterPlot alloc] init] ;
    dataSourceLinePlot.identifier     = kPlotIdentifier;
    dataSourceLinePlot.cachePrecision = CPTPlotCachePrecisionDouble;

    CPTMutableLineStyle *lineStyle = [dataSourceLinePlot.dataLineStyle mutableCopy];
    lineStyle.lineWidth              = 3.0;
    lineStyle.lineColor              = [CPTColor greenColor];
    dataSourceLinePlot.dataLineStyle = lineStyle;

    dataSourceLinePlot.dataSource = self;
    [graph addPlot:dataSourceLinePlot];

    // Plot space
    CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)graph.defaultPlotSpace;
    plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromUnsignedInteger(0) length:CPTDecimalFromDouble(kTimePeriod)];
    plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromUnsignedInteger(0) length:CPTDecimalFromUnsignedInteger(1)];

    plotData  = [[NSMutableArray alloc] init];
    scrollTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 / kFrameRate
                                         target:self
                                       selector:@selector(scrollPlot:)
                                       userInfo:nil
                                        repeats:YES];
    newDataTimer = [NSTimer scheduledTimerWithTimeInterval:3.0
                                                   target:self
                                                 selector:@selector(newData:)
                                                 userInfo:nil
                                                  repeats:YES];
}

#pragma mark Test Methods

-(void)scrollPlot:(NSTimer *)theTimer
{
    CPTGraph *theGraph = graphView.hostedGraph;
    CPTPlot *thePlot   = [theGraph plotWithIdentifier:kPlotIdentifier];

    if ( thePlot ) {
        currentIndex = (-[referenceDate timeIntervalSinceNow])-kTimePeriod;
        CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)theGraph.defaultPlotSpace;
        double location       = currentIndex;
        plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromDouble(location)
                                                        length:CPTDecimalFromDouble(kTimePeriod)];

        //[plotData addObject:[NSNumber numberWithDouble:(1.0 - kAlpha) * [[plotData lastObject] doubleValue] + kAlpha * rand() / (double)RAND_MAX]];
        //[thePlot insertDataAtIndex:plotData.count - 1 numberOfRecords:1];
    }
}

-(void)newData:(NSTimer *)theTimer
{
    CPTGraph *theGraph = graphView.hostedGraph;
    CPTPlot *thePlot   = [theGraph plotWithIdentifier:kPlotIdentifier];

    if ( thePlot ) {
        DataObj *test = [[DataObj alloc] init];
        test.timeInterval = -[referenceDate timeIntervalSinceNow];
        test.testValue = rand() / (double)RAND_MAX;

        [plotData addObject:test];
        [thePlot insertDataAtIndex:plotData.count - 1 numberOfRecords:1];
    }
}

#pragma mark Plot Data Source Methods

-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
{
    return [plotData count];
}

-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
    NSNumber *num = nil;
    DataObj *dataObj = [plotData objectAtIndex:index];
    switch ( fieldEnum ) {
        case CPTScatterPlotFieldX:
            num = [NSNumber numberWithDouble:dataObj.timeInterval];
            break;

        case CPTScatterPlotFieldY:
            num = [NSNumber numberWithDouble:dataObj.testValue];
            break;

        default:
            break;
    }

    return num;
}

How can i get the program to be time-accurate and less cpu consuming? Thanks.

Upvotes: 1

Views: 1044

Answers (1)

Eric Skroch
Eric Skroch

Reputation: 27381

The automatic axis labeling policy doesn't work with dates very well. If you want to label every 20 seconds, for example, use the fixed interval labeling policy.

The high CPU usage is probably related to redrawing the axes and the plot for every plot range update. You'll need to experiment with different scrolling frame rates to find the best combination of CPU usage and smooth updates.

Upvotes: 2

Related Questions