资讯动态

WinForms Chart控件实战:曲线图与柱状图绘制技巧及性能优化

发布时间:2026/10/13 7:39:44 来源:尧图企业网站定制
1. 为什么 WinForms 里 Chart 控件依然是曲线与柱状图的首选方案刚入行那会儿带我的师傅扔过来一个需求把采集到的温度数据实时画成曲线还要能切换成柱状图看每天的均值。我第一反应是找第三方图表库结果折腾了一圈发现WinForms 自带的System.Windows.Forms.DataVisualization.Charting命名空间下的 Chart 控件其实已经能覆盖八成以上的常规绘图需求。这个控件从 .NET Framework 4.0 开始就内置在框架里不需要额外引入任何 NuGet 包对于做工业上位机、数据监控面板、报表工具这类场景来说省去了依赖管理的麻烦。Chart 控件本质上是一个容器里面可以挂载多个ChartArea绘图区域、Series数据系列、Legend图例和Title标题。曲线图和柱状图的区别核心就在于Series的ChartType属性——设成Spline或Line就是曲线设成Column或Bar就是柱状图。听起来简单但实际用起来从数据绑定、坐标轴配置到实时刷新每一步都有坑。我见过不少项目图表画出来要么卡顿要么坐标轴乱飘要么数据点一多就糊成一团根子都在对 Chart 内部机制理解不够。这篇文章面向的是正在用 C# 做桌面端数据可视化的开发者不管你是在做设备监控、销售报表还是实验数据分析只要涉及到曲线和柱状图的绘制这里的内容都能直接拿去用。我会从整体设计思路讲起把 Series 和 ChartArea 的关系掰开揉碎然后给出曲线图和柱状图的完整实现代码最后把我这些年踩过的坑和排查技巧整理成速查表。代码基于 .NET Framework 4.7.2 和 .NET 6 的 WinForms 都验证过差异点我会单独标注。2. Chart 控件的整体架构与核心对象拆解2.1 Chart、ChartArea、Series 三者的关系很多人第一次用 Chart 控件看到属性面板里一堆东西就懵了。其实你只需要抓住三个核心对象Chart是画布ChartArea是画布上的一块绘图区域Series是画在这块区域上的数据系列。一个 Chart 可以包含多个 ChartArea一个 ChartArea 又可以承载多个 Series。这种设计的好处是你可以在同一张图上叠加不同类型的图表——比如柱状图表示月度销量曲线图表示同比增长率两者共用一个 X 轴但各自有独立的 Y 轴。我画个简单的结构示意文字描述Chart 容器 → ChartArea[0] → Series[0]柱状、Series[1]曲线ChartArea[0] 还关联了 AxisX、AxisY、AxisX2、AxisY2 四个坐标轴对象。Series 通过ChartArea属性指定自己归属哪个绘图区域通过ChartType决定绘制形态。Legend 和 Title 则是挂在 Chart 级别的不属于某个具体的 ChartArea。理解这个层级关系之后很多配置问题就迎刃而解了。比如你想让曲线和柱状图用不同的 Y 轴刻度那就需要给 Series 分别指定YAxisType为Primary和Secondary然后在 ChartArea 里配置AxisY和AxisY2的属性。再比如你想让两个图表并排显示那就创建两个 ChartArea分别设置它们的Position属性用百分比定义在画布上的位置和大小。2.2 Series 的 ChartType 选型曲线、柱状、样条的区别Series.ChartType是一个枚举常用的几种我列一下ChartType 值效果适用场景Line折线图数据点之间用直线连接趋势展示数据点密集时首选Spline样条曲线数据点之间用平滑曲线连接需要视觉平滑效果的场景但会引入插值误差Column垂直柱状图分类数据对比如每月销量Bar水平柱状图分类名称较长时横向排列更易读Area面积图强调累积量或占比Scatter散点图展示数据分布不连接点选型的时候有个经验如果你的数据是等间隔采样的时间序列用Line就够了Spline虽然好看但它会在数据点之间做三次样条插值可能产生原始数据中不存在的极值点。我做过一个温度监控项目客户要求曲线平滑我用了Spline结果在温度骤变的地方曲线出现了过冲看起来像是温度超过了实际峰值后来换回Line并调细了线宽才解决。柱状图这边Column和Bar的区别就是方向。Column的 X 轴是分类轴Y 轴是数值轴Bar反过来。如果你的分类标签文字比较长比如华东区域第一季度销售额用Bar会更舒服因为文字可以横向排列不用旋转。2.3 数据绑定方式Points 集合 vs DataSourceChart 的数据绑定有两种方式。第一种是直接操作Series.Points集合手动AddXY或AddY。这种方式最直观适合数据量不大或者需要频繁局部更新的场景。第二种是设置Series.DataSource为一个DataTable或List然后通过XValueMember和YValueMembers指定字段名。这种方式适合从数据库或集合批量加载数据。我个人的习惯是实时刷新场景用Points手动管理因为可以精确控制每次只更新变化的部分静态报表场景用DataSource代码更简洁。但要注意DataSource绑定后如果直接修改源数据图表不会自动刷新需要重新赋值或调用DataBind()。这一点和 WPF 的绑定机制不一样WinForms 的 Chart 没有实现INotifyPropertyChanged的自动监听。还有一个细节Points.AddXY的参数是object类型你可以传DateTime、double、int都行Chart 内部会根据坐标轴的ValueType自动转换。但如果 X 轴是DateTime类型记得把AxisX.LabelStyle.Format设成HH:mm:ss之类的格式否则默认显示的是ToString()的结果可能又长又乱。3. 曲线图绘制的完整实现与关键参数配置3.1 从零搭建一个实时温度曲线假设我们要做一个温度监控面板每秒钟采集一次数据在 Chart 上画出最近 60 秒的温度曲线。先看界面初始化部分的代码private void InitTemperatureChart() { // 清除默认的 Series 和 ChartArea chart1.Series.Clear(); chart1.ChartAreas.Clear(); chart1.Legends.Clear(); chart1.Titles.Clear(); // 创建绘图区域 ChartArea area new ChartArea(TempArea); area.AxisX.LabelStyle.Format HH:mm:ss; area.AxisX.MajorGrid.LineColor Color.LightGray; area.AxisX.MajorGrid.LineDashStyle ChartDashStyle.Dash; area.AxisY.Minimum 0; area.AxisY.Maximum 100; area.AxisY.Interval 10; area.AxisY.Title 温度 (℃); area.AxisX.Title 时间; area.BackColor Color.WhiteSmoke; chart1.ChartAreas.Add(area); // 创建曲线系列 Series series new Series(Temperature); series.ChartType SeriesChartType.Line; series.ChartArea TempArea; series.BorderWidth 2; series.Color Color.OrangeRed; series.XValueType ChartValueType.DateTime; series.YValueType ChartValueType.Double; series.IsValueShownAsLabel false; series.MarkerStyle MarkerStyle.Circle; series.MarkerSize 5; series.MarkerColor Color.DarkRed; chart1.Series.Add(series); // 添加标题 chart1.Titles.Add(实时温度监控); chart1.Titles[0].Font new Font(微软雅黑, 12, FontStyle.Bold); }这段代码里有几个关键点值得展开。AxisX.LabelStyle.Format设成HH:mm:ss之后X 轴上的时间标签就会按这个格式显示而不是默认的完整日期时间字符串。MajorGrid是主网格线我习惯把颜色调淡一点并设成虚线这样不会喧宾夺主。AxisY.Minimum和Maximum固定了 Y 轴范围避免数据波动时坐标轴自动缩放导致视觉上的跳动——这在监控场景里很重要否则你盯着看的时候会觉得曲线在呼吸。Series的MarkerStyle设成Circle会在每个数据点上画一个小圆点。数据点少的时候这样很好看但如果 60 个点全画上圆点曲线会显得很拥挤。我的做法是数据点少于 20 个时显示 Marker超过就设成MarkerStyle.None只保留线条。3.2 实时数据追加与滚动窗口的实现数据采集通常在一个定时器或后台线程里进行。这里要注意Chart 控件不是线程安全的所有对Series.Points的操作必须在 UI 线程上执行。我一般用System.Windows.Forms.Timer它的Tick事件本身就在 UI 线程上省去了Invoke的麻烦。如果数据采集在后台线程那就必须用chart1.Invoke(new Action(() { ... }))来更新。private void timer1_Tick(object sender, EventArgs e) { double temp ReadTemperature(); // 模拟读取温度 DateTime now DateTime.Now; Series series chart1.Series[Temperature]; series.Points.AddXY(now, temp); // 保持最近 60 个点 while (series.Points.Count 60) { series.Points.RemoveAt(0); } // 让 X 轴自动跟随最新时间 chart1.ChartAreas[TempArea].AxisX.Minimum series.Points[0].XValue; chart1.ChartAreas[TempArea].AxisX.Maximum series.Points[series.Points.Count - 1].XValue; }这里有个性能陷阱每次AddXY之后如果调用chart1.Refresh()或chart1.Invalidate()在数据量大时会导致界面闪烁和 CPU 占用飙升。实际上 Chart 控件在Points集合变化后会自动重绘不需要手动触发。我实测过每秒追加一个点、保持 60 个点的窗口CPU 占用不到 1%。但如果每秒追加 100 个点、保持 10000 个点那就需要考虑用Series.Points.SuspendUpdates()和ResumeUpdates()来批量操作或者改用DataSource绑定并定期整体替换。另外AxisX.Minimum和Maximum的设置时机也有讲究。如果在AddXY之前设置新加的点可能落在范围外如果在之后设置又可能因为Points集合变化导致坐标轴短暂错乱。我的做法是先把点加进去、移除旧点最后再统一设置坐标轴范围这样最稳。3.3 曲线样式微调线宽、颜色、平滑度与空点处理线宽通过BorderWidth控制单位是像素。我一般设 2 到 3太细了在投影仪上看不清太粗了多条曲线会糊在一起。颜色方面Color属性设的是线条颜色MarkerColor是数据点标记的颜色两者可以不同。如果有多条曲线建议用一组对比度高的颜色比如Color.OrangeRed、Color.SteelBlue、Color.SeaGreen、Color.Goldenrod避免用相近的色系。平滑度就是前面说的Spline和Line的选择。如果非要用Spline可以调Series[Temp].SetCustomProperty(LineTension, 0.5)来控制张力值越小越接近折线越大越平滑。默认是 0.8我一般调到 0.3 到 0.5 之间既有一点平滑效果又不会产生明显的过冲。空点处理是个容易被忽略的问题。传感器偶尔会返回无效值如果你直接AddXY一个double.NaN曲线会在那个位置断开。这其实是好事比画一条错误的直线过去要诚实。但如果你想让曲线跳过空点继续连接可以设series.EmptyPointStyle.Color Color.Transparent并配合series.IsXValueIndexed true不过这样 X 轴的刻度会变成索引而不是实际时间需要权衡。4. 柱状图绘制的分类轴配置与多系列对比4.1 分类轴与数值轴的本质区别柱状图和曲线图最大的不同在于 X 轴的性质。曲线图的 X 轴通常是连续的数值轴或时间轴而柱状图的 X 轴是分类轴。在 Chart 控件里当你把ChartType设成Column或Bar时X 轴会自动切换成AxisX的IsLabelAutoFit模式每个数据点占据一个等宽的槽位标签显示在槽位下方。这里有个常见的困惑如果你用AddXY(1, 100)、AddXY(2, 200)、AddXY(3, 150)添加数据X 轴会显示 1、2、3 作为标签。但如果你用AddXY(一月, 100)这样传字符串Chart 会把它当作分类标签处理。两种方式都可以但混用会导致坐标轴类型混乱。我的建议是柱状图统一用字符串标签通过Points.AddXY(标签, 值)的方式添加这样 X 轴自动变成分类轴标签居中显示在柱子下方。private void InitBarChart() { chart1.Series.Clear(); chart1.ChartAreas.Clear(); ChartArea area new ChartArea(BarArea); area.AxisX.MajorGrid.Enabled false; // 分类轴不需要垂直网格线 area.AxisY.MajorGrid.LineColor Color.LightGray; area.AxisY.Title 销售额 (万元); area.AxisX.Interval 1; // 每个分类都显示标签 chart1.ChartAreas.Add(area); Series series new Series(Sales); series.ChartType SeriesChartType.Column; series.ChartArea BarArea; series.Color Color.SteelBlue; series.IsValueShownAsLabel true; // 在柱子上显示数值 series.LabelFormat F1; // 数值保留一位小数 series.Font new Font(微软雅黑, 9); chart1.Series.Add(series); // 添加数据 series.Points.AddXY(一月, 120.5); series.Points.AddXY(二月, 98.3); series.Points.AddXY(三月, 145.7); series.Points.AddXY(四月, 132.1); series.Points.AddXY(五月, 167.9); }IsValueShownAsLabel true会在每根柱子顶部显示数值LabelFormat F1控制格式为一位小数。如果数值很大标签可能会超出绘图区域被截断这时候可以调area.AxisY.Maximum留出顶部空间或者把series.SmartLabelStyle.Enabled true打开让 Chart 自动调整标签位置避免重叠。4.2 多系列柱状图的并排与堆叠实际业务里经常需要对比多个系列比如今年和去年的月度销售额。这时候有两种呈现方式并排Clustered和堆叠Stacked。并排是默认行为多个 Series 的柱子会在同一个分类槽位里并排显示堆叠需要设置series[SeriesName].SetCustomProperty(StackedGroupName, Group1)同一组的 Series 会堆叠在一起。// 并排柱状图 Series series2023 new Series(2023年); series2023.ChartType SeriesChartType.Column; series2023.Color Color.SteelBlue; // ... 添加数据 Series series2024 new Series(2024年); series2024.ChartType SeriesChartType.Column; series2024.Color Color.OrangeRed; // ... 添加数据 chart1.Series.Add(series2023); chart1.Series.Add(series2024);并排模式下每个分类槽位会被平均分配给各个 Series柱子宽度自动计算。如果柱子太窄可以调series[SeriesName].SetCustomProperty(PointWidth, 0.8)值范围 0 到 1表示柱子占槽位宽度的比例。默认是 0.8调到 1 就是柱子紧挨着。堆叠模式适合展示总量 构成的场景。比如展示每月的总销售额同时用不同颜色区分线上和线下渠道。设置StackedGroupName之后同一组的 Series 会垂直堆叠Y 轴的总高度就是各系列之和。注意堆叠模式下IsValueShownAsLabel显示的是每个系列自己的值不是累计值如果你需要显示累计值得手动计算并设置Label属性。4.3 柱状图数值标签与坐标轴刻度优化柱状图的数值标签有几个实用技巧。第一如果柱子是负数比如亏损月份标签会显示在柱子下方这时候可以设series[Sales].SmartLabelStyle.Enabled true让 Chart 自动避让。第二如果数值太大导致标签太长可以用LabelFormat做单位换算比如0.0,万或者自定义PostBack事件来格式化。第三series.Font可以单独设置标签字体和坐标轴字体分开控制。坐标轴刻度方面柱状图的 Y 轴通常从 0 开始不要设负数最小值否则柱子会从中间某处开始画视觉上容易误导。AxisY.Interval可以手动指定刻度间隔如果不设Chart 会自动计算一个好看的间隔比如 20、50、100。但自动计算有时候会给出 33 这种不规整的值我一般会根据数据范围手动设一个最大值 168 的话设Interval 50Maximum 200这样刻度是 0、50、100、150、200看起来很清爽。X 轴的Interval 1确保每个分类标签都显示。如果分类很多比如 31 天的日报标签会挤在一起这时候可以设Interval 5每隔 5 个显示一个或者把LabelStyle.Angle设成 -45 度让标签倾斜排列。倾斜角度不要超过 60 度否则阅读起来很费劲。5. 双轴混合图与动态数据更新的进阶玩法5.1 曲线与柱状图共存的配置要点把曲线和柱状图画在同一张图上最常见的场景是柱状图表示绝对值曲线表示增长率或占比。这种图的关键在于两个 Series 使用不同的 Y 轴。配置步骤如下// 柱状图系列使用主 Y 轴 Series barSeries new Series(销量); barSeries.ChartType SeriesChartType.Column; barSeries.YAxisType AxisType.Primary; barSeries.ChartArea MixedArea; // 曲线系列使用次 Y 轴 Series lineSeries new Series(增长率); lineSeries.ChartType SeriesChartType.Line; lineSeries.YAxisType AxisType.Secondary; lineSeries.ChartArea MixedArea; lineSeries.BorderWidth 3; // 配置次 Y 轴 ChartArea area chart1.ChartAreas[MixedArea]; area.AxisY2.Enabled AxisEnabled.True; area.AxisY2.Title 增长率 (%); area.AxisY2.LabelStyle.Format P0; // 百分比格式 area.AxisY2.MajorGrid.Enabled false; // 避免和主 Y 轴网格线重叠这里有个细节AxisY2.MajorGrid.Enabled一定要设成false否则两组网格线交错在一起图会显得很乱。另外次 Y 轴的颜色最好和曲线颜色一致这样读者能直观地对应起来。可以设area.AxisY2.LineColor lineSeries.Color和area.AxisY2.LabelStyle.ForeColor lineSeries.Color。图例的位置也需要注意。默认图例在右上角可能会挡住曲线。可以设chart1.Legends[0].Docking Docking.Top把它放到顶部或者Docking Docking.Bottom放到底部。如果图例项太多可以设chart1.Legends[0].LegendStyle LegendStyle.Table让它以表格形式排列。5.2 大数据量下的性能优化策略当数据点超过几千个时Chart 的绘制性能会明显下降。我做过一个测试在普通办公电脑上Line类型画 10000 个点大约需要 200 毫秒刷新一次如果每秒刷新 10 次CPU 就吃不消了。优化手段有几个第一减少Marker的显示。MarkerStyle MarkerStyle.None可以省去大量绘制开销。第二关闭动画效果。chart1.AnimationsEnabled false在实时刷新场景下是必须的否则每次更新都会触发动画卡到怀疑人生。第三使用Series.Points.SuspendUpdates()和ResumeUpdates()包裹批量操作series.Points.SuspendUpdates(); try { for (int i 0; i newData.Length; i) { series.Points.AddXY(newData[i].Time, newData[i].Value); } // 移除旧数据 while (series.Points.Count maxPoints) series.Points.RemoveAt(0); } finally { series.Points.ResumeUpdates(); }第四如果数据量实在太大比如 10 万点以上考虑做数据抽稀。比如每 10 个点取一个平均值或最大值把 10 万点降到 1 万点再画。视觉上几乎看不出差别但性能提升十倍。抽稀的算法可以用等间隔采样、LTTBLargest Triangle Three Buckets或者简单的滑动窗口平均。LTTB 的效果最好能保留曲线的关键拐点但实现稍复杂网上有现成的 C# 实现可以参考。5.3 图表导出为图片与打印的实操Chart 控件内置了SaveImage方法可以把当前图表保存为 PNG、JPEG、BMP 等格式chart1.SaveImage(chart_output.png, ChartImageFormat.Png);如果要控制导出图片的尺寸和分辨率可以创建一个Bitmap然后调用chart1.DrawToBitmapBitmap bmp new Bitmap(1920, 1080); chart1.DrawToBitmap(bmp, new Rectangle(0, 0, 1920, 1080)); bmp.Save(chart_hd.png, ImageFormat.Png);注意DrawToBitmap在 Chart 控件上有个已知问题如果控件当前不可见比如在 TabPage 的未选中页里画出来的可能是空白。解决办法是先把控件临时显示出来或者用chart1.SaveImage配合chart1.Width和Height的临时调整。我一般用SaveImage的重载版本直接指定宽高chart1.SaveImage(chart_export.png, ChartImageFormat.Png);打印的话可以把 Chart 的Printing相关属性配置好然后调用chart1.Printing.Print(true)直接打印或者用PrintPreviewDialog做打印预览。打印前记得把背景色设成白色否则默认的灰色背景打印出来很费墨。6. 常见问题排查与避坑经验速查6.1 图表不显示或显示异常的排查清单现象可能原因解决方法图表一片空白Series 没有添加到 Chart或 ChartArea 名不匹配检查chart1.Series.Add()和series.ChartArea的值曲线画在坐标轴外面X 轴或 Y 轴的 Minimum/Maximum 设置不当暂时注释掉 Min/Max 设置让 Chart 自动缩放数据点显示为 0AddXY的参数顺序搞反了确认是AddXY(x, y)而不是AddXY(y, x)时间轴标签显示为数字XValueType没设成DateTime设series.XValueType ChartValueType.DateTime柱状图柱子重叠多个 Series 没有正确分组检查是否需要设StackedGroupName或调整PointWidth刷新时界面闪烁频繁调用Refresh()或Invalidate()去掉手动刷新让 Chart 自动重绘大数据量卡顿数据点过多Marker 和动画开销大关闭动画、隐藏 Marker、做数据抽稀这个表里的每一行都是我实际遇到过的。特别是数据点显示为 0这一条我见过至少三个新手犯这个错误因为AddXY的签名是AddXY(object xValue, params object[] yValue)参数顺序很容易记反。记住X 在前Y 在后和数学里的坐标写法一致。6.2 坐标轴刻度乱跳与标签重叠的解决坐标轴刻度乱跳通常发生在实时刷新场景。原因是每次AddXY之后Chart 会自动重新计算坐标轴范围如果数据波动大范围就会频繁变化。解决办法是固定AxisY.Minimum和Maximum或者用AxisY.ScaleView做视图缩放。ScaleView的好处是数据可以超出视图范围但显示区域固定用户可以通过滚动条查看历史数据。标签重叠的问题在 X 轴分类较多时很常见。除了前面说的倾斜标签和设置Interval还有一个技巧是启用AxisX.IsLabelAutoFit true并设置LabelAutoFitStylearea.AxisX.IsLabelAutoFit true; area.AxisX.LabelAutoFitStyle LabelAutoFitStyles.LabelsAngleStep30 | LabelAutoFitStyles.StaggeredLabels;StaggeredLabels会让标签上下错开排列相当于两行显示能有效缓解拥挤。但注意这个属性在数据点动态变化时可能导致标签位置跳动如果介意的话还是手动设Interval更稳定。6.3 实时刷新导致的内存增长与释放长时间运行的监控程序如果每秒钟往Points里加数据但不清理内存会持续增长。我见过一个跑了三天的程序内存从 50MB 涨到 2GB最后直接崩溃。根本原因就是Points集合只增不减。解决办法很简单每次添加新点后检查Points.Count超过阈值就RemoveAt(0)移除最旧的。但要注意RemoveAt(0)是 O(n) 操作如果集合很大比如 10 万点每次移除都会导致大量数据搬移。这时候应该用循环队列的思路或者定期整体重建Points集合。另一个内存泄漏点是事件订阅。如果你在每次刷新时都new一个Series并Add进去旧的 Series 没有从chart1.Series里移除就会一直堆积。正确的做法是初始化时创建好 Series后续只更新Points不要反复创建和添加 Series。6.4 跨线程更新 Chart 的安全写法前面提过 Chart 不是线程安全的但实际项目中数据采集往往在后台线程。如果你直接在后台线程操作series.Points.AddXY运气好可能没事运气不好就会抛InvalidOperationException或者界面卡死。安全的写法是用Invoke或BeginInvokeprivate void OnDataReceived(double value) { if (chart1.InvokeRequired) { chart1.BeginInvoke(new Actiondouble(OnDataReceived), value); return; } // 这里已经在 UI 线程上了 series.Points.AddXY(DateTime.Now, value); // ... 清理旧数据 }用BeginInvoke而不是Invoke的好处是不会阻塞后台线程。但如果数据来得太快BeginInvoke会在消息队列里堆积大量待执行的操作导致界面响应变慢。这时候需要加一个节流机制比如用一个bool标志位如果上一次更新还没执行完就跳过这次数据或者把数据缓存起来批量更新。7. 从实际项目里攒下来的几条硬核经验做数据可视化这些年我最大的体会是图表好不好看是次要的数据表达准不准确才是第一位的。我见过太多为了好看而牺牲准确性的例子——用Spline导致曲线过冲、Y 轴不从 0 开始导致柱状图对比失真、用 3D 效果导致数据点位置难以判断。这些坑我都踩过所以现在做图表我的原则是能用Line就不用Spline柱状图 Y 轴必须从 0 开始坚决不用 3D 效果。另一个经验是关于颜色。很多人喜欢用 Chart 默认的调色板但默认颜色在投影仪上往往对比度不够。我一般会自定义一组颜色确保在灰度打印时也能区分。具体做法是选一组明度差异明显的颜色比如深蓝、橙红、墨绿、金黄这样即使打印成黑白也能通过灰度深浅区分不同的系列。还有一点是关于数据点的密度。曲线图的数据点不是越多越好。如果 X 轴范围是 24 小时你画了 86400 个点每秒一个屏幕上根本分辨不出来反而浪费性能。合理的做法是根据显示区域的像素宽度来决定数据点数量——比如绘图区域宽 800 像素那最多画 800 个点就够了多出来的做抽稀。这样既保证了视觉精度又控制了性能开销。最后说一个关于Chart控件版本兼容性的问题。.NET Framework 4.0 到 4.8 的 Chart 控件在 API 上基本一致但 .NET Core 和 .NET 5 的 WinForms 需要单独安装System.Windows.Forms.DataVisualizationNuGet 包版本 1.0.0 左右。这个包在 .NET 6 上能用但有一些细微差异比如SaveImage的某些重载可能不存在。如果你在 .NET 6 上遇到编译错误检查一下包版本或者改用DrawToBitmap方案。我在 .NET 6 项目里实测过基本的曲线和柱状图绘制完全没问题性能甚至比 .NET Framework 还好一点因为底层的 GDI 调用优化了。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑