绘制图表
在 WinForms 应用中进行数据可视化,使用功能强大且易于集成的第三方库是最高效的选择。本章将详细介绍 ScottPlot,一个专为 .NET 设计的优秀开源绘图库,并提供多种实用场景的详细代码示例。
为何选择 ScottPlot?
- 现代且易用: API 设计简洁直观,学习曲线平缓。
- 高性能: 对大数据量和实时数据展示有很好的优化,性能卓越。
- 交互性强: 内置支持鼠标缩放、平移等交互功能。
- 集成方便: 提供专门的
ScottPlot.WinForms
包,可以像使用普通Panel
一样在设计器中拖拽和布局。 - 开源免费: 可用于商业项目。
入门指南
1. 安装
通过 Visual Studio 的 NuGet 包管理器控制台安装 ScottPlot.WinForms
:
安装后,请重新编译你的项目,FormsPlot
控件即会出现在 Visual Studio 的工具箱中。
2. 添加图表控件
从工具箱中拖拽一个 FormsPlot
控件到你的窗体上。它就是一个标准的 WinForms 控件,可以设置 Dock
, Anchor
等属性来控制其布局。假设其 Name
属性为 formsPlot1
。
3. 官方文档
ScottPlot 提供了非常详尽的官方文档和示例,你可以在其官网上找到:
基础图表类型
曲线图/散点图 (Scatter Plot)
这是最常见的图表类型,用于展示数据点和趋势。
// 1. 准备数据
double[] days = { 1, 2, 3, 4, 5, 6, 7 };
double[] temperatures = { 12, 15, 17, 16, 18, 17, 20 };
// 2. 在 FormsPlot 控件上添加一个散点图
var scatter = formsPlot1.Plot.Add.Scatter(days, temperatures);
// 3. (可选) 自定义线条和标记点的样式
scatter.Color = ScottPlot.Colors.DarkBlue;
scatter.LineWidth = 2;
scatter.MarkerSize = 10;
scatter.MarkerShape = ScottPlot.MarkerShape.FilledCircle;
scatter.Label = "每日气温"; // 用于图例显示
// 4. (可选) 自定义图表标题和轴标签
formsPlot1.Plot.Title("一周气温趋势");
formsPlot1.Plot.XLabel("日期 (天)");
formsPlot1.Plot.YLabel("平均气温 (摄氏度)");
formsPlot1.Plot.ShowLegend(); // 显示图例
// 5. 刷新图表以应用更改
formsPlot1.Refresh();
柱状图 (Bar Plot)
柱状图非常适合比较不同类别的数据。
// 1. 准备数据
double[] sales = { 150, 320, 280, 410, 380 };
string[] productNames = { "苹果", "香蕉", "樱桃", "葡萄", "橙子" };
// 2. 创建柱状图
formsPlot1.Plot.Add.Bars(productNames, sales);
// 3. 自定义
formsPlot1.Plot.Title("水果销量对比");
formsPlot1.Plot.YLabel("销量 (公斤)");
// 4. 刷新
formsPlot1.Refresh();
饼图 (Pie Chart)
饼图用于展示各部分占整体的比例。
// 1. 准备数据
var slices = new ScottPlot.PieSlice[]
{
new() { Value = 30, FillColor = ScottPlot.Colors.Red, Label = "市场部" },
new() { Value = 40, FillColor = ScottPlot.Colors.Blue, Label = "研发部" },
new() { Value = 25, FillColor = ScottPlot.Colors.Green, Label = "销售部" },
new() { Value = 5, FillColor = ScottPlot.Colors.Gray, Label = "行政部" },
};
// 2. 添加饼图
var pie = formsPlot1.Plot.Add.Pie(slices);
// 3. (可选) 自定义
pie.ShowLabels = true; // 在每个扇区上显示标签
formsPlot1.Plot.Title("各部门人员占比");
// 4. 刷新
formsPlot1.Refresh();
交互与动态更新
鼠标交互
ScottPlot 默认启用鼠标交互: - 左键拖拽: 平移图表。 - 右键拖拽: 缩放图表。 - 滚轮滚动: 缩放图表。
你可以通过修改 formsPlot1.Interaction
对象的属性来禁用或自定义这些行为。
获取鼠标坐标
可以订阅 MouseMove
事件来获取鼠标在图表数据坐标系中的实时位置。
formsPlot1.MouseMove += (s, e) =>
{
// 从屏幕像素坐标转换为图表数据坐标
Pixel mousePixel = new(e.Location.X, e.Location.Y);
Coordinates mouseCoords = formsPlot1.Plot.GetCoordinates(mousePixel);
// 在窗口标题栏或 StatusStrip 中显示坐标
this.Text = $"X: {mouseCoords.X:F2}, Y: {mouseCoords.Y:F2}";
};
实时更新图表
要实现动态实时更新的图表,可以使用 System.Windows.Forms.Timer
。为了获得最佳性能,我们应该直接修改绘图对象的数据源,而不是每次都 Clear()
和 Add()
。
注意: 对于高频更新(如每秒数百次),应使用 Signal
图表类型,它为大数据和实时渲染做了极致优化。以下示例适用于低频更新场景。
using ScottPlot.Plottables;
public partial class RealTimeForm : Form
{
private System.Windows.Forms.Timer _updateTimer = new();
private Scatter _realtimePlot;
private List<double> _xData = new();
private List<double> _yData = new();
private int _nextDataIndex = 0;
public RealTimeForm()
{
InitializeComponent();
// 1. 初始化一个空的绘图对象,并传入可变的数据源 (List<T>)
_realtimePlot = formsPlot1.Plot.Add.Scatter(_xData, _yData);
_realtimePlot.Color = ScottPlot.Colors.Red;
formsPlot1.Plot.Title("实时数据");
// 2. 设置定时器
_updateTimer.Interval = 500; // 0.5秒
_updateTimer.Tick += (s, e) => UpdateData();
_updateTimer.Start();
}
private void UpdateData()
{
// 3. 向数据源添加新数据
_xData.Add(_nextDataIndex++);
_yData.Add(new Random().NextDouble() * 100);
// (可选) 如果数据量过大,可以移除旧数据
if (_xData.Count > 100)
{
_xData.RemoveAt(0);
_yData.RemoveAt(0);
}
// 4. 自动调整坐标轴范围以适应新数据
formsPlot1.Plot.Axes.AutoScale();
// 5. 刷新图表
formsPlot1.Refresh();
}
}
高级定制
自定义轴刻度标签
如果希望坐标轴显示文字标签而不是数字,可以自定义刻度生成器。
double[] positions = { 0, 1, 2, 3 };
double[] values = { 10, 25, 18, 32 };
// 创建标签数组
var labels = new string[] { "春季", "夏季", "秋季", "冬季" };
formsPlot1.Plot.Add.Bars(positions, values);
// 创建一个自定义的刻度生成器
var customTickGenerator = new ScottPlot.TickGenerators.NumericManual();
for (int i = 0; i < positions.Length; i++)
{
customTickGenerator.Add(positions[i], labels[i]);
}
// 将其应用到X轴
formsPlot1.Plot.Axes.Bottom.TickGenerator = customTickGenerator;
// 确保标签不旋转
formsPlot1.Plot.Axes.Bottom.TickLabelStyle.Rotation = 0;
formsPlot1.Refresh();
多Y轴(次坐标轴)
当需要在一张图上展示两种不同单位的数据时(例如温度和降雨量),可以使用次Y轴。
// 1. 准备两组数据
double[] temp = { 20, 22, 24, 23, 25 };
double[] rainfall = { 5, 2, 10, 15, 8 };
double[] days = { 1, 2, 3, 4, 5 };
// 2. 创建第二个Y轴
var yAxis2 = formsPlot1.Plot.Axes.AddRightAxis("降雨量 (mm)");
// 3. 绘制第一组数据(使用默认左侧Y轴)
var tempPlot = formsPlot1.Plot.Add.Scatter(days, temp);
formsPlot1.Plot.Axes.Left.Label.Text = "温度 (°C)";
// 4. 绘制第二组数据,并将其关联到新的Y轴
var rainPlot = formsPlot1.Plot.Add.Bars(days, rainfall);
rainPlot.Axes.YAxis = yAxis2; // 关键步骤
formsPlot1.Refresh();