You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 04:21:56