我正在快速编写自己的代码来绘制图表以显示大型数据集。(不使用现有框架的原因是我想使用对数和线性视图,以及用户与图形的交互)。我需要帮助来加快我的代码速度,以尝试让应用程序感觉响应,因为它目前太慢了。
我将我的数据集传递到我的幅度图类(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 数组,认为这会节省一些时间,但这仍然需要很长时间。
能否计算得更多。有效还是我达到了我的应用程序的限制?