C# WPF 折线图制作(可以连接数据库)

一、项目核心信息

1. 技术栈

  • 框架:WPF(Windows Presentation Foundation)

  • 数据存储:SQLite + SqlSugar(ORM 框架)

  • 图表展示:LiveCharts.Wpf(开源图表库)

  • 功能定位:可视化展示年度销量数据,支持动态追加月度数据并持久化到数据库。

2. 核心文件说明

文件 / 类 作用
SalesRecord.cs 销量数据实体类,映射 SQLite 中的 SalesRecord
DbHelper.cs 数据库操作工具类,封装 SqlSugar 连接、建表、数据增查逻辑
UCLineChart.xaml 折线图用户控件布局,包含图表容器和追加数据按钮
UCLineChart.xaml.cs 图表逻辑处理,包括数据初始化、图表渲染、动态追加数据等核心逻辑

3.项目结构梳理

复制代码
LineChart2/
├─ DbHelper.cs          // Sugar ORM数据库操作封
├─ SalesRecord.cs       // 销量数据实体类
├─ UCLineChart.xaml        // 折线图用户控件XAML
├─ UCLineChart.xaml.cs     // 折线图逻辑(整合数据库)
├─ MainWindow.xaml         // 主窗口
└─ SalesData.db            // SQLite数据库文件(运行后生成)

二、核心代码解析与使用示例

1. 数据库层使用

(1)SalesRecord 实体(数据映射)
复制代码
// 核心特性说明
[SugarTable("SalesRecord")] // 指定数据库表名
public class SalesRecord
{
    [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] // 主键+自增
    public int Id { get; set; }
​
    [SugarColumn(ColumnName = "Year")] // 映射数据库字段名(可选,默认属性名)
    public int SalesYear { get; set; } // 年份
​
    public int Month { get; set; } // 月份(1-12)
    public double SalesValue { get; set; } // 销量值
    public DateTime CreateTime { get; set; } = DateTime.Now; // 数据创建时间
}

使用示例:创建一条 2024 年 6 月的销量数据

复制代码
var newRecord = new SalesRecord
{
    SalesYear = 2024,
    Month = 6,
    SalesValue = 180.5 // 销量值支持小数,图表会自动格式化
};
(2)DbHelper 工具类(数据库操作)

核心功能 1:初始化数据库连接

复制代码
// 静态构造函数自动初始化,无需手动调用
// 数据库文件路径:程序运行目录/SalesData.db
// 关键配置:自动关闭连接、从特性读取主键配置

核心功能 2:数据操作封装(使用示例)

复制代码
// 1. 批量插入数据(初始化默认数据时使用)
var defaultData = new List<SalesRecord>
{
    new SalesRecord { SalesYear = 2024, Month = 1, SalesValue = 120 },
    new SalesRecord { SalesYear = 2024, Month = 2, SalesValue = 150 }
};
DbHelper.BatchInsertSalesData(defaultData.ToArray());
​
// 2. 插入单条数据并返回自增ID
int newId = DbHelper.InsertSalesData(newRecord);
Console.WriteLine($"新增数据ID:{newId}");
​
// 3. 查询指定年份数据
List<SalesRecord> 2024Data = DbHelper.GetSalesDataByYear(2024);
foreach (var item in 2024Data)
{
    Console.WriteLine($"{item.Month}月销量:{item.SalesValue}");
}
​
// 4. 查询所有数据
List<SalesRecord> allData = DbHelper.GetAllSalesData();

2. 图表层使用

(1)XAML 布局(UCLineChart.xaml)
复制代码
<!-- 核心图表控件配置 -->
<lvc:CartesianChart x:Name="chart"
                    Series="{Binding SeriesCollection}" 
                    LegendLocation="Top"
                    Background="White">
    <!-- Y轴:销量(件),格式化显示整数 -->
    <lvc:CartesianChart.AxisY>
        <lvc:Axis Title="销量(件)" 
                  LabelFormatter="{Binding YFormatter}"
                  Foreground="#333333"/>
    </lvc:CartesianChart.AxisY>
    <!-- X轴:月份,绑定标签数组 -->
    <lvc:CartesianChart.AxisX>
        <lvc:Axis Title="月份" 
                  Labels="{Binding Labels}"
                  Foreground="#333333"/>
    </lvc:CartesianChart.AxisX>
</lvc:CartesianChart>
(2)图表逻辑(UCLineChart.xaml.cs)

核心功能 1:初始化图表数据(从数据库加载)

复制代码
private void InitChartDataFromDb()
{
    SeriesCollection = new SeriesCollection();
    var allSalesData = DbHelper.GetAllSalesData();
​
    // 无数据时初始化默认数据
    if (!allSalesData.Any())
    {
        InitDefaultDataAndWriteToDb(); // 写入2023/2024年1-5月默认数据
        allSalesData = DbHelper.GetAllSalesData();
    }
​
    // 按年份分组构建折线
    var groupedData = allSalesData.GroupBy(r => r.SalesYear);
    foreach (var group in groupedData)
    {
        int year = group.Key;
        var values = group.OrderBy(r => r.Month).Select(r => r.SalesValue).ToList();
​
        // 创建折线系列(不同年份区分样式)
        LineSeries series = new LineSeries
        {
            Title = $"{year}年",
            Values = new ChartValues<double>(values),
            LineSmoothness = 0.3, // 折线平滑度
            StrokeThickness = 2, // 线条粗细
            PointGeometrySize = 8 // 数据点大小
        };
​
        // 样式区分:2024年蓝色圆形,2023年红色方形
        series.Stroke = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
        series.PointForeground = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
        series.PointGeometry = year == 2024 ? DefaultGeometries.Circle : DefaultGeometries.Square;
​
        SeriesCollection.Add(series);
    }
​
    // 构建X轴标签(1-当前最大月份)
    _currentMaxMonth = allSalesData.Max(r => r.Month);
    Labels = Enumerable.Range(1, _currentMaxMonth).Select(m => $"{m}月").ToArray();
}

核心功能 2:动态追加数据(按钮点击事件)

复制代码
private void BtnAddData_Click(object sender, RoutedEventArgs e)
{
    // 1. 月份自增
    _currentMaxMonth++;
    int newMonth = _currentMaxMonth;
​
    // 2. 生成随机销量(100-200)
    double sales2024 = _random.Next(100, 200);
    double sales2023 = _random.Next(100, 200);
​
    // 3. 写入数据库(持久化)
    DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2024, Month = newMonth, SalesValue = sales2024 });
    DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2023, Month = newMonth, SalesValue = sales2023 });
​
    // 4. 更新图表数据点
    SeriesCollection[0].Values.Add(sales2024); // 2024年系列
    SeriesCollection[1].Values.Add(sales2023); // 2023年系列
​
    // 5. 更新X轴标签(修复数组直接扩容的CS0206错误)
    string[] tempLabels = Labels;
    Array.Resize(ref tempLabels, tempLabels.Length + 1);
    tempLabels[tempLabels.Length - 1] = $"{newMonth}月";
    Labels = tempLabels;
​
    // 6. 刷新UI绑定
    chart.AxisX[0].Labels = Labels;
​
    // 7. 更新按钮文本(追加7月→追加8月...)
    (sender as Button).Content = $"追加{newMonth + 1}月数据";
}

3. 完整使用示例(主窗口集成)

步骤 1:在 MainWindow.xaml 中引用控件
复制代码
<Window x:Class="LineChart2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:LineChart2"
        Title="销量折线图" Height="500" Width="800">
    <Grid>
        <!-- 引用自定义折线图控件 -->
        <local:UCLineChart/>
    </Grid>
</Window>
步骤 2:运行效果
  1. 首次运行:

    • 自动创建 SalesData.db 数据库文件;

    • 写入 2023/2024 年 1-5 月默认数据;

    • 图表显示两条折线(2023 年红色方形点,2024 年蓝色圆形点),X 轴标签为 "1 月 - 5 月"。

  2. 点击 "追加 6 月数据" 按钮:

    • 数据库新增 2023/2024 年 6 月随机销量数据;

    • 图表自动追加 6 月数据点,X 轴标签更新为 "1 月 - 6 月";

    • 按钮文本变为 "追加 7 月数据",重复点击可继续追加后续月份。

三、环境配置与依赖

1. NuGet 包安装

包名 版本(建议) 作用
SqlSugarCore 5.1.4.100 SQLite 数据库 ORM 操作
System.Data.SQLite 1.0.118 SQLite 数据提供程序
LiveCharts.Wpf 0.97.0 WPF 图表展示核心库
LiveCharts 0.97.0 LiveCharts 基础核心库

2. 安装命令(Package Manager Console)

复制代码
Install-Package SqlSugarCore -Version 5.1.4.100
Install-Package System.Data.SQLite -Version 1.0.118
Install-Package LiveCharts.Wpf -Version 0.97.0

四、扩展与定制示例

1. 定制折线样式

修改 InitChartDataFromDbLineSeries 的配置:

复制代码
LineSeries series = new LineSeries
{
    Title = $"{year}年",
    Values = new ChartValues<double>(values),
    LineSmoothness = 0.8, // 更平滑的折线(0-1)
    StrokeThickness = 3, // 加粗线条
    PointGeometrySize = 10, // 放大数据点
    Fill = Brushes.LightBlue // 折线下方填充颜色(2024年)
};
// 自定义颜色(如2025年用绿色三角形)
if (year == 2025)
{
    series.Stroke = Brushes.Green;
    series.PointForeground = Brushes.Green;
    series.PointGeometry = DefaultGeometries.Triangle;
}

2. 扩展多年份数据

InitDefaultDataAndWriteToDb 中添加 2025 年数据:

复制代码
// 2025年1-5月数据
double[] sales2025 = { 110, 140, 160, 170, 190 };
for (int i = 0; i < sales2025.Length; i++)
{
    defaultData.Add(new SalesRecord
    {
        SalesYear = 2025,
        Month = i + 1,
        SalesValue = sales2025[i]
    });
}

3. 限制最大月份(仅展示 12 个月)

修改 BtnAddData_Click 事件:

复制代码
private void BtnAddData_Click(object sender, RoutedEventArgs e)
{
    if (_currentMaxMonth >= 12)
    {
        MessageBox.Show("已追加到12月,无法继续追加!");
        (sender as Button).IsEnabled = false;
        return;
    }
    // 原有追加逻辑...
}

五、常见问题与解决

1. 图表无数据显示

  • 检查数据库文件是否生成(程序运行目录 / SalesData.db);

  • 调试 DbHelper.GetAllSalesData() 是否返回数据;

  • 确认 SeriesCollection 已赋值且 DataContext = this; 执行。

2. 追加数据后 X 轴标签不更新

  • 确保执行 chart.AxisX[0].Labels = Labels; 刷新绑定;

  • 检查 Labels 数组扩容逻辑是否正确(必须通过临时变量中转)。

3. 数据库连接失败

  • 确认 System.Data.SQLite 包安装完整,适配项目平台(x86/x64/AnyCPU);

  • 检查连接字符串格式:Data Source={dbPath}; 为 SQLite 标准格式,路径无特殊字符。

六、运行流程总结

  1. 程序启动 → DbHelper 初始化 SQLite 连接 → 自动创建 SalesRecord 表;

  2. UCLineChart 加载 → 从数据库查询数据 → 无数据则写入默认 1-5 月数据;

  3. 按年份分组渲染折线图 → 显示 1-5 月销量;

  4. 点击追加按钮 → 月份 + 1 → 生成随机销量 → 写入数据库 → 更新图表数据和标签;

  5. 重复点击可追加到 12 月,数据持久化到数据库,重启程序后仍能加载历史数据。

七、完整代码

DbHelper.cs

复制代码
using SqlSugar;
using LineChart2;
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
​
namespace LineChart2
{
    public class DbHelper
    {
        // Sugar数据库连接实例(单例)
        public static SqlSugarClient Db { get; private set; }
​
        static DbHelper()
        {
            // 初始化连接
            InitDb();
        }
​
        /// <summary>
        /// 初始化SQLite连接
        /// </summary>
        private static void InitDb()
        {
            // 获取数据库文件路径(项目根目录)
            string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SalesData.db");
​
            Db = new SqlSugarClient(new ConnectionConfig
            {
                ConnectionString = $"Data Source={dbPath};", // SQLite连接字符串
                DbType = DbType.Sqlite, // 数据库类型
                IsAutoCloseConnection = true, // 自动关闭连接
                InitKeyType = InitKeyType.Attribute // 从特性读取主键/自增配置
            });
​
            // 初始化数据表(不存在则创建)
            Db.CodeFirst.InitTables(typeof(SalesRecord));
​
            // 开启日志(调试用,生产可关闭)
            Db.Aop.OnLogExecuting = (sql, pars) =>
            {
                Console.WriteLine($"SQL:{sql} \r\n参数:{string.Join(",", pars.Select(p => p.ParameterName + "=" + p.Value))}");
            };
        }
​
        #region 销量数据操作封装
        /// <summary>
        /// 批量插入销量数据
        /// </summary>
        public static void BatchInsertSalesData(SalesRecord[] records)
        {
            Db.Insertable(records).ExecuteCommand();
        }
​
        /// <summary>
        /// 插入单条销量数据
        /// </summary>
        public static int InsertSalesData(SalesRecord record)
        {
            return Db.Insertable(record).ExecuteReturnIdentity();
        }
​
        /// <summary>
        /// 根据年份查询销量数据
        /// </summary>
        public static List<SalesRecord> GetSalesDataByYear(int year)
        {
            return Db.Queryable<SalesRecord>()
                     .Where(r => r.SalesYear == year)
                     .OrderBy(r => r.Month)
                     .ToList();
        }
​
        /// <summary>
        /// 查询所有年份的销量数据
        /// </summary>
        public static List<SalesRecord> GetAllSalesData()
        {
            return Db.Queryable<SalesRecord>()
                     .OrderBy(r => r.SalesYear)
                     .ToList();
        }
        #endregion
    }
}
​
​

Model.cs

复制代码
using SqlSugar;
using System;
​
namespace LineChart2
{
    /// <summary>
    /// 销量数据表实体
    /// </summary>
    [SugarTable("SalesRecord")]
    public class SalesRecord
    {
        /// <summary>
        /// 主键(自增)
        /// </summary>
        [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { get; set; }
​
        /// <summary>
        /// 年份(2023/2024)
        /// </summary>
        [SugarColumn(ColumnName = "Year")]
        public int SalesYear { get; set; }
​
        /// <summary>
        /// 月份(1-12)
        /// </summary>
        public int Month { get; set; }
​
        /// <summary>
        /// 销量数值
        /// </summary>
        public double SalesValue { get; set; }
​
        /// <summary>
        /// 创建时间
        /// </summary>
        public DateTime CreateTime { get; set; } = DateTime.Now;
    }
}
​
​
​

UCLineChart.xaml

复制代码
<UserControl x:Class="LineChart2.UCLineChart"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:LineChart2"
             xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
​
        <lvc:CartesianChart x:Name="chart"
                            Series="{Binding SeriesCollection}" 
                            LegendLocation="Top"
                            Background="White">
            <lvc:CartesianChart.AxisY>
                <lvc:Axis Title="销量(件)" 
                          LabelFormatter="{Binding YFormatter}"
                          Foreground="#333333"/>
            </lvc:CartesianChart.AxisY>
            <lvc:CartesianChart.AxisX>
                <lvc:Axis Title="月份" 
                          Labels="{Binding Labels}"
                          Foreground="#333333"/>
            </lvc:CartesianChart.AxisX>
        </lvc:CartesianChart>
​
        <!-- 追加数据按钮 -->
        <Button Grid.Row="1" 
                Content="追加6月数据" 
                Width="120" Height="30"
                Margin="10"
                Click="BtnAddData_Click"/>
    </Grid>
</UserControl>
    
    
    

UCLineChart.xaml.cs

复制代码
using LiveCharts;
using LiveCharts.Defaults;
using LiveCharts.Wpf;
using LineChart2;
using LineChart2;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
​
namespace LineChart2
{
    public partial class UCLineChart : UserControl
    {
        public SeriesCollection SeriesCollection { get; set; }
        public string[] Labels { get; set; }
        public Func<double, string> YFormatter { get; set; }
​
        // 模拟销量数据
        private readonly Random _random = new Random();
        // 当前最大月份(用于追加数据)
        private int _currentMaxMonth = 5;
​
        public UCLineChart()
        {
            InitializeComponent();
​
            // 初始化图表数据(优先从数据库读取)
            InitChartDataFromDb();
​
            // Y轴格式化(显示整数)
            YFormatter = value => $"{value:N0}";
​
            DataContext = this;
        }
​
        /// <summary>
        /// 从数据库初始化图表数据
        /// </summary>
        private void InitChartDataFromDb()
        {
            SeriesCollection = new SeriesCollection();
​
            // 1. 查询所有销量数据
            var allSalesData = DbHelper.GetAllSalesData();
​
            // 2. 如果数据库无数据,初始化默认数据并写入数据库
            if (!allSalesData.Any())
            {
                InitDefaultDataAndWriteToDb();
                allSalesData = DbHelper.GetAllSalesData();
            }
​
            // 3. 按年份分组构建折线系列
            var groupedData = allSalesData.GroupBy(r => r.SalesYear);
            foreach (var group in groupedData)
            {
                int year = group.Key;
                var values = group.OrderBy(r => r.Month).Select(r => r.SalesValue).ToList();
​
                // 创建折线系列
                LineSeries series = new LineSeries
                {
                    Title = $"{year}年",
                    Values = new ChartValues<double>(values),
                    LineSmoothness = 0.3,
                    StrokeThickness = 2,
                    PointGeometrySize = 8
                };
​
                // 不同年份设置不同颜色
                series.Stroke = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
                series.PointForeground = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
                series.PointGeometry = year == 2024 ? DefaultGeometries.Circle : DefaultGeometries.Square;
​
                SeriesCollection.Add(series);
            }
​
            // 4. 构建X轴标签(1-当前最大月份)
            _currentMaxMonth = allSalesData.Max(r => r.Month);
            Labels = Enumerable.Range(1, _currentMaxMonth).Select(m => $"{m}月").ToArray();
        }
​
        /// <summary>
        /// 初始化默认数据并写入数据库
        /// </summary>
        private void InitDefaultDataAndWriteToDb()
        {
            List<SalesRecord> defaultData = new List<SalesRecord>();
​
            // 2024年1-5月数据
            double[] sales2024 = { 120, 150, 110, 180, 160 };
            for (int i = 0; i < sales2024.Length; i++)
            {
                defaultData.Add(new SalesRecord
                {
                    SalesYear = 2024,
                    Month = i + 1,
                    SalesValue = sales2024[i]
                });
            }
​
            // 2023年1-5月数据
            double[] sales2023 = { 100, 130, 140, 150, 130 };
            for (int i = 0; i < sales2023.Length; i++)
            {
                defaultData.Add(new SalesRecord
                {
                    SalesYear = 2023,
                    Month = i + 1,
                    SalesValue = sales2023[i]
                });
            }
​
            // 批量写入数据库
            DbHelper.BatchInsertSalesData(defaultData.ToArray());
        }
​
        // 追加数据按钮点击事件
        private void BtnAddData_Click(object sender, RoutedEventArgs e)
        {
            // 1. 月份+1
            _currentMaxMonth++;
            int newMonth = _currentMaxMonth;
​
            // 2. 生成随机销量(100-200)
            double sales2024 = _random.Next(100, 200);
            double sales2023 = _random.Next(100, 200);
​
            // 3. 写入数据库
            DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2024, Month = newMonth, SalesValue = sales2024 });
            DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2023, Month = newMonth, SalesValue = sales2023 });
​
            // 4. 追加数据点到图表
            SeriesCollection[0].Values.Add(sales2024);
            SeriesCollection[1].Values.Add(sales2023);
​
            // 5. 更新X轴标签(修复CS0206错误)
            // 步骤1:将属性赋值给临时变量
            string[] tempLabels = Labels;
            // 步骤2:对临时变量执行数组扩容
            Array.Resize(ref tempLabels, tempLabels.Length + 1);
            // 步骤3:给扩容后的数组赋值新标签
            tempLabels[tempLabels.Length - 1] = $"{newMonth}月";
            // 步骤4:将临时变量赋值回属性
            Labels = tempLabels;
​
            // 6. 刷新绑定(触发UI更新)
            chart.AxisX[0].Labels = Labels;
​
            // 更新按钮文本(追加7月、8月...)
            (sender as Button).Content = $"追加{newMonth + 1}月数据";
        }
    }
}
​
​
​

MainWindow.xaml

复制代码
<Window x:Class="LineChart2.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:LineChart2"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Grid Margin="10">
            <!-- 自定义折线图用户控件 -->
            <local:UCLineChart x:Name="ucLineChart"
                           HorizontalAlignment="Stretch"
                           VerticalAlignment="Stretch"
                           Background="#F5F5F5"
                           BorderBrush="#E0E0E0"
                           BorderThickness="1"
                           />
​
        </Grid>
    </Grid>
</Window>
​

效果展示:

相关推荐
kylezhao20192 小时前
C# 中的委托(Delegate)与事件(Event)
c#·c#上位机
Han.miracle2 小时前
MySQL 用户与权限精细化管理实战:从创建到权限回收全流程
数据库
lzhdim2 小时前
C#应用程序取得当前目录和退出
开发语言·数据库·microsoft·c#
last_zhiyin2 小时前
Oracle sql tuning guide 翻译 Part 4-1 --- 连接操作(Joins)
数据库·sql·oracle
wuguan_2 小时前
C#之接口
c#·接口
老华带你飞2 小时前
农产品销售管理|基于springboot农产品销售管理系统(源码+数据库+文档)
数据库·vue.js·spring boot
电商API&Tina2 小时前
跨境电商速卖通(AliExpress)数据采集与 API 接口接入全方案
大数据·开发语言·前端·数据库·人工智能·python
-suiyuan-2 小时前
sqli-labs靶场1~2笔记
数据库·sql
瀚高PG实验室3 小时前
在Highgo DB 中创建MySQL兼容函数datediff
数据库·mysql·瀚高数据库