
1. 为什么要把柱形图和折线图塞进同一个绘图区WinForms 里做数据看板最常遇到的一类需求就是同一张图上既要看绝对量比如每天的不良数量又要看比率比如不良率。如果拆成两张图上下排鼠标滚来滚去对不上时间轴老板看一眼就皱眉。把柱形图和折线图叠加到同一个 ChartArea再给 X 轴挂一个滚动条长周期数据也能一屏一屏翻着看这是最省事也最直观的做法。这篇就围绕 System.Windows.Forms.DataVisualization.Charting 这个内置控件把「柱形图 折线图同图叠加 双 Y 轴 X 轴滚动条」这套组合拳拆开讲。适合正在写 WinForms 报表、MES 看板、设备监控界面的同学代码可以直接复制到你的项目里改数据源。核心检索词先摆出来C# Chart 控件、柱形图、折线图、滚动条、双 Y 轴、ScaleView、AxisX.ScrollBar。下面从控件准备讲到运行验证每一步都给可复制的配置。2. 前置准备控件、命名空间与数据形态2.1 引入 Chart 控件在 Visual Studio 的工具箱里Chart 控件默认就在「数据」分组下如果没看到右键工具箱 → 选择项 → 勾选 System.Windows.Forms.DataVisualization。拖一个到窗体上默认名字是 chart1。它内部会自动带一个 ChartArea1 和一个 Series1我们要做的是再加一个 Series并让两个 Series 共用同一个 ChartArea。命名空间记得加using System.Windows.Forms.DataVisualization.Charting; using System.Drawing;2.2 数据长什么样假设你从数据库查出来两张表或者一张表两个字段柱形图数据X 轴是日期或班次字符串Y 轴是数量double。折线图数据X 轴同样的日期或班次Y 轴是比率0~1 之间的小数显示时转百分比。两条 Series 的 X 值必须一一对应否则叠加后折线会错位。我习惯在内存里先整理成两个List(string X, double Y)再统一 AddXY这样比在循环里直接读 DataTable 更可控。2.3 双 Y 轴的分工左侧主轴 AxisY 给柱形图用右侧副轴 AxisY2 给折线图用。折线图的 Series 要设置YAxisType AxisType.Secondary这样它才会去读 AxisY2 的刻度。百分比格式就挂在 AxisY2 的 LabelStyle 上。3. 可复制的 ChartArea 与 Series 配置3.1 初始化 ChartArea背景、3D、网格先把绘图区的基础样式定下来。3D 关掉背景给一个浅蓝X 轴网格线去掉间隔设为 1避免日期标签挤成一团。private void InitChartArea() { ChartArea area chart1.ChartAreas[ChartArea1]; // 关闭 3D柱形图叠加折线时 3D 会让折线飘起来很难看 area.Area3DStyle.Enable3D false; area.Area3DStyle.LightStyle LightStyle.Realistic; // 背景色浅蓝和白色窗体区分开 area.BackColor Color.FromArgb(64, 165, 191, 228); // X 轴主网格线去掉减少视觉噪音 area.AxisX.MajorGrid.LineWidth 0; area.AxisX.Interval 1; area.AxisX.IntervalOffset 1; area.AxisX.LabelStyle.IsStaggered true; // 标签交错显示防重叠 // 右侧副轴百分比格式 area.AxisY2.LabelStyle.Format 0.00%; area.AxisY2.Title 比率; area.AxisY.Title 数量; }3.2 配置 X 轴滚动条与缩放滚动条的关键在AxisX.ScrollBar.Enabled和AxisX.ScaleView这一组属性。ScaleView.Size 决定一屏显示多少个点Position 决定从第几个点开始。CursorX 那几行是让用户能用鼠标拖选、框选缩放。private void InitScrollBar() { ChartArea area chart1.ChartAreas[ChartArea1]; // 开启 X 轴滚动条 area.AxisX.ScrollBar.Enabled true; area.AxisX.ScrollBar.ButtonColor Color.Silver; area.AxisX.ScrollBar.LineColor Color.Black; // 需要两端按钮都显示时打开下面这行 // area.AxisX.ScrollBar.ButtonStyle ScrollBarButtonStyles.All; // 允许鼠标滚轮自动滚动 area.CursorX.AutoScroll true; area.CursorX.IsUserEnabled true; area.CursorX.IsUserSelectionEnabled true; // 允许缩放 area.AxisX.ScaleView.Zoomable true; area.AxisX.ScaleView.Position 1; // 从第 1 个点开始 area.AxisX.ScaleView.Size 10; // 一屏显示 10 个点 }注意ScaleView.Size 如果设成 0 或者大于总点数滚动条会失效或者整屏显示。数据点少于 10 个时把 Size 调小或直接不设。3.3 配置两条 Series柱形 折线Series[0] 做柱形Series[1] 做折线并绑定副轴。折线的 MarkerStyle、BorderWidth、Label 格式都在这里定。private void InitSeries() { // 第一条柱形图走左轴 Series bar chart1.Series[0]; bar.ChartType SeriesChartType.Column; bar.Name 数量; bar.IsValueShownAsLabel true; bar.YAxisType AxisType.Primary; // 第二条折线图走右轴 Series line chart1.Series[1]; line.ChartType SeriesChartType.Line; line.Name 比率; line.YAxisType AxisType.Secondary; // 关键绑定右侧副轴 line.IsValueShownAsLabel true; line.MarkerStyle MarkerStyle.Circle; line.MarkerSize 8; line.BorderWidth 2; line.Color Color.Red; line.MarkerColor Color.BlueViolet; line.Label #VAL{P}; // 标签显示百分比 }3.4 填充数据数据填充时柱形图直接 AddXY折线图的比率值要转成 0~1 的小数因为 AxisY2 的格式是0.00%它会自动乘 100 显示。private void BindData(List(string X, double Y) barData, List(string X, double Y) lineData) { chart1.Series[0].Points.Clear(); chart1.Series[1].Points.Clear(); foreach (var item in barData) chart1.Series[0].Points.AddXY(item.X, item.Y); foreach (var item in lineData) chart1.Series[1].Points.AddXY(item.X, item.Y); // Y 传 0.85 这种小数 }如果你是从 DataTable 来的注意列名和行遍历顺序别把 X 和 Y 搞反。原代码里用双重循环遍历 DataTable 的列和行那种写法在列数多的时候容易把列名当 X 值建议先转成明确的键值对再绑定。4. 运行验证确认叠加与滚动都正常4.1 验证双图叠加编译运行后先看三件事第一柱形和折线是否在同一个绘图区。如果折线跑到图外面或者贴着顶多半是 YAxisType 没设成 Secondary或者 AxisY2 的 Maximum/Minimum 没给范围。第二右侧 Y 轴刻度是否显示成百分比。如果显示的是 0.85 而不是 85.00%检查AxisY2.LabelStyle.Format 0.00%有没有生效以及折线数据传的是不是小数。第三折线上的数据点标签是否显示成百分比。Label #VAL{P}配合小数数据会显示成 85.00%。4.2 验证滚动条鼠标滚轮在绘图区滚动X 轴应该跟着平移。拖动滚动条两端的按钮或者拖中间的滑块视图应该跟着变。如果滚动条灰着不能动检查AxisX.ScrollBar.Enabled和ScaleView.Size是否合理。再试一下框选按住鼠标左键在绘图区拖一个矩形松开后应该放大到选中区间。这是CursorX.IsUserSelectionEnabled true的效果。4.3 验证长周期数据造 100 个点的数据ScaleView.Size 设 10滚动条应该能从头拖到尾。翻到最后一屏时折线和柱形仍然对齐没有错位。如果发现翻页后折线断开检查两条 Series 的 X 值是否严格一一对应。5. 本篇常见错误排查5.1 折线不显示或跑到图外最常见的原因是YAxisType没设成AxisType.Secondary。折线默认走左轴而左轴的量级是几百上千比率 0.85 在左轴上几乎贴着底。设成副轴后还要确认 AxisY2 的 Maximum 和 Minimum 合理比如 0 到 1。5.2 滚动条不出现三个检查点AxisX.ScrollBar.Enabled true有没有写ScaleView.Size是否小于总点数ChartArea 的AxisX.ScaleView.Zoomable是否为 true。另外如果数据点太少比如只有 5 个而 Size 设了 10滚动条不会出现因为一屏就装下了。5.3 百分比显示成 0.85 而不是 85%LabelStyle.Format 0.00%只对轴刻度生效Series 的 Label 要用#VAL{P}才会按百分比格式化。两者要分别设置别只设一个。5.4 标签重叠X 轴标签太长时打开LabelStyle.IsStaggered true让标签交错。如果还挤可以设LabelStyle.Angle -45倾斜显示或者缩短日期格式。5.5 数据绑定后图表不刷新WinForms 的 Chart 在数据变化后有时需要手动调用chart1.Invalidate()或chart1.Update()。如果是在循环里频繁 AddXY建议先Points.Clear()再批量加最后统一刷新。6. 把配置沉淀成可复用方法上面这些配置我习惯拆成InitChartArea、InitScrollBar、InitSeries、BindData四个方法窗体加载时调一次初始化查询按钮里调 BindData。这样换数据源时只动 BindData样式和交互逻辑不用碰。如果你在接入模型或做 AI 辅助编码时想快速验证接口返回可以用 TaoToken 的模型对话页面直接试请求长期写代码、跑 Agent 任务的话Coding Plan 更适合需要管理密钥就去 API Keys 页面接入细节看接入文档。这几个入口按你的场景选就行不用全开。最后留一个实用技巧ScaleView.Size 不要写死根据数据点总数动态算比如Math.Min(10, totalPoints)这样数据少的时候不会出现空白的滚动区域。滚动条的颜色和按钮样式也可以跟着主题走深色背景下把 ButtonColor 换成深灰视觉上更统一。