如何在基于CorePlot的Objective-C iOS角度图表中高亮缺失数据
嘿,这个场景我之前做类似的CorePlot图表时碰到过,给你几个实用的方案来高亮缺失数据,完全适配你每秒更新、向下滚动的需求:
核心思路:先标记缺失数据
首先得明确怎么识别缺失数据——既然外部模块每秒生成一个角度值,那当某一秒没有收到新数据时,就往你的NSMutableArray *angleData里插入一个[NSNull null]占位,而不是跳过这个时间点。这样数组的每个索引刚好对应一个时间点,和Y轴的时间刻度完全对齐,后续处理起来就方便多了。
你可以加个时间戳检测逻辑来自动插入占位符:
@property (nonatomic, assign) NSTimeInterval lastReceivedDataTime; @property (nonatomic, strong) NSMutableArray *angleData; @property (nonatomic, strong) NSMutableArray *missingIndices; // 记录缺失数据的索引 // 收到新角度值时调用 - (void)addNewAngleValue:(NSNumber *)newAngle { NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate]; // 计算和上次收到数据的时间差,留0.2秒容错 NSTimeInterval timeDiff = currentTime - self.lastReceivedDataTime; if (timeDiff > 1.2 && self.lastReceivedDataTime > 0) { // 计算缺失的秒数,插入对应数量的NSNull NSInteger missingSeconds = floor(timeDiff); for (NSInteger i = 0; i < missingSeconds; i++) { [self.angleData addObject:[NSNull null]]; [self.missingIndices addObject:@(self.angleData.count - 1)]; } } [self.angleData addObject:newAngle]; self.lastReceivedDataTime = currentTime; // 触发图表更新 [self updateChart]; }
方案1:用CorePlot原生特性高亮缺失点
CorePlot的散点图(CPTScatterPlot)本身支持处理缺失数据,你可以通过数据源方法和样式设置来直观展示:
1.1 断开线条+红色标记点
在数据源方法中,遇到NSNull就返回nil,CorePlot会自动断开缺失位置的线条;同时给缺失点添加红色标记:
// CorePlot数据源方法:返回数据点 - (NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index { id dataPoint = self.angleData[index]; if ([dataPoint isKindOfClass:[NSNull class]]) { return nil; // 告诉CorePlot这里是缺失数据 } if (fieldEnum == CPTScatterPlotFieldX) { return dataPoint; // X轴是角度值 } else { return @(index); // Y轴是时间索引(对应每秒一个点) } } // 设置缺失点的标记样式 - (CPTPlotSymbol *)symbolForScatterPlot:(CPTScatterPlot *)plot recordIndex:(NSUInteger)index { id dataPoint = self.angleData[index]; if ([dataPoint isKindOfClass:[NSNull class]]) { CPTPlotSymbol *missingSymbol = [CPTPlotSymbol symbolWithShape:CPTPlotSymbolShapeCross]; missingSymbol.fill = [CPTFill fillWithColor:[CPTColor redColor]]; missingSymbol.size = CGSizeMake(8, 8); return missingSymbol; } // 正常数据可以不显示标记,或者返回小圆圈 return nil; }
1.2 用虚线连接缺失段
如果想把缺失前后的线条用红色虚线连接,设置missingDataLineStyle即可:
CPTScatterPlot *anglePlot = [[CPTScatterPlot alloc] init]; // 正常线条样式 CPTMutableLineStyle *normalLineStyle = [CPTMutableLineStyle lineStyle]; normalLineStyle.lineColor = [CPTColor blueColor]; normalLineStyle.lineWidth = 2.0; anglePlot.dataLineStyle = normalLineStyle; // 缺失数据的线条样式(红色虚线) CPTMutableLineStyle *missingLineStyle = [CPTMutableLineStyle lineStyle]; missingLineStyle.lineColor = [CPTColor redColor]; missingLineStyle.lineDashPattern = @[@5, @3]; anglePlot.missingDataLineStyle = missingLineStyle;
方案2:添加自定义缺失提示图层
如果想要更醒目的提示(比如红色背景条),可以直接在图表的对应时间位置添加自定义UIView:
- (void)updateChart { // 先移除之前的缺失标记 for (UIView *subview in self.graph.hostingView.subviews) { if (subview.tag == 1001) { // 给缺失标记设专属tag [subview removeFromSuperview]; } } CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)self.graph.defaultPlotSpace; CGRect plotAreaBounds = self.graph.plotAreaFrame.plotArea.bounds; for (NSNumber *indexNum in self.missingIndices) { NSUInteger index = [indexNum unsignedIntegerValue]; // 跳过不在当前显示范围内的缺失数据 if (index < plotSpace.yRange.locationUnsignedInteger || index >= plotSpace.yRange.locationUnsignedInteger + plotSpace.yRange.lengthUnsignedInteger) { continue; } // 把数据索引转换为图表视图坐标 CGPoint plotPoint = [plotSpace plotPointForPlotRange:plotSpace.xRange plotRange:plotSpace.yRange xDouble:0 // 可以用X轴中心位置 yDouble:index]; CGPoint viewPoint = [self.graph convertPoint:plotPoint fromLayer:self.graph.plotAreaFrame.plotArea]; // 创建红色背景条,覆盖整个X轴宽度 UIView *missingBar = [[UIView alloc] initWithFrame:CGRectMake(0, viewPoint.y - 2, plotAreaBounds.size.width, 4)]; missingBar.backgroundColor = [UIColor colorWithRed:1.0 green:0.2 blue:0.2 alpha:0.3]; missingBar.tag = 1001; [self.graph.hostingView addSubview:missingBar]; } // 更新图表滚动范围(显示最近50秒数据) NSUInteger displayCount = 50; NSUInteger startIndex = MAX(0, self.angleData.count - displayCount); plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromUnsignedInteger(startIndex) length:CPTDecimalFromUnsignedInteger(displayCount)]; // 更新X轴范围(以角度平均值为中心) CGFloat avgAngle = [self calculateAverageAngle]; plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(avgAngle - 50) length:CPTDecimalFromFloat(100)]; [self.graph reloadData]; } // 计算角度数组的平均值(跳过NSNull) - (CGFloat)calculateAverageAngle { CGFloat sum = 0; NSInteger count = 0; for (id obj in self.angleData) { if ([obj isKindOfClass:[NSNumber class]]) { sum += [obj floatValue]; count++; } } return count > 0 ? sum / count : 0; }
方案3:用背景Plot高亮缺失时间段
如果不想用UIView,也可以用CorePlot的CPTBarPlot作为背景层,给缺失时间段添加淡红色背景:
// 初始化时添加背景Plot - (void)setupBackgroundPlot { CPTBarPlot *missingBackgroundPlot = [[CPTBarPlot alloc] init]; missingBackgroundPlot.dataSource = self; missingBackgroundPlot.identifier = @"MissingBackground"; missingBackgroundPlot.barWidth = CPTDecimalFromDouble(self.graph.plotAreaFrame.plotArea.bounds.size.width); missingBackgroundPlot.barOffset = CPTDecimalFromDouble(0); missingBackgroundPlot.fill = [CPTFill fillWithColor:[CPTColor colorWithComponentRed:1.0 green:0.8 blue:0.8 alpha:0.5]]; // 把背景Plot放在最底层 [self.graph.plotAreaFrame.plotArea addPlot:missingBackgroundPlot belowPlot:nil]; } // 数据源方法返回缺失数据的位置 - (NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index { if ([plot.identifier isEqualToString:@"MissingBackground"]) { NSNumber *missingIndex = self.missingIndices[index]; if (fieldEnum == CPTBarPlotFieldBarLocation) { return missingIndex; // Y轴位置 } else if (fieldEnum == CPTBarPlotFieldBarLength) { return @(1); // 高度为1个时间单位(1秒) } } // 其他Plot的处理... return nil; }
这些方案都能完美适配你每秒更新、向下滚动的图表场景,你可以根据需求选一个或者组合使用~
内容的提问来源于stack exchange,提问作者andreapavan
相关产品推荐
相关产品推荐

