做数据可视化的时候,折线图画出来了,数据也对了,但总觉得少点什么——图表太"干",领导看一眼就划走,用户盯着屏幕也读不出重点。
这不是设计能力的问题,而是图表类型选错了。
折线图适合趋势对比,但如果你想让用户一眼感受到数据的"量感"——比如销售额的堆积、温度的波动范围、流量的峰谷变化——那面积图(AreaSeries)才是正解。更进一步,渐变填充能让视觉层次感直接拉满,区域越大颜色越深,区域收窄颜色自然淡去,数据的高低起伏在视觉上变得极其直观。
本文基于 LiveCharts 2(LiveChartsCore.SkiaSharpView.WinForms),从零到一带你实现:
代码可直接运行,拿去就能用。
很多开发者在做监控面板或数据报表时,第一反应是折线图。折线图确实简洁,但它有一个致命弱点:视觉重量感不足。
用折线图展示"某月每日销售额",用户看到的是一条线在波动,但很难直觉上感知"这个月整体销量是多是少"。面积图通过填充线条以下的区域,把趋势 + 量感同时传递给用户,认知负担大幅降低。
而普通的纯色填充又容易显得呆板,尤其在深色主题或多系列叠加时,颜色块堆在一起辨识度很差。渐变填充的核心价值在于:
LiveCharts 2 的 LinearGradientPaint 正是为此而生,但官方文档在 WinForms 场景下的示例相当有限,很多开发者折腾半天找不到正确姿势。下面我们一步步来。
在动手之前,有几个概念值得先搞清楚,避免后面踩坑。
LiveCharts 2 的绘制引擎是 SkiaSharp,这意味着所有的颜色、画笔、渐变都走 Skia 的 API,而不是 WinForms 原生的 System.Drawing。两套体系不互通,混用会报错。
AreaSeries<T> 有两个关键画笔属性:
Stroke:控制上方折线的样式Fill:控制填充区域的样式普通纯色填充用 SolidColorPaint,渐变填充用 LinearGradientPaint。LinearGradientPaint 接收一个颜色数组和渐变方向,颜色从上到下(或任意方向)过渡,配合透明度(Alpha 通道)就能实现"上深下淡"的经典面积图效果。
另一个常见误区是忘记设置 GeometrySize = 0。默认情况下,AreaSeries 在每个数据点上会画一个小圆点,数据量大时这些圆点会严重影响性能和美观。实际项目里通常直接把它设为 0 隐藏掉。
这是最基础的使用场景:单系列数据,渐变从主色调过渡到透明,清晰展示趋势。
首先通过 NuGet 安装依赖:
LiveChartsCore.SkiaSharpView.WinForms
目前稳定版本为 2.0.0-rc2 系列,建议锁定版本避免 API 变动。
新建一个 WinForms 项目,在 Form1.cs 中:
csharpusing LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.WinForms;
using SkiaSharp;
namespace AppLiveChart15
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
InitChart();
}
private void InitChart()
{
var salesData = new double[]
{
120, 145, 132, 178, 165, 190, 210,
198, 223, 245, 230, 267, 289, 275,
301, 318, 295, 340, 328, 356, 372,
360, 389, 401, 385, 420, 445, 432, 460, 478
};
var gradientFill = new LinearGradientPaint(
new[]
{
new SKColor(33, 150, 243, 180),
new SKColor(33, 150, 243, 20)
},
new SKPoint(0.5f, 0f),
new SKPoint(0.5f, 1f)
);
// 我记得以前有一个 AreaSeries
var areaSeries = new LineSeries<double>
{
Values = salesData,
Name = "月销售额(万元)",
Stroke = new SolidColorPaint(new SKColor(33, 150, 243))
{
StrokeThickness = 2
},
// 设置 Fill 即可实现面积图效果
Fill = gradientFill,
GeometrySize = 0,
LineSmoothness = 0.65
};
var cartesianChart = new CartesianChart
{
Dock = DockStyle.Fill,
Series = new ISeries[] { areaSeries },
XAxes = new[]
{
new Axis
{
Name = "日期",
LabelsRotation = 0
}
},
YAxes = new[]
{
new Axis
{
Name = "销售额(万元)",
MinLimit = 0
}
}
};
Controls.Add(cartesianChart);
}
}
}

运行后你会看到一条蓝色平滑曲线,曲线以下区域从顶部的半透明蓝色渐变到底部的几乎透明,整体既有层次感又不会遮挡背景。MinLimit = 0 这一行很关键——Y 轴如果不从 0 开始,面积区域会被截断,"量感"大打折扣。
实际项目里经常需要对比两组数据,比如"本年度 vs 上年度"、"实际值 vs 预测值"。多系列面积图叠加时,透明度的控制是关键,否则前景系列会完全遮挡背景系列。
csharpusing LiveChartsCore;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using LiveChartsCore.SkiaSharpView.WinForms;
using SkiaSharp;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
namespace AppLiveChart15
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
InitMultiSeriesChart();
}
private void InitMultiSeriesChart()
{
// 本年度数据
var currentYear = new double[]
{
210, 245, 232, 278, 265, 290, 310,
298, 323, 345, 330, 367, 389
};
// 上年度数据
var lastYear = new double[]
{
180, 195, 210, 225, 240, 255, 270,
260, 285, 300, 290, 315, 330
};
// 本年度:蓝色渐变,顶部透明度稍高
var currentFill = new LinearGradientPaint(
new[]
{
new SKColor(33, 150, 243, 160),
new SKColor(33, 150, 243, 10)
},
new SKPoint(0.5f, 0f),
new SKPoint(0.5f, 1f)
);
// 上年度:橙色渐变,整体透明度更低,避免遮挡
var lastFill = new LinearGradientPaint(
new[]
{
new SKColor(255, 152, 0, 120),
new SKColor(255, 152, 0, 10)
},
new SKPoint(0.5f, 0f),
new SKPoint(0.5f, 1f)
);
var currentSeries = new LineSeries<double>
{
Values = currentYear,
Name = "本年度",
Stroke = new SolidColorPaint(new SKColor(33, 150, 243)) { StrokeThickness = 2.5f },
Fill = currentFill,
GeometrySize = 0,
LineSmoothness = 0.5
};
var lastSeries = new LineSeries<double>
{
Values = lastYear,
Name = "上年度",
Stroke = new SolidColorPaint(new SKColor(255, 152, 0)) { StrokeThickness = 2f },
Fill = lastFill,
GeometrySize = 0,
LineSmoothness = 0.5
};
var chart = new CartesianChart
{
Dock = DockStyle.Fill,
Series = new ISeries[] { currentSeries, lastSeries },
YAxes = new[] { new Axis { MinLimit = 0 } },
// 图例显示在右侧
LegendPosition = LiveChartsCore.Measure.LegendPosition.Right
};
Controls.Add(chart);
}
}
}

多系列叠加时有一个经典的坑:两个系列的 Fill 透明度都设置太高,结果颜色混在一起完全分不清。经验值是:主系列顶部 Alpha 控制在 140~180,次要系列控制在 80~120,底部统一接近 0。这样视觉上既有区分度,又保持了整体的轻盈感。
另外,LineSmoothness 设为 1 时曲线会过度弯曲,在数据波动剧烈时会出现曲线"下穿"X 轴的问题。推荐保持在 0.5~0.7 之间。
监控类应用里,数据是实时推入的。LiveCharts 2 支持 ObservableCollection 或 ObservableValue,数据变化时图表自动刷新,不需要手动重绘。
csharpusing System.Collections.ObjectModel;
using LiveChartsCore.Defaults;
public partial class Form1 : Form
{
private ObservableCollection<ObservableValue> _liveData;
private System.Windows.Forms.Timer _timer;
public Form1()
{
InitializeComponent();
InitLiveChart();
}
private void InitLiveChart()
{
// 初始化20个数据点
_liveData = new ObservableCollection<ObservableValue>(
Enumerable.Range(0, 20).Select(_ => new ObservableValue(0))
);
var gradientFill = new LinearGradientPaint(
new[]
{
new SKColor(76, 175, 80, 180), // 绿色,顶部半透明
new SKColor(76, 175, 80, 15) // 底部接近透明
},
new SKPoint(0.5f, 0f),
new SKPoint(0.5f, 1f)
);
var liveSeries = new AreaSeries<ObservableValue>
{
Values = _liveData,
Name = "实时CPU使用率(%)",
Stroke = new SolidColorPaint(new SKColor(76, 175, 80)) { StrokeThickness = 2 },
Fill = gradientFill,
GeometrySize = 0,
LineSmoothness = 0.4
};
var chart = new CartesianChart
{
Dock = DockStyle.Fill,
Series = new ISeries[] { liveSeries },
YAxes = new[] { new Axis { MinLimit = 0, MaxLimit = 100 } }
};
Controls.Add(chart);
// 每500ms推入一个新数据点,模拟实时监控
_timer = new System.Windows.Forms.Timer { Interval = 500 };
var rng = new Random();
_timer.Tick += (s, e) =>
{
_liveData.RemoveAt(0); // 移除最旧的点
_liveData.Add(new ObservableValue(rng.Next(20, 95))); // 推入新数据
};
_timer.Start();
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
_timer?.Stop();
_timer?.Dispose();
base.OnFormClosed(e);
}
}

这个方案模拟了一个 CPU 使用率的实时监控面板,绿色渐变面积图在滚动更新,视觉效果非常流畅。RemoveAt(0) + Add() 的组合实现了滑动窗口,内存占用稳定,不会随运行时间增长。
问题一:渐变方向不对,颜色从左到右而不是从上到下
原因是 SKPoint 的坐标是相对坐标(0~1),(0,0) 是左上角,(1,1) 是右下角。从上到下渐变应该是 (0.5f, 0f) → (0.5f, 1f),从左到右则是 (0f, 0.5f) → (1f, 0.5f)。
问题二:图表在高 DPI 屏幕上模糊
在 Program.cs 中添加 DPI 感知声明:
csharpApplication.SetHighDpiMode(HighDpiMode.SystemAware);
问题三:多图表同时存在时内存持续增长
LiveCharts 2 的图表控件实现了 IDisposable,窗体关闭时务必调用 chart.Dispose(),否则 SkiaSharp 的 native 资源不会及时释放。
以下数据在 Windows 11 / i7-12700H / .NET 6.0 / Release 模式 下测试:
| 场景 | 数据点数量 | 渲染帧率 | 内存占用 |
|---|---|---|---|
| 单系列静态面积图 | 1,000 点 | 稳定 60fps | ~45MB |
| 双系列静态面积图 | 各 500 点 | 稳定 60fps | ~52MB |
| 单系列动态滚动 | 窗口 50 点 | 稳定 60fps | ~48MB |
| 单系列大数据量 | 10,000 点 | ~28fps | ~110MB |
数据点超过 5,000 时建议开启数据降采样(LiveCharts 2 内置 DataLabelsSize 和采样策略),或在数据层做预聚合后再传入图表。
你在项目里用面积图展示过哪类数据?是实时监控、财务报表,还是用户行为分析?渐变配色上有没有踩过什么坑,欢迎在评论区聊聊你的实践经验。
另外抛一个思考题:如果需要在面积图上叠加阈值警戒线(比如超过 80% 就变红),你会怎么实现? 这个需求在监控场景里很常见,实现方式有好几种,感兴趣的可以评论区讨论。
本文围绕 LiveCharts 2 的 Area 展开了三个渐进式方案,这个在新版本中实际还是LineSeries,通过Fill颜色来完成的:
LinearGradientPaint 渐变填充,适合大多数数据展示场景ObservableCollection 驱动的动态滚动面积图,适合实时监控类应用渐变填充的核心不在于"好看",而在于用视觉语言降低用户的认知成本——颜色深浅本身就在传递信息,这才是数据可视化的本质价值。
完整工程代码结构清晰,可直接在项目中集成。建议从方案一入手,跑通渐变逻辑后再按需扩展到多系列和动态场景。
#C# #WinForms #LiveCharts2 #数据可视化 #SkiaSharp
相关信息
我用夸克网盘给你分享了「AppLiveChart15.zip」,点击链接或复制整段内容,打开「夸克APP」即可获取。
/2e653YWxcB:/
链接:https://pan.quark.cn/s/96256842308f
提取码:ymSK
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!