0

我正在快速编写自己的代码来绘制图表以显示大型数据集。(不使用现有框架的原因是我想使用对数和线性视图,以及用户与图形的交互)。我需要帮助来加快我的代码速度,以尝试让应用程序感觉响应,因为它目前太慢了。

我将我的数据集传递到我的幅度图类(UIView)中,并计算数据线(divLine)的坐标,并使用以下函数将它们保存为 UIBezierPath:

func calcDataCoOrdinates(frequencies: [Float], Mag: [Float]) -> UIBezierPath {
    let divLine = UIBezierPath()
    let graphWidth:Int = width - dBLabelSpace
    let graphHeight:Int = height - freqLabelSpace
    let yRange = Float(yMax-yMin)

    //Limit Data to the Values being displayed
    let indexOfGreaterThanXMin = frequencies.firstIndex(where: {$0 > xMin})
    let indexLessThanXMax = frequencies.lastIndex(where: {$0 < xMax})
    let frequencyRange = frequencies[indexOfGreaterThanXMin!..<indexLessThanXMax!]
    let MagnitudeRange = Mag[indexOfGreaterThanXMin!..<indexLessThanXMax!]

    let graphRatio = (yMax-yMin)/Float(graphHeight)
    let logX = vForce.log10(frequencyRange)
    let logRange = log10(Double(xMax))-log10(Double(xMin));
    let graphDivideByLog = Float(graphWidth) / Float(logRange)
    let xMinArray:[Float] = Array(repeating: Float(log10(xMin)), count: logX.count)
    let x5 = vDSP.multiply(subtraction: (a: logX, b: xMinArray), graphDivideByLog)
    let x6 = vDSP.add(Float(dBLabelSpace + 10), x5)
    let yMaxArray = Array(repeating:Float(yMax), count: MagnitudeRange.count)
    let y2 = vDSP.subtract(yMaxArray, MagnitudeRange)
    let y3 = vDSP.divide(y2, Float(graphRatio))

    var allPoints:[CGPoint]=[]
    for i in 0..<x6.count{
        let value = CGPoint.init(x: Double(x6[i]), y: Double(y3[i]))
        allPoints.append(value)
    }

    divLine.removeAllPoints()
    divLine.move(to: allPoints[0])
    for i  in 1 ..< allPoints.count {
        divLine.addLine(to: allPoints[i])
    }

    return divLine
}

我没有直接从触发它的 UI 元素调用 setNeedsDisplay,而是调用 redrawGraph()。这会在后台线程中调用 calcDataCoOrdinates 函数,然后在计算完所有坐标后,我会在主线程上调用 setNeedsDisplay。

func redrawGraph(){
    width = Int(self.frame.width);
    height = Int(self.frame.height);

    magnitudeQueue.sync{
        if mag1.isEmpty == false {
            self.data1BezierPath = self.calcData1CoOrdinates(frequencies: freq1, Mag: mag1)
        }
        if mag2.isEmpty == false {
            self.data2BezierPath = self.calcData1CoOrdinates(frequencies: freq2, Mag: mag2)
        }
        if magSum.isEmpty == false {
            self.dataSumBezierPath = self.calcData1CoOrdinates(frequencies: freqSum, Mag: magSum)
        }            
        DispatchQueue.main.async {
            self.setNeedsDisplay()
        }
    }

最后贴出实际绘图功能的代码:

override func draw(_ rect: CGRect) {
    self.layer.sublayers = nil
    drawData(Color: graphData1Color, layer: in1Layer, lineThickness: Float(graphDataLineWidth), line:data1BezierPath)
    drawData(Color: graphData2Color, layer: in2Layer, lineThickness: Float(graphDataLineWidth), line:data2BezierPath)
    drawData(Color: sumColor, layer: in3Layer, lineThickness: Float(sumLineThickness), line: dataSumBezierPath)
}

func drawData(Color: UIColor, layer: CAShapeLayer, lineThickness:Float, line:UIBezierPath){
    layer.removeFromSuperlayer()
    layer.path = nil
    Color.set()
    line.lineWidth = CGFloat(lineThickness)
    layer.strokeColor = Color.cgColor
    layer.fillColor = UIColor.clear.cgColor
    layer.path = line.cgPath
    layer.lineWidth = CGFloat(lineThickness)
    layer.contentsScale = UIScreen.main.scale
    let mask = CAShapeLayer()
    mask.contentsScale = UIScreen.main.scale
    mask.path = bpath?.cgPath
    layer.mask = mask
    self.layer.addSublayer(layer)

}

此代码可以很好地计算三个数据集并在图表上查看它们。我已经排除了计算背景层(网格线)的代码,但这只是为了简化代码。

我得到的问题是,要尝试和处理“实时”交互性需要做很多事情。我认为我们需要大约 30 fps 的速度来感觉响应,这总共提供了大约 33 毫秒的处理和渲染时间。

现在,如果我说 32k 个数据点,目前正在测量这些数据点,那么 calcDataCoOrdinates 的三遍中的每一遍都可以在它们之间进行。22-36ms,总计约90ms。1/10 秒非常滞后,所以我的问题是如何加快速度?如果我测量实际的绘图过程,这目前非常低(1-2ms),这让我相信我的 calcDataCoOrdinates 函数需要改进。

我尝试了几种方法,包括使用 Douglas-Peucker 算法来减少数据点的数量,但是这是一个很大的时间成本(+100 毫秒)。

我尝试跳过重叠像素的数据点

我尝试尽可能多地使用加速框架,该函数的上半部分在大约 10 毫秒内运行,但下半部分将坐标放入 CGPoint 数组,并迭代生成 UIBezierPath 需要 20 毫秒。我尝试使用 CGPoints 预初始化 allPoints 数组,认为这会节省一些时间,但这仍然需要很长时间。

能否计算得更多。有效还是我达到了我的应用程序的限制?

4

0 回答 0