由于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 方法来绘制曲线和数据点,重写了 MeasureOverrideArrangeOverride 方法来计算控件大小和布局,重写了 UpdateVisibleRange 方法来计算可见区域和可见数据范围。同时使用了 ScrollViewer 控件来实现滚动条的功能。

WPF 高效绘制290万数据走势曲线图:虚拟化技术和滚动条实现

原文地址: https://www.cveoy.top/t/topic/jNP6 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录