在散点图上绘制置信椭圆

Drawing a confidence ellipse on top of a scatter plot

我目前正在开发一个 iOS 应用程序,我在其中使用 CorePlot 库(2.1 版)绘制散点图。我的散点图绘制得很好,在下一步中,我想在散点图的顶部绘制一个半透明的置信椭圆。我写了一个 class 计算椭圆的主轴和短轴以及所需的旋转角度。我的 ConfidenceEllipse class 实现了一个 getPath() 方法,其中 returns 一个代表要绘制的椭圆的 CGPath。

func getPath() -> CGPath
{
var ellipse: CGPath

var transform = CGAffineTransformIdentity
transform = CGAffineTransformTranslate (transform, CGFloat(-self.meanX), CGFloat(-self.meanY))
transform = CGAffineTransformRotate (transform, CGFloat(self.angle))
transform = CGAffineTransformTranslate (transform, CGFloat(self.meanX), CGFloat(self.meanY))

ellipse = CGPathCreateWithEllipseInRect(CGRectMake (CGFloat(-self.mainHalfAxis), CGFloat(-self.minorHalfAxis), CGFloat(2 * self.mainHalfAxis), CGFloat(2 * self.minorHalfAxis)),&transform)

return ellipse
}

在网上搜索了一段时间后,似乎注释是可行的方法,所以我尝试了这个:

let graph     = hostView.hostedGraph!
let space     = graph.defaultPlotSpace    
let ellipse = ConfidenceEllipse(chiSquare: 5.991)
ellipse.analyze(self.samples)

let annotation = CPTPlotSpaceAnnotation (plotSpace: space!, anchorPlotPoint: [0,0])
let overlay = CPTBorderedLayer (frame: graph.frame)
overlay.outerBorderPath = ellipse.getPath()

let fillColor = CPTColor.yellowColor()
overlay.fill = CPTFill (color: fillColor)

annotation.contentLayer = overlay
annotation.contentLayer?.opacity = 0.5
graph.addAnnotation(annotation)

这样做,会给我以下信息 Screenshot 如您所见,叠加层占据了框架的全部大小,考虑到我在创建 CPTBorderedLayer 对象时传递了框架尺寸这一事实,这似乎是合乎逻辑的。我也尝试将构造函数留空,但叠加层根本不显示。所以我想知道,我在这里遗漏了什么吗?

您需要缩放椭圆以匹配绘图。使用绘图区bounds作为标注层的边框,将标注附加到绘图区。在 x 和 y 方向缩放椭圆以匹配绘图使用的变换 space 以适合绘图区域中的绘图。

编辑: 在研究了边框图层的工作原理后,我意识到我上面的建议是行不通的。 CPTBorderedLayer 每当图层边界更改时自动设置 outerBorderPath。不要试图影响图层边框,而是将椭圆绘制到图像中并将其用作带边框图层的 fill。您应该调整层的大小,使椭圆恰好适合内部。

在未能使注释正常工作后,我决定走另一条路。我的最终解决方案是用第二个散点图覆盖我原来的散点图,它只包含一个数据点,即我的置信椭圆的中心。这是代码

    func drawConfidenceEllipse () {

    let graph     = hostView.hostedGraph!
    let plotSpace = graph.defaultPlotSpace as! CPTXYPlotSpace

    let scaleX = (graph.bounds.size.width - graph.paddingLeft - graph.paddingRight) / CGFloat(plotSpace.xRange.lengthDouble)
    let scaleY = (graph.bounds.size.height - graph.paddingTop - graph.paddingBottom) / CGFloat(plotSpace.yRange.lengthDouble)

    let analysis = ConfidenceEllipse(chiSquare: 5.991)
    analysis.analyze(self.samples)
    let unscaledPath = analysis.getPath()

    let bounds = CGPathGetBoundingBox(unscaledPath)

    var scaler = CGAffineTransformIdentity
    scaler = CGAffineTransformScale (scaler, scaleX, scaleY)
    scaler = CGAffineTransformTranslate (scaler, CGFloat (-bounds.origin.x), CGFloat (-bounds.origin.y))
    let scaledPath   = CGPathCreateCopyByTransformingPath (unscaledPath, &scaler)
    let scaledBounds = CGPathGetPathBoundingBox(scaledPath)

    let symbol = CPTPlotSymbol ()
    symbol.symbolType = CPTPlotSymbolType.Custom
    symbol.customSymbolPath = scaledPath
    symbol.fill = CPTFill (color: CPTColor.yellowColor().colorWithAlphaComponent(0.25))
    symbol.size = CGSize (width: scaledBounds.size.width, height: scaledBounds.size.height)

    let lineStyle = CPTMutableLineStyle()
    lineStyle.lineWidth = 1
    lineStyle.lineColor = CPTColor.yellowColor()

    symbol.lineStyle = lineStyle

    let ellipse = CPTScatterPlot (frame: hostView.frame)
    ellipse.title = "Confidence Ellipse"
    ellipse.delegate = self
    ellipse.dataSource = self
    ellipse.plotSymbol = symbol
    ellipse.dataLineStyle = nil

    graph.addPlot(ellipse)

}

这是最终结果的屏幕截图:

95% Confidence Ellipse on top of scatter plot

希望对您有所帮助