WPF 高效绘制290万数据走势曲线图:虚拟化技术和滚动条实现
由于290万组数据量太大,直接加载会导致程序卡顿,因此需要使用虚拟化技术,只加载当前可见区域内的数据。
下面是一个简单的示例代码,仅供参考:
XAML:
<Window x:Class='WpfApp1.MainWindow'
xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:WpfApp1'
Title='MainWindow' Height='600' Width='800'>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height='Auto'/>
<RowDefinition Height='*'/>
</Grid.RowDefinitions>
<StackPanel Orientation='Horizontal' Margin='10'>
<Label Content='X轴最大值:' VerticalAlignment='Center'/>
<TextBox x:Name='tbMaxX' Width='50' Text='3600' VerticalAlignment='Center' Margin='5'/>
<Button Content='生成曲线' Click='Button_Click' Margin='10'/>
</StackPanel>
<ScrollViewer Grid.Row='1' HorizontalScrollBarVisibility='Auto' VerticalScrollBarVisibility='Auto'>
<local:LineChart x:Name='lineChart'/>
</ScrollViewer>
</Grid>
</Window>
C#:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
int maxX = int.Parse(tbMaxX.Text);
lineChart.GenerateChart(maxX);
}
}
public class LineChart : FrameworkElement
{
private const int PointRadius = 2;
private const int PointDiameter = PointRadius * 2;
private const int ChartMargin = 10;
private List<Point> points = new List<Point>();
private int maxX;
private int visibleMinX;
private int visibleMaxX;
private int totalWidth;
private int totalHeight;
private int visibleWidth;
private int visibleHeight;
private int startIndex;
private int endIndex;
public LineChart()
{
this.Loaded += LineChart_Loaded;
}
private void LineChart_Loaded(object sender, RoutedEventArgs e)
{
UpdateLayout();
UpdateVisibleRange();
InvalidateVisual();
}
public void GenerateChart(int maxX)
{
this.maxX = maxX;
Random random = new Random();
for (int i = 0; i < 2900000; i++)
{
double x = random.NextDouble() * maxX;
double y = random.NextDouble() * ActualHeight;
points.Add(new Point(x, y));
}
UpdateLayout();
UpdateVisibleRange();
InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if (points == null || points.Count == 0)
{
return;
}
Pen pen = new Pen(Brushes.Blue, 1);
Point lastPoint = new Point(-1, -1);
for (int i = startIndex; i <= endIndex; i++)
{
Point point = points[i];
if (lastPoint.X >= 0 && lastPoint.Y >= 0)
{
drawingContext.DrawLine(pen, lastPoint, point);
}
lastPoint = point;
}
Brush brush = Brushes.Red;
foreach (Point point in points)
{
if (point.X >= visibleMinX && point.X <= visibleMaxX)
{
Point center = new Point((point.X - visibleMinX) * visibleWidth / (visibleMaxX - visibleMinX), point.Y);
drawingContext.DrawEllipse(brush, null, center, PointRadius, PointRadius);
}
}
}
protected override Size MeasureOverride(Size availableSize)
{
totalWidth = (int)availableSize.Width;
totalHeight = (int)availableSize.Height;
visibleWidth = totalWidth - ChartMargin * 2;
visibleHeight = totalHeight - ChartMargin * 2;
return new Size(totalWidth, totalHeight);
}
protected override Size ArrangeOverride(Size finalSize)
{
UpdateVisibleRange();
return base.ArrangeOverride(finalSize);
}
private void UpdateVisibleRange()
{
if (points == null || points.Count == 0)
{
return;
}
int visibleMinX = (int)HorizontalOffset;
int visibleMaxX = visibleMinX + visibleWidth;
if (visibleMaxX > maxX)
{
visibleMaxX = maxX;
}
if (visibleMinX < 0)
{
visibleMinX = 0;
}
int startIndex = -1;
int endIndex = -1;
for (int i = 0; i < points.Count; i++)
{
Point point = points[i];
if (point.X >= visibleMinX && point.X <= visibleMaxX)
{
if (startIndex == -1)
{
startIndex = i;
}
endIndex = i;
}
else if (endIndex != -1)
{
break;
}
}
if (startIndex == -1)
{
startIndex = 0;
endIndex = -1;
}
if (startIndex != this.startIndex || endIndex != this.endIndex)
{
this.visibleMinX = visibleMinX;
this.visibleMaxX = visibleMaxX;
this.startIndex = startIndex;
this.endIndex = endIndex;
InvalidateVisual();
}
}
}
这里使用了 FrameworkElement 来自定义控件,重写了 OnRender 方法来绘制曲线和数据点,重写了 MeasureOverride 和 ArrangeOverride 方法来计算控件大小和布局,重写了 UpdateVisibleRange 方法来计算可见区域和可见数据范围。同时使用了 ScrollViewer 控件来实现滚动条的功能。
原文地址: https://www.cveoy.top/t/topic/jNP6 著作权归作者所有。请勿转载和采集!