在WinForms应用程序中处理大量数据时,性能问题尤为突出。尤其是在刷新图表时,希望它能够尽可能快地绘制,以减少用户的等待时间并最小化内存消耗。当使用Microsoft Chart Control(MSChart)在WinForms中工作时,随着数据量的增加,使用的内存和绘制图表的时间也会变长。FastLine系列虽然性能更好,但当数据量超过100K数据点时,性能仍然会受到影响。
在有限的像素屏幕上绘制大量数据点会导致所有数据点和线条重叠,从而导致用户对信息的误解。例如,下面展示了100K数据点的正弦波与下采样到500数据点的图表的对比。
正弦波100k数据点 正弦波下采样到500数据点
显然,需要一种下采样策略,以便在屏幕上绘制足够的数据点以获得更好的速度,同时保留波形的峰值和谷值。重要的是要记住,下采样的数据仅用于数据可视化目的,而不是用于定量分析。
在MSChart扩展中,决定实现Sveinn Steinarsson在他的硕士论文中描述的Largest-Triangle-Three-Bucket(LTTB)算法。完整的论文和不同编程语言的实现可以在Sveinn Steinarsson的GitHub页面上找到。MSChartExtension中的实现基于Adrian Seeley的C#代码。
该算法一次处理三个桶,并从左到右选择一个点,该点具有最大的有效面积,以代表下采样数据中的每个桶。下面的例子展示了具有5000数据点的随机数据系列和相同系列下采样到500数据点的结果。
随机数据5000点 随机数据下采样到500点
对于选定的桶中的每个点n,使用点a和avg计算每个点的三角形面积,其中点a是前一个桶中选定的点,而点avg是下一个桶中的临时点。桶n中面积最大的数据点将被选中。
double max_area = double.MinValue;
for (int n = bucket_start; n < bucket_end; n++) {
// Calculate triangle area over three buckets
double area = Math.Abs((a_x - avg_x) * (array[n].Y - a_y) - (a_x - n) * (avg_y - a_y));
if (area > max_area) {
max_area = area;
max_area_point = array[n];
next_a = n;
// Next a is this b
}
}
// Pick this point from the Bucket
window[w++] = max_area_point;
如果遵循Sveinn Steinarsson的论文和GitHub页面,将意识到LTTB有以下限制:不支持数据数组中的空白(null值)。X值必须严格递增。决定更进一步,以解决原始LTTB算法中描述的限制。
与通过数据索引将数据点划分为桶不同,改进的DynamicX-Largest-Triangle-Three-Bucket(DLTTB)算法将数据点划分为每个桶的固定X间隔。每个桶中的数据点数量可能会根据数据而变化。如果桶的边界之间没有数据点的X值,则该桶可能没有数据。
while (array[start].X < bucketBoundary) { start++; }
bucketBoundary += bucketSizeX;
end = start;
if (end <= (array.Length - 1)) {
// Prevent buffer overrun
while (array[end].X < bucketBoundary) {
end++;
if (end == array.Length)
break;
}
}
没有数据点的桶将不会被绘制,而其他桶的下采样数据将根据LTTB算法计算。考虑到一些桶可能是空的,DLTTB算法的下采样数据可能会少于定义的显示数据大小。
下面的例子展示了具有2种不同间隔的5000数据点的正弦波,其中前4500个数据的x增加了1,而最后500个数据点的x增加了20。看看前4500个数据点的数量,它们被绘制为实心块。
原始数据5000点
下面的图像显示了使用LTTB算法将原始数据下采样到800数据点的结果。由于LTTB不了解数据中不同的X间隔,最后500个数据点看起来丑陋且丢失了重要细节。
使用LTTB下采样到800点
下一张图像显示了使用DLTTB算法将系列下采样到800数据点的结果,看起来更好。
使用DLTTB下采样到800点
DLTTB和LTTB的性能是相同的,因为两种算法都使用了单次遍历方法,没有涉及复杂的乘法计算。
LTTB和DLTTB算法在MSChartExtension的DownSampling类中实现。通过设置dynamicX为true(DLTTB)和false(LTTB)来选择不同的算法。在ChartOption类中引入了一个新的选项BufferedMode。
要使用缓冲模式,请在调用EnableZoomAndPanControls方法时启用BufferedMode并设置ChartOption中的DisplayDataSize,如下所示:
new ChartOption() {
...
BufferedMode = true,
DisplayDataSize = 800
...
};
接下来,使用以下扩展方法为Series类添加数据点。
series.AddXYBuffered(xvalue, yvalue);
在缓冲模式下,图表上显示的最大数据点数在DisplayDataSize属性中定义。当用户放大图表时,将重新评估比例视图中可见数据的数量。如果可见数据的数量超过定义的显示数据大小的两倍,则下采样算法将生效。否则,将绘制所有可见数据。这样,当用户放大时,数据的细节将提供给用户。始终绘制系列的第一个和最后一个点,以确保PAN功能正确工作。