Reputation: 138
I am trying to draw a real time plot of a 4-channel input stream using 4 separate plots on the same graph. Currently I am taking this data out of a text file, but will eventually work to get this data using bluetooth. My plot itself works fine, but core-plot appears to draw an extra horizontal line for each plot at the beginning of the plotting at the levels of the first data set. I think this has something to do with core-plot trying to get all the points to be plotted before it starts to display them. The following image shows these lines, while the actual waveforms, square waves, are in the middle of being plot.
As the plotting progresses and the displayed range of the x-axis changes, these lines disappear. Like in here:
Following is the relevant section of my code. It is based on Ray Wenderlich's scatter plot tutorial and Eric Skrooch's real time plot example. (I hope I spelt those names right!). Personally, I believe that this is a bug in core-plot and not a coding error though. Any help on how to get rid of these troubling lines is greatly appreciated! Thanks!
-(void)configurePlots {
//Get graph and plot space
CPTGraph *graph = self.hostView.hostedGraph;
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *) graph.defaultPlotSpace;
//Create the four plots
CPTScatterPlot *channel1Plot = [[CPTScatterPlot alloc]init];
channel1Plot.dataSource = self;
channel1Plot.identifier = @"channel1";
[graph addPlot:channel1Plot toPlotSpace:plotSpace];
CPTScatterPlot *channel2Plot = [[CPTScatterPlot alloc]init];
channel2Plot.dataSource = self;
channel2Plot.identifier = @"channel2";
[graph addPlot:channel2Plot toPlotSpace:plotSpace];
CPTScatterPlot *channel3Plot = [[CPTScatterPlot alloc]init];
channel3Plot.dataSource = self;
channel3Plot.identifier = @"channel3";
[graph addPlot:channel3Plot toPlotSpace:plotSpace];
CPTScatterPlot *channel4Plot = [[CPTScatterPlot alloc]init];
channel4Plot.dataSource = self;
channel4Plot.identifier = @"channel4";
[graph addPlot:channel4Plot toPlotSpace:plotSpace];
//Set up plot space
[plotSpace scaleToFitPlots:[NSArray arrayWithObjects:channel1Plot, channel2Plot, channel3Plot, channel4Plot, nil]];
CPTMutablePlotRange *xRange = [plotSpace.xRange mutableCopy];
[xRange expandRangeByFactor:CPTDecimalFromCGFloat(1.1f)];
plotSpace.xRange = xRange;
CPTMutablePlotRange *yRange = [plotSpace.yRange mutableCopy];
[yRange expandRangeByFactor:CPTDecimalFromCGFloat(1.2f)];
plotSpace.yRange = yRange;
//Create styles and symbols
CPTMutableLineStyle *channelLineStyle = [channel1Plot.dataLineStyle mutableCopy];
channelLineStyle.lineWidth = 1.0;
channelLineStyle.lineColor = [CPTColor redColor];
channel1Plot.dataLineStyle = channelLineStyle;
channel2Plot.dataLineStyle = channelLineStyle;
channel3Plot.dataLineStyle = channelLineStyle;
channel4Plot.dataLineStyle = channelLineStyle;
}
-(void)configureAxes {
//Create styles
CPTMutableTextStyle *axisTitleStyle = [CPTMutableTextStyle textStyle];
axisTitleStyle.color = [CPTColor whiteColor];
axisTitleStyle.fontName = @"Helvetica-Bold";
axisTitleStyle.fontSize = 12.0f;
CPTMutableLineStyle *axisLineStyle = [CPTMutableLineStyle lineStyle];
axisLineStyle.lineWidth = 2.0f;
axisLineStyle.lineColor = [CPTColor whiteColor];
CPTMutableTextStyle *axisTextStyle = [[CPTMutableTextStyle alloc] init];
axisTextStyle.color = [CPTColor yellowColor];
axisTextStyle.fontName = @"Helvetica-Bold";
axisTextStyle.fontSize = 11.0f;
CPTMutableLineStyle *tickLineStyle = [CPTMutableLineStyle lineStyle];
tickLineStyle.lineWidth = 2.0f;
tickLineStyle.lineColor = [CPTColor yellowColor];
CPTMutableLineStyle *gridLineStyle = [CPTMutableLineStyle lineStyle];
tickLineStyle.lineColor = [CPTColor purpleColor];
tickLineStyle.lineWidth = 1.0f;
//Get axis set
CPTXYAxisSet *axisSet = (CPTXYAxisSet *) self.hostView.hostedGraph.axisSet;
//Configure X-axis
CPTAxis *x = axisSet.xAxis;
x.title = @"Time";
x.titleTextStyle = axisTitleStyle;
x.titleOffset = 15.0f;
x.axisLineStyle = axisLineStyle;
x.labelingPolicy = CPTAxisLabelingPolicyNone;
x.labelTextStyle = axisTextStyle;
x.majorTickLineStyle = axisLineStyle;
x.majorTickLength = 4.0f;
x.tickDirection = CPTSignNegative;
CGFloat pointsCount = POINTS_ON_SCREEN;
NSMutableSet *xLabels = [NSMutableSet setWithCapacity:pointsCount];
NSMutableSet *xLocations = [NSMutableSet setWithCapacity:pointsCount];
NSInteger i=0;
//NSString *loc = [NSString stringWithFormat:@"d",j];
for (NSInteger j=0; j<POINTS_ON_SCREEN; j++) {
CPTAxisLabel *label = [[CPTAxisLabel alloc] initWithText:[NSString stringWithFormat:@"%ld", (long)j+1] textStyle:x.labelTextStyle];
CGFloat location = i++;
label.tickLocation = CPTDecimalFromCGFloat(location);
label.offset = x.majorTickLength;
if (label) {
[xLabels addObject:label];
[xLocations addObject:[NSNumber numberWithFloat:location]];
}
}
x.axisLabels = xLabels;
x.majorTickLocations = xLocations;
//Configure Y-axis
axisSet.yAxis.axisConstraints = [CPTConstraints constraintWithLowerOffset:0.0];
CPTAxis *y = axisSet.yAxis;
y.title = @"Channel outputs";
y.titleTextStyle = axisTitleStyle;
y.titleOffset = -40.0f;
y.axisLineStyle = axisLineStyle;
y.majorGridLineStyle = gridLineStyle;
y.labelingPolicy = CPTAxisLabelingPolicyNone;
y.labelTextStyle = axisTextStyle;
y.labelOffset = 16.0f;
y.majorTickLineStyle = axisLineStyle;
y.majorTickLength = 4.0f;
y.minorTickLength = 2.0f;
y.tickDirection = CPTSignPositive;
CGFloat majorIncrement = 0.0005;
CGFloat minorIncrement = 0.0001;
CGFloat yMax = 0.0040f;
NSMutableSet *yLabels = [NSMutableSet set];
NSMutableSet *yMajorLocations = [NSMutableSet set];
NSMutableSet *yMinorLocations = [NSMutableSet set];
for (CGFloat j = minorIncrement; j<=yMax; j+=minorIncrement) {
j = roundf(j*100000)/100000;
CGFloat mod = j / majorIncrement;
NSInteger modInt = (NSInteger)mod;
CGFloat modMantissa = mod - modInt;
modMantissa = roundf(modMantissa * 100)/100;
if (modMantissa < 0.1 || modMantissa > 0.9) {
CPTAxisLabel *label = [[CPTAxisLabel alloc] initWithText:[NSString stringWithFormat:@"%.5f", j] textStyle:y.labelTextStyle];
NSDecimal location = CPTDecimalFromCGFloat(j);
//NSNumber *location = [NSNumber numberWithFloat:j];
label.tickLocation = location;
label.offset = -y.majorTickLength -y.labelOffset -9;
//label.offset = 0;
if (label) {
[yLabels addObject:label];
}
[yMajorLocations addObject:[NSDecimalNumber decimalNumberWithDecimal:location]];
} else {
[yMinorLocations addObject:[NSDecimalNumber decimalNumberWithString:[NSString stringWithFormat:@"%f",j]]];
}
}
y.axisLabels = yLabels;
y.majorTickLocations = yMajorLocations;
y.minorTickLocations = yMinorLocations;
}
-(void)createTimer {
NSTimer *dataTimer = [NSTimer timerWithTimeInterval:0.002 target:self selector:@selector(dynamicUpdate:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:dataTimer forMode:NSDefaultRunLoopMode];
}
-(void)dynamicUpdate: (NSTimer *)dataTimer {
CPTGraph *graph = self.hostView.hostedGraph;
CPTPlot *channel1Plot = [graph plotWithIdentifier:@"channel1"];
CPTPlot *channel2Plot = [graph plotWithIdentifier:@"channel2"];
CPTPlot *channel3Plot = [graph plotWithIdentifier:@"channel3"];
CPTPlot *channel4Plot = [graph plotWithIdentifier:@"channel4"];
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)graph.defaultPlotSpace;
if (channel1Plot && channel2Plot && channel3Plot && channel4Plot) {
if ( arrayIndex1 >= POINTS_ON_SCREEN-1
|| arrayIndex2 >= POINTS_ON_SCREEN-1
|| arrayIndex3 >= POINTS_ON_SCREEN-1
|| arrayIndex4 >= POINTS_ON_SCREEN-1) {
[channel1Array removeObjectAtIndex:0];
[channel1Plot deleteDataInIndexRange:NSMakeRange(0, 1)];
[channel2Array removeObjectAtIndex:0];
[channel2Plot deleteDataInIndexRange:NSMakeRange(0, 1)];
[channel3Array removeObjectAtIndex:0];
[channel3Plot deleteDataInIndexRange:NSMakeRange(0, 1)];
[channel4Array removeObjectAtIndex:0];
[channel4Plot deleteDataInIndexRange:NSMakeRange(0, 1)];
}
plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromUnsignedInteger(currentIndex >= POINTS_ON_SCREEN ? currentIndex-POINTS_ON_SCREEN +1 : 0) length:CPTDecimalFromUnsignedInteger(POINTS_ON_SCREEN-1)];
currentIndex++;
[channel1Plot insertDataAtIndex:POINTS_ON_SCREEN-1 numberOfRecords:1];
[channel2Plot insertDataAtIndex:POINTS_ON_SCREEN-1 numberOfRecords:1];
[channel3Plot insertDataAtIndex:POINTS_ON_SCREEN-1 numberOfRecords:1];
[channel4Plot insertDataAtIndex:POINTS_ON_SCREEN-1 numberOfRecords:1];
}
}
-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot {
return POINTS_ON_SCREEN;
}
-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx {
// NSLog(@"Plotting point");
NSNumber *num = nil;
if ([self.activityIndicator isAnimating] == YES) {
[self.activityIndicator stopAnimating];
}
switch (fieldEnum) {
case CPTScatterPlotFieldX:
if (idx < POINTS_ON_SCREEN) {
return [NSNumber numberWithUnsignedInteger:idx + currentIndex - POINTS_ON_SCREEN];
};
break;
case CPTScatterPlotFieldY:
if ([plot.identifier isEqual:@"channel1"]) {
num = [channel1Array objectAtIndex:idx];
arrayIndex1 = idx;
} else if ([plot.identifier isEqual:@"channel2"]) {
num = [channel2Array objectAtIndex:idx];
arrayIndex2 = idx;
} else if ([plot.identifier isEqual:@"channel3"]) {
num = [channel3Array objectAtIndex:idx];
arrayIndex3 = idx;
} else if ([plot.identifier isEqual:@"channel4"]) {
num = [channel4Array objectAtIndex:idx];
arrayIndex4 = idx;
} else {
NSLog(@"data unavailable");
}
break;
default:
NSLog(@"ERROR: trying to plot on unidentified axis");
break;
}
NSString *numString = [NSString stringWithFormat:@"%@", num];
dispatch_async(AddToArrayQ, ^{[self writeData:numString];});
return num;
}
Upvotes: 0
Views: 375
Reputation: 138
The problem here lied not in hardcoding the numberOfRecordsForPlot: method as RishiG suggested, but in hardcoding the case CPTScatterPlotFieldX in the numberForPlot:field:recordIndex method. But RishiG's suggestion was a good one nonetheless and I made some changes before I resolved the problem.
Original: Arrays channel1Array - channel4Array already contained the data to be plotted and I went on deleting items from the array as I plotted them. This meant that I could not use something like [channel1Array count] in the numberOfRecordsForPlot: method and had to rely on the plot index instead. This also would have worked but is inefficient anyway. Modified: Followed Eric Skroch's example fully to start with empty arrays and update them as plotting proceeded.
The original case CPTScatterPlotFieldX in the numberForPlot:field:recordIndex method used here had the return value as (idx + currrentIndex - POINTS_ON_SCREEN) which might have produced a negative result. It looks like core-plot perhaps used its absolute value to get the x-coordinate, and hence the horizontal line. This can be remedied by removing the if condition and having the return value as (idx + currentIndex - channel1Array.count).
P.S.: @vikingosegundo I know, thanks. But I had to take photos in quick succession so my camera's burst mode was best suited!
Upvotes: 0
Reputation: 2830
Unless I have misread this, you are returning a hardcoded value from numberOfRecordsForPlot:, so CorePlot expects to be able to fill all of those points on the x axis. Those lines you see are probably due to having initialized those arrays to a constant value (they must be initialized, right, or you would be getting an error when you try to access uninitialized indices?)
Notice in Eric Skroch's example that he is returning [plotData count] from numberOfRecordsForPlot:, and that plotData is empty on initialization.
Upvotes: 2