WPF中LiveCharts2折线图的实现与优化指南

0 次阅读

LiveCharts2 是面向 .NET 的现代化图表库,支持 WPF、WinForms、Avalonia、MAUI 等多个 UI 平台。相比传统图表组件,它提供了更灵活的数据绑定方式、较丰富的交互能力以及更现代的 API。对于 WPF 桌面程序来说,折线图尤其适合展示实时监控、数据趋势、性能指标、统计结果等信息。

下面结合实际项目,介绍 LiveCharts2 在 WPF 中实现折线图的基本方式,并进一步说明坐标轴、数据标签、样式、动态刷新以及性能优化等常见问题。

安装LiveCharts2

创建 WPF 项目后,可以通过 NuGet 安装 LiveChartsCore.SkiaSharpView.WPF。

使用 Visual Studio 的 NuGet 包管理器搜索:

LiveChartsCore.SkiaSharpView.WPF

也可以通过 NuGet 控制台安装:

PowerShell
Install-Package LiveChartsCore.SkiaSharpView.WPF

如果使用 .NET CLI,则可以执行:

Bash
dotnet add package LiveChartsCore.SkiaSharpView.WPF

LiveCharts2 的核心绘图能力基于 SkiaSharp,因此 WPF 项目通常直接使用 LiveChartsCore.SkiaSharpView.WPF 即可。

安装完成后,需要在 XAML 中引入命名空间:

XML
xmlns:lvc="http://schemas.livecharts.com/2.0/wpf"

创建最简单的WPF折线图

LiveCharts2 中的折线图主要由 LineSeries 表示,最简单的方式是准备一个数值集合,然后绑定到 Series 属性。

XAML:

XML
 x:Class="WpfChartDemo.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:lvc="http://schemas.livecharts.com/2.0/wpf"
        Title="LiveCharts2折线图"
        Width="900"
        Height="500">

    
         Series="{Binding Series}">
        
    

后台代码:

C#
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;

public partial class MainWindow
{
    public ISeries[] Series { get; set; }

    public MainWindow()
    {
        InitializeComponent();

        Series =
        [
            new LineSeries<double>
            {
                Values = [10, 25, 18, 35, 30, 48, 42]
            }
        ];

        DataContext = this;
    }
}

运行程序后,就可以看到一条基础折线。

CartesianChart 是笛卡尔坐标图控件,而 LineSeries 表示使用 double 类型数据绘制折线。这样的结构非常适合 WPF 的数据绑定模式。

配置折线图坐标轴

实际业务中,仅仅显示一条线通常是不够的。合理设置 X 轴和 Y 轴可以明显提升图表的可读性。

例如:

C#
using LiveChartsCore.SkiaSharpView;

public Axis[] XAxes { get; set; } =
[
    new Axis
    {
        Labels = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
    }
];

public Axis[] YAxes { get; set; } =
[
    new Axis
    {
        Name = "销售额",
        MinLimit = 0
    }
];

XAML:

XML

    Series="{Binding Series}"
    XAxes="{Binding XAxes}"
    YAxes="{Binding YAxes}" />

这样 X 轴就会显示对应的星期标签,Y 轴则从 0 开始。

如果数据存在固定范围,也可以设置最大值:

C#
new Axis
{
    Name = "数量",
    MinLimit = 0,
    MaxLimit = 100
}

不过对于动态数据,通常不建议把最大值写死,否则数据超出范围后可能影响图表的展示效果。

设置折线颜色和线条样式

LiveCharts2 支持通过 StrokeFillGeometrySize 等属性调整折线外观。

例如:

C#
using LiveChartsCore.SkiaSharpView;
using SkiaSharp;

Series =
[
    new LineSeries<double>
    {
        Values = [10, 25, 18, 35, 30, 48, 42],
        Stroke = new SolidColorPaint(SKColors.DodgerBlue)
        {
            StrokeThickness = 3
        },
        GeometrySize = 8
    }
];

其中:

  • Stroke 控制折线颜色和线宽;

  • StrokeThickness 控制线条粗细;

  • GeometrySize 控制数据点大小。

如果希望隐藏数据点,可以将 GeometrySize 设置为较小的值,或者根据具体版本 API 采用对应的几何图形配置。

给折线图添加区域填充

趋势图有时需要突出折线以下的区域,可以配置 Fill

C#
new LineSeries<double>
{
    Values = [10, 25, 18, 35, 30, 48, 42],
    Stroke = new SolidColorPaint(SKColors.DodgerBlue)
    {
        StrokeThickness = 3
    },
    Fill = new SolidColorPaint(SKColors.LightBlue)
}

对于后台数据监控界面,这种方式能够增强视觉层次。

不过填充区域并不适合所有场景。如果同时显示多条折线,多个填充区域可能互相遮挡,此时通常只给主要数据系列设置填充效果。

显示多个折线系列

LiveCharts2 很适合进行多组数据对比。

例如同时显示本周和上周数据:

C#
Series =
[
    new LineSeries<double>
    {
        Name = "本周",
        Values = [12, 28, 25, 40, 35, 52, 48]
    },
    new LineSeries<double>
    {
        Name = "上周",
        Values = [10, 22, 30, 32, 38, 45, 43]
    }
];

XAML:

XML

    Series="{Binding Series}"
    XAxes="{Binding XAxes}"
    YAxes="{Binding YAxes}" />

通过 Name 设置系列名称后,可以配合图例显示不同数据系列。

多折线场景尤其适合:

  • 产品销量趋势比较;

  • CPU 与内存使用率比较;

  • 多台服务器性能比较;

  • 不同时间段数据比较;

  • 实际值与预测值比较。

配置图例

图例能够帮助用户快速理解每条折线代表什么。

可以在图表中配置:

XML

    Series="{Binding Series}"
    XAxes="{Binding XAxes}"
    YAxes="{Binding YAxes}"
    LegendPosition="Bottom">

常见位置包括顶部、底部、左侧和右侧。

如果页面空间有限,可以考虑不显示图例,而直接通过标题、标签或者自定义 UI 说明数据含义。

使用MVVM实现数据绑定

WPF 项目通常采用 MVVM 架构,因此不建议把大量图表逻辑全部写在窗口后台代码中。

可以创建一个 ViewModel:

C#
using LiveChartsCore;
using LiveChartsCore.SkiaSharpView;

public class ChartViewModel
{
    public ISeries[] Series { get; }

    public Axis[] XAxes { get; }

    public Axis[] YAxes { get; }

    public ChartViewModel()
    {
        Series =
        [
            new LineSeries<double>
            {
                Name = "访问量",
                Values = [120, 180, 150, 230, 280, 260, 310]
            }
        ];

        XAxes =
        [
            new Axis
            {
                Labels = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
            }
        ];

        YAxes =
        [
            new Axis
            {
                Name = "访问次数",
                MinLimit = 0
            }
        ];
    }
}

窗口中:

C#
public MainWindow()
{
    InitializeComponent();
    DataContext = new ChartViewModel();
}

这种结构可以把数据和界面分离,更适合中大型 WPF 项目。

动态更新折线图数据

实时监控系统往往需要不断向折线图添加数据。例如每秒获取一次温度:

C#
private readonly Random _random = new();

private void UpdateChart()
{
    var series = (LineSeries<double>)Series[0];

    var values = series.Values?.ToArray() ?? [];

    var newValues = values.Append(
        20 + _random.NextDouble() * 10
    ).ToArray();

    series.Values = newValues;
}

如果只是少量数据,这种方式比较直观。

但对于实时数据量较大的程序,不建议每次更新都创建一个全新的数组。频繁分配数组可能增加 GC 压力,同时导致不必要的图表刷新。

实际项目中可以使用 LiveCharts2 提供的可变集合机制,例如:

C#
public ObservableCollection<double> Values { get; } =
[
    10, 20, 15, 30
];

然后:

C#
Values.Add(35);

这种方式更适合持续变化的数据源。

实现实时滚动折线图

实时监控场景通常不是无限保存数据,而是只保留最近一段时间的数据。

例如只显示最近 100 个数据点:

C#
Values.Add(newValue);

if (Values.Count > 100)
{
    Values.RemoveAt(0);
}

这种设计可以避免数据量随着程序运行时间不断增长。

如果数据每秒更新一次,那么 100 个点大约代表最近 100 秒。实际项目可以根据业务需求设置为 60、300、1000 等数量。

对于高频采样数据,更推荐先在数据层进行降采样或聚合,而不是把每一条原始数据全部交给 UI 绘制。

折线图性能优化

LiveCharts2 虽然具有较好的绘图性能,但 WPF 图表性能最终仍然受到数据规模、刷新频率和 UI 线程负载的影响。

控制数据量

最直接的优化方式是限制显示数据量。

例如:

C#
const int MaxPoints = 500;

Values.Add(value);

while (Values.Count > MaxPoints)
{
    Values.RemoveAt(0);
}

对于实时监控而言,用户通常只关心最近一段时间,因此没有必要让一个折线系列长期保存几十万甚至上百万个点。

降低刷新频率

假设后台每 10 毫秒采集一次数据,但屏幕每秒刷新 10~20 次已经足够,那么完全没有必要让图表每 10 毫秒都重绘。

可以在数据采集和 UI 展示之间增加缓冲层:

数据采集
   ↓
缓存队列
   ↓
定时聚合
   ↓
图表更新
   ↓
WPF界面

这种设计可以有效降低 UI 线程压力。

避免频繁替换Series

如果只是数据发生变化,不建议频繁执行:

C#
Series = new ISeries[]
{
    new LineSeries<double>
    {
        Values = newValues
    }
};

这种做法会导致图表结构本身不断发生变化。

更合理的方式是保持 Series 不变,只更新其数据集合。

处理缺失数据

业务数据中经常存在空值。例如某个时间点没有采集到服务器温度。

这时需要根据业务决定如何处理。

如果希望折线直接断开,可以考虑使用支持空值的类型:

C#
new LineSeries<double?>
{
    Values = [20, 22, null, 25, 28]
}

如果希望图表保持连续,则可以在数据层进行插值或者使用前一个有效值,但这会改变数据表达方式,因此不应该为了图表好看而随意填充缺失数据。

对于监控类系统,保留断点往往更符合真实数据含义。

处理大量数据时的优化思路

当折线图需要展示几十万甚至百万级数据时,仅仅调整 LiveCharts2 配置通常无法从根本上解决性能问题。

一个常见错误是:

数据库 → 查询全部数据 → 创建百万个Point → WPF绘制

更合理的方式是:

数据库
  ↓
按时间范围查询
  ↓
数据聚合/降采样
  ↓
保留关键数据点
  ↓
LiveCharts2
  ↓
WPF渲染

例如一天有 86400 个秒级数据点,但当前窗口只有 1200 像素宽。如果一个像素对应几十个数据点,那么完全没有必要把所有点都原样绘制出来。

可以按照时间窗口进行聚合,例如计算:

  • 最小值;

  • 最大值;

  • 平均值;

  • 首值;

  • 尾值。

对于波动明显的数据,单纯使用平均值可能丢失峰值,因此可以采用 Min/Max 保留策略,使缩放后的曲线仍然能够体现异常波动。

时间轴折线图

如果 X 轴表示真实时间,而不是简单的类别标签,那么可以直接使用 DateTime 作为数据点。

例如:

C#
public class DataPoint
{
    public DateTime Time { get; set; }

    public double Value { get; set; }
}

数据:

C#
var data = new[]
{
    new DataPoint
    {
        Time = DateTime.Now.AddMinutes(-3),
        Value = 21.5
    },
    new DataPoint
    {
        Time = DateTime.Now.AddMinutes(-2),
        Value = 22.8
    },
    new DataPoint
    {
        Time = DateTime.Now.AddMinutes(-1),
        Value = 23.1
    }
};

具体绘制方式可以根据 LiveCharts2 当前版本的数据类型和轴格式化 API 进行配置。

时间轴尤其适用于:

  • 温度监控;

  • CPU 使用率;

  • 网络流量;

  • 股票价格;

  • 设备运行参数;

  • IoT 传感器数据。

需要注意的是,时间轴数据最好保证时间顺序正确,否则折线可能出现异常回折。

添加数据提示

用户把鼠标移动到折线附近时,通常希望看到具体数值。

LiveCharts2 提供了 Tooltip 相关能力,可以结合系列和主题进行配置。

实际项目中,Tooltip 内容最好包含业务信息,例如:

时间:14:35:20
CPU:68.5%
服务器:Server-01

而不是只显示一个孤立的数字。

对于数据监控系统,这类交互可以明显提高图表的使用效率。

让折线图更加适合业务页面

一个完整的数据看板通常不应该只放一个原始图表,而可以围绕图表增加辅助信息:

┌─────────────────────────────────────┐
│ CPU使用率                            │
│ 当前:68.5%   峰值:91.2%   平均:62% │
├─────────────────────────────────────┤
│                                     │
│        ╱╲      ╱╲                   │
│   ╱───╯  ╲────╯  ╲───              │
│                                     │
├─────────────────────────────────────┤
│ 最近24小时                           │
└─────────────────────────────────────┘

这种布局能够让用户在查看趋势的同时快速获得当前值、峰值和平均值。

因此,LiveCharts2 的使用重点并不只是“把折线画出来”,而是要让图表与业务数据结合起来。

常见问题排查

图表完全不显示

首先检查:

C#
DataContext = this;

或者 ViewModel 是否正确绑定。

然后确认 XAML 中的命名空间是否正确:

XML
xmlns:lvc="http://schemas.livecharts.com/2.0/wpf"

同时检查 Series 是否为空。

修改数据后图表不刷新

如果使用普通数组,并且只是修改数组内部元素,有时并不能达到预期的通知效果。

更推荐使用 LiveCharts2 支持的数据集合,或者直接重新设置数据源。

对于 MVVM 项目,还需要确认数据属性是否正确实现通知机制。

图表刷新卡顿

优先检查三个指标:

  1. 数据点数量是否过大;

  2. 更新频率是否过高;

  3. 是否在 UI 线程执行了大量计算。

如果后台线程持续产生数据,不应该把数据库查询、计算、数据转换等复杂操作全部放在 UI 线程。

可以采用:

后台线程采集数据
        ↓
ConcurrentQueue缓存
        ↓
定时器批量处理
        ↓
UI线程更新图表

这样通常比“每来一条数据就立即刷新图表”更加稳定。

LiveCharts2折线图开发建议

实际开发过程中,可以按照以下原则设计:

数据量较小的静态图表

直接使用 LineSeries 和数组即可,代码简单、维护成本低。

普通业务统计图

使用 MVVM,将 SeriesXAxesYAxes 放到 ViewModel 中,通过数据绑定管理图表。

实时监控图表

使用可变数据集合,并限制最大数据点数量,同时控制刷新频率。

海量历史数据

不要直接把所有原始数据交给图表,应在数据库查询层、服务层或数据处理层进行聚合和降采样。

多系列数据

明确每个系列的名称、单位和含义,合理配置图例、Tooltip 和坐标轴,避免用户无法区分多条曲线。

总结

WPF 中使用 LiveCharts2 实现折线图并不复杂,核心结构可以概括为 CartesianChart + LineSeries + Axis。通过数据绑定可以快速完成静态趋势图,而结合 MVVM、可变数据集合和定时刷新机制,则能够进一步构建实时数据监控界面。

真正影响项目体验的往往不是折线图能否正常显示,而是数据量、刷新频率、坐标轴设计和交互方式。数据量较小时可以优先考虑开发效率;进入实时监控或海量数据场景后,则应该重点控制数据点数量和 UI 刷新频率。

合理使用这些优化策略,LiveCharts2 可以很好地满足 WPF 项目中的数据统计、趋势分析、设备监控和实时数据可视化需求。