WinForms Chart控件实战:Series与Points的避坑指南

WinForms Chart控件实战:Series与Points的避坑指南 做上位机界面那几年Chart控件几乎是我每个项目里都要碰一遍的东西。不管是温度曲线、压力曲线还是设备状态趋势最后都要落到一根线、一堆点上。用多了以后我有个很深的体会图表不显示、坐标轴乱跳、点一多就卡成PPT十有八九不是控件本身的问题而是Series和Points这两个最基础的东西没用好。先把概念说清楚Series就是一条数据序列一条线、一组柱子、一片区域都算一个SeriesPoints是这条序列里的数据点集合。听起来很简单真正用起来坑是一个接一个。这篇就围绕Chart控件里Series和Points属性的实操和避坑展开结合我实际踩过的坑说说那些文档里不会写、但项目里一定会遇到的细节。适合刚接手WinForms图表开发的初学者也适合天天被实时曲线卡顿折磨的上位机开发者。1. 动手之前先搞懂Series和Points在Chart里的位置1.1 Chart控件的三层结构一张图讲明白很多初学者拿到Chart控件以后第一件事就是往设计器里拖一个Chart然后双击进去写代码。结果写了半天发现要么什么都不显示要么显示出来的东西跟自己想的不一样。这时候问题往往出在结构理解上。Chart控件本身是一个顶级容器它下面挂着几个子集合ChartAreas绘图区、Series数据序列、Legends图例。它们的关系可以这样理解ChartArea是一块画布Series是画布上的一条曲线或者一组柱子Points是构成这条曲线的每一个采样点。你要在哪个画布上画就要把Series指定到哪个ChartArea你要画几条线就要新建几个Series。我见过最典型的错误写法是这样的chart1.Series[温度曲线].Points.AddY(25.6);如果Chart1里没有预先创建名字为“温度曲线”的Series这一行代码直接抛异常。更隐蔽的情况是有些项目里写了好几个窗体的图表代码复制粘贴以后Series的Name重了最后跑到别的Chart上去了。所以我的建议是创建Series的时候一定要显式Add到chart1.Series集合里并指定ChartAreaSeries series new Series(温度曲线); series.ChartType SeriesChartType.Line; series.ChartArea ChartArea1; chart1.Series.Add(series);千万别省略最后一步Add也别省略ChartArea的指定。省略Add等于你挂了一把锁但没往门上装省略ChartArea默认会跑到第一个ChartArea里一旦Chart里有多个Area就乱了。1.2 一个数据点到底由什么组成搞懂结构之后下一步就要理解Points集合里到底存的是什么。很多人以为Points里存的就是一个个数字其实不是。每一个DataPoint由两个核心部分组成XValue和YValues。XValue是一个double类型的值它代表这个点在X轴上的位置。如果X轴是时间轴那XValue就是一个OADate格式的double数字。YValues是一个double数组通常情况下这个数组只有一个元素也就是YValues[0]比如普通折线图、柱状图。但有些图表类型会用到多个Y值比如K线图需要高低开收四个值气泡图需要三个值这些场景下YValues数组就有多个元素。于是问题就来了。很多人用AddXY方法时以为参数顺序是X和Y的关系这没错。但实际在底层AddXY做的事情是把第一个参数赋给DataPoint.XValue把第二个参数塞进YValues[0]。如果你想手动构造DataPoint并添加必须把这个关系搞清楚DataPoint dp new DataPoint(); dp.XValue 10; // X轴位置 dp.YValues[0] 25.6; // Y轴值 series.Points.Add(dp);注意DataPoint新建出来以后YValues数组默认长度是1可以直接用YValues[0]赋值。但如果遇到K线图那种多Y值场景就需要在处理数据时先设置YValues的长度。还要特别提醒一点不要把XValue和YValues的概念跟ChartType混淆。不管是折线、柱状还是面积图它们的数据组织方式都一样都是X加Y的组合区别只在于Chart是怎么把点“画”出来的。1.3 默认行为藏着不少坑Chart控件还有很多默认行为这些默认行为在简单场景下没问题一到复杂场景就开始捣乱。第一个坑是自动分配颜色。如果你不手动指定Series的ColorChart会按照默认调色板给每个Series自动上色。听起来挺好的但当你动态添加Series、删除Series、再添加Series时自动分配的颜色可能跟之前某个Series重复导致两条曲线肉眼根本分不清。所以项目里只要Series数量超过两个我基本都会手动指定Color不用自动分配。第二个坑是自动图例。默认情况下图例显示的是Series的Name。如果多个Series的Name重了图例就乱。另外当你不想在图形上显示某条序列时要把Series的IsVisibleInLegend设为false否则图例上还是会有它容易让看图的人误解。第三个坑是轴自动缩放。这个本身不算坑但它会导致实时数据刷新时坐标轴来回“跳动”看起来特别晃眼。这个问题后面在实时刷新那一节细说。2. Series属性详解类型、轴、样式一样都不能省2.1 ChartType选错后面的努力全白费SeriesChartType是Series最重要的属性没有之一。它决定你的数据最终以什么形态呈现。常用的几种类型我列个表类型适用场景特点Line普通的折线趋势相邻点用直线连接数据点可显示MarkerSpline平滑曲线趋势曲线会穿过每个数据点但形状经过插值FastLine大数据量折线绘制效率高但不支持Marker等功能Point散点分布只显示点不连线FastPoint大数据量散点速度快适合海量离散点Column柱状对比适合分类对比、数量对比Area面积趋势折线加区域填充Pie占比展示饼图X值一般没有实际意义这里要特别提醒两个坑。第一个坑是Spline和Line的选择。Spline画出来的曲线是平滑的看起来很美观但它会在原始数据点之间做插值运算展示出来的“曲线值”并不是真实采集值。如果项目里要求图表必须真实反映原始数据就老老实实用Line别用Spline。我见过有人把温度曲线用Spline画结果波峰和波谷被圆滑得看起来失真了领导一眼就看出不对劲。第二个坑是FastLine的局限性。FastLine的核心优势是性能数据显示量大的时候比Line快很多。但它不支持Marker、不支持透明度、不支持很多装饰性效果。如果你既要大数据量又要好看的Marker那就得自己权衡。我的建议是实时刷新场景用FastLine数据量小、追求展示效果的用Line。2.2 颜色、线宽、Marker和Legend要显式配置之前说过自动配色会踩坑这里展开说一下怎么手动配置。应收先给每个Series指定一个固定Color同时设置BorderWidth。BorderWidth默认是1在上位机大屏上看起来偏细我一般会设成2这样扫一眼就能看清曲线走向。series.Color Color.FromArgb(0, 120, 215); series.BorderWidth 2; series.MarkerStyle MarkerStyle.Circle; series.MarkerSize 6; series.MarkerColor Color.Red;如果有多条曲线交叉颜色区分是关键但只靠颜色区分对色弱人群不友好。这种场景下可以给不同Series设置不同的MarkerStyle比如一条用圆点、一条用方块、一条用三角这样即使打印成黑白文档也能分辨出来。Legend这块容易被忽略。默认图例文字用的是Series的Name你要是新建Series时忘记设置名字它就会变成类似“Series1”这样的默认值。项目里最好在创建Series时就把Name和LegendText都设置好。LegendText可以设置成“温度”、“压力”这类业务名称即使用户看不懂代码也能从图例里一眼看懂曲线含义。另外有一个实用小技巧如果你有一条辅助线不想让它在图例里出现记得把它的IsVisibleInLegend设为false否则图例里会多出来一个莫名其妙的项。2.3 XValueType和YValueType是时间轴的关键很多人画时间曲线时遇到X轴显示一堆小数的问题其实原因很简单XValueType没设对。XValueType告诉ChartX轴上的数值是数字还是日期时间。如果你存的是OADate格式的时间值但不设置XValueType为DateTimeChart就会把那一串小数直接显示在轴上。正确姿势是series.XValueType ChartValueType.DateTime; series.YValueType ChartValueType.Double; chart1.ChartAreas[0].AxisX.LabelStyle.Format HH:mm:ss;注意LabelStyle.Format的格式字符串。想看时分秒就用HH:mm:ss想看日期就用yyyy-MM-dd HH:mm。“HH”是24小时制“hh”是12小时制别搞混。还有一个容易被忽略的点XValueType只是告诉Chart怎么解析和显示X值它并不帮你自动转换数据类型。AddXY的第一个参数如果是double那你就得保证这个double确实是OADate格式的时间值如果是DateTime对象Chart控件内部会帮你转。为了明确起见我习惯直接用DateTime对象传给AddXY或者手动转成ToOADate()两种写法都可以DateTime now DateTime.Now; series.Points.AddXY(now, tempValue); // 或者 series.Points.AddXY(now.ToOADate(), tempValue);2.4 EmptyPointStyle、ToolTip和CustomProperties空值是图表开发里绕不开的问题。采集系统偶尔会有某几个时刻数据缺失如果你直接把缺失的数据跳过不添加那曲线就会断掉如果你把缺失值填成0那曲线会出现一个瞬间跌到0的假象这两种都不对。正确做法是把该点的Y值设为double.NaN然后配置EmptyPointStyleseries.EmptyPointStyle.BorderStyle DashStyle.Dash; series.EmptyPointStyle.MarkerStyle MarkerStyle.Diamond; series.EmptyPointStyle.MarkerColor Color.Orange;这样设置之后数据缺失的位置会用虚线连接或者用特殊Marker标出来看图表的人一眼就能看出这里有数据缺失而不是误以为数据真的跌到0了。ToolTip也是日常用得比较多的功能。鼠标悬停到数据点上时希望显示具体的时间和数值可以用占位符series.ToolTip 时间#VALX\n数值#VALY;#VALX表示X值#VALY表示Y值注意不要写成#VALY[0]有些版本不认这种写法。如果Series设置了XValueType为DateTimeToolTip里显示的时间格式默认可能不是你想要的样子可以配合AxisX的LabelStyle.Format来影响显示。CustomProperties属于进阶属性比如柱状图的柱宽、误差线设置等都是通过它来配置的。这类属性文档说明少不同图表类型的用法差异很大不建议初学者一上来就研究遇到具体需求时再查资料。记住一点CustomProperties用错了不会报错只是不生效排查起来很隐蔽。3. Points集合实操添加、修改、清空和绑定的正确姿势3.1 AddY、AddXY、Add三种方式各有用武之地Points集合有几种添加数据的方法选错了会造成X轴含义完全不对。AddY方法是只传一个Y值X值自动按照0、1、2这样的索引顺序生成。适合的场景是X轴没有实际业务含义只是纯粹的分类序号比如柱状图里显示几个班级的成绩。这种场景如果非要用AddXY反而要多维护一个没意义的X值数组。AddXY方法是显式传入X值适合X轴有实际含义的场景比如时间轴、频率轴、距离轴。实时曲线显示温度随时间变化这种就必须用AddXY否则时间轴会变成0、1、2的序号完全没法看。Add方法则需要手动构造DataPoint对象适合需要精细控制每个点样式、空值标记的场景。比如某些点要特殊标红某些点要加注记用AddY和AddXY不能满足这种需求时就手动创建DataPoint再Add。这三种方式的适用场景我用一个表总结一下方法X值来源适用场景AddY自动按照0、1、2索引分类柱状图、序号序列AddXY显式指定时间曲线、频率曲线、任何X有含义的场景Add(DataPoint)DataPoint.XValue需要精细控制每个点有个隐藏问题值得注意如果你用AddY加了点然后某次又用AddXY加了带时间X值的点同一个Series里的X值含义就乱了。要么全用AddY要么全用AddXY不要混用。3.2 修改已有数据点SetValueY和SetValueXY更靠谱有时候业务上需要动态修改某几个点的值而不是重新添加。最直接的做法是series.Points[3].YValues[0] 88.5;这样写能生效但不推荐。原因一是如果你后面要把Series绑定到数据源直接改YValues可能不会被图表完整感知二是代码可读性差别人看半天不知道你改的是啥。更稳妥的做法是用SetValueY方法和SetValueXY方法series.Points.SetValueY(3, 88.5); series.Points.SetValueXY(3, newXValue, newYValue);这两个方法字面意思很清楚就是设置第几个点的Y值或者X和Y值。它们会在内部处理数据和坐标轴的联动比直接操作数组更安全。还有一点很容易踩坑修改了某个点的XValue之后X轴的实际范围可能不会自动重算。比如你原本X范围是0到100现在把最后一个点的X改成了200轴可能还停留在0到100。这时候需要手动调用ChartArea的RecalculateAxesScale方法chart1.ChartAreas[0].RecalculateAxesScale();RecalculateAxesScale是个好方法但它只在手动设置轴范围的时候才有意义。如果轴的Minimum和Maximum是自动模式图表会自己重新计算不需要额外调用。我通常在清空Points、修改Points后顺手调用一次防患于未然。3.3 DataBindXY和DataBindTable的隐藏行为批量添加数据时很多人喜欢用DataBindXY绑定数组或者List。比如从数据库查出了时间列表和温度列表一次绑定上去double[] xValues ...; double[] yValues ...; series.Points.DataBindXY(xValues, yValues);这个方法的坑在于多次调用DataBindXY会重新构建数据点它和你前面手动Add的点混在一起时行为很诡异。我在项目里曾经先手动Add了几个点然后调用DataBindXY绑定新数据结果图表上同时出现了旧点和新点排查了半天才发现是绑定覆盖的问题。所以用DataBindXY前如果Series里已经有数据最好先Points.Clear()。DataBindTable是另一个更“智能”但更容易让人措手不及的方法。它会根据DataTable自动创建多个Series每个数据列对应一个Series列名自动成为图例文字。听起来很方便但如果你只用DataBindXY就能解决问题就别用DataBindTable。原因是你一旦使用DataBindTable图表的数据结构就由数据表结构决定了后续想单独控制某个Series的样式、轴类型、空值策略会很被动。绑定数据源时还有一类的坑绑定后不能随意更改数据源集合元素。比如绑定了List然后去修改List里的元素Chart控件不一定会实时响应。建议把数据准备好后再绑定不要在绑定之后还频繁改源数据。3.4 Points.Clear()之后别忘了恢复轴范围清空Points最典型的问题是数据清掉了但坐标轴范围还留在旧数据的范围内。你调用Points.Clear()之后理想情况是图表变成空的等待新数据但实际可能依然显示之前的老坐标范围新数据只有一点点画出来就像被“压”在某个角落。解决方法是清空数据后把轴范围恢复为自动并调用RecalculateAxesScaleseries.Points.Clear(); ChartArea ca chart1.ChartAreas[0]; ca.AxisX.Minimum double.NaN; ca.AxisX.Maximum double.NaN; ca.AxisY.Minimum double.NaN; ca.AxisY.Maximum double.NaN; ca.RecalculateAxesScale();double.NaN在Chart控件的轴属性里有特殊含义表示自动计算。你手动设置了Minimum和Maximum之后再想恢复自动就必须把这两个值重新设成double.NaN。这个逻辑很多文档不会写但实际项目中特别常见。另外删除单个点时用RemoveAt会比Remove快因为Remove需要按值查找。如果你实时更新曲线时不打算用固定上限策略而是每次删除最旧的一个点再追加新点直接用RemoveAt(0)就行。3.5 实时刷新时的Points管理策略实时刷新是上位机图表开发中最常见的场景也是性能问题的重灾区。很多人拿一个Timer每隔100ms就往Points里Add一个点十分钟后Points里有6000个点图表开始卡一小时后有36000个点界面基本没法看。根本原因就是Points集合无限增长图表每次重绘都要处理越来越多的数据点。这个问题不只是Chart控件有所有图形库都一样。解决思路有两条第一条是设置点数上限超出上限就删掉最旧的点。比如最多保留3000个点每次添加前先检查if (series.Points.Count 3000) { series.Points.RemoveAt(0); } series.Points.AddXY(now, value);这个策略简单粗暴但对实时曲线来说很有效。窗口内的数据永远是最新的3000个点。第二条是限制轴的可视范围。即使不删点也可以通过固定AxisX的Minimum和Maximum让图表只显示最近一段时间的数据ChartArea ca chart1.ChartAreas[0]; ca.AxisX.Minimum now.AddSeconds(-30).ToOADate(); ca.AxisX.Maximum now.ToOADate();这样图表看起来像一个滑动的窗口。注意轴范围固定后如果Points里还有大量旧数据控件依然要循环处理它们性能还是上不去。所以实际项目里我都是两条策略同时用限制Points数量同时固定可视时间窗口。另外实时刷新时如果有多个Series要分开管理。别把所有数据都塞进一个Series到时候想单独隐藏某一条曲线会非常痛苦。4. 常见问题排查从空白到卡顿的完整处理思路4.1 图表一片空白先查这5个地方第一个要查的是Points.Count是否为0。图表空白最常见的原因就是Points集合里根本没有数据你以为是没显示出来其实是压根没加进去。可以在当前数据添加完成的代码下面断点直接看Points.Count的值。第二个要查的是数据值本身。有人从数据库读数据某几个字段是DBNull赋值给double之后变成了默认值0画出来的曲线有一大段贴着0走。还有人用double.Parse解析字符串解析失败抛了异常但被吞掉数据根本没加上。第三个要查的是轴范围。前面说过如果上一次数据量很大轴范围被固定到了很宽的区间下一次数据量小曲线就会被“压扁”到几乎看不见。这种情况调用RecalculateAxesScale或者重设轴为NaN即可。第四个要查的是Series和ChartArea是不是失联了。代码里新建了一个Series但忘了chart1.Series.Add(series)或者Series.ChartArea指向了一个不存在的ChartArea图表都会显示空白。第五个要查的是Chart控件本身的位置和尺寸。曾经遇到过一个很尴尬的问题图表代码逻辑全对数据也有就是不显示。最后发现是Chart的Dock属性被改成了NoneAnchor和大小没设置图表高度为0。这种问题排查半天最后发现是最初拖动控件时不小心改了布局属性。4.2 X轴顺序乱、时间轴不对怎么办X轴顺序乱的问题在从数据库读取数据时很常见。比如查询结果没有按时间排序绘制的曲线就会来回交叉看起来一团乱麻。解决办法是在数据源层面就order by然后在绑定到Points之前确保X值是递增的。如果设置IsXValueIndexed为true图表会严格按照Points的索引顺序绘制不再对X值排序。这个属性在实时曲线场景下很管用。但要注意一旦开启IsXValueIndexedX轴的标签显示也依赖Points的XValue所以如果XValue是OADate时间值XValueType还是得配好。时间轴不对的另一个常见问题是XValueType没有设置成DateTime。前面说过不设置的情况下Chart会把XValue当作普通数字显示于是你看到的就是一串浮点数或者一个很大的数字。这种情况设置一下XValueType和LabelStyle.Format就解决了。还有一种情况是多个Series的时间轴不统一。每个Series的数据点X值范围不一样导致图表自动缩放时时间范围被拉得忽大忽小。解决方法是显式指定AxisX的Minimum和Maximum或者统一所有Series的时间点。4.3 数据量一大就卡顿性能优化清单Chart控件在小数据量下表现很好几百个点怎么折腾都流畅。一旦数据量上到几千上万就开始考验优化水平了。我总结了一下针对Chart控件的性能优化清单按优先级排列第一用FastLine替代Line用FastPoint替代Point。这是差距最明显的优化手段。实测同一台机器普通Line画5000个点能明显感觉到拖动和刷新卡顿换成FastLine后流畅很多。第二限制Points数量。就算用了FastLine数据点无限增长也扛不住。实时曲线保留最近几千个点即可超出就移除最旧的。第三关闭不必要的视觉效果。阴影、3D效果、抗锯齿这类功能在大数据量下非常消耗性能。如果业务场景不要求这些效果就不要开。Chart控件的3D效果可以通过ChartArea.Area3DStyle.Enable3D开关默认是关的但有些人不小心打开了图表看起来有个立体边而且画起来特别慢。第四减少轴范围的频繁变化。实时刷新时如果每次都让轴自动缩放RX轴范围会跟着最新数据来回跳不仅看起来不舒服性能也会受影响。固定一个【最近N分钟】的窗口是对实时图表最好的方案。第五不要频繁调用RecalculateAxesScale。这个方法虽然方便但每次调用都会触发一次轴重算相当于让整个图表重新布局一遍。在实时刷新循环里高频调用它性能会直线下降。固定轴范围、固定Points上限的情况下可以尽量少调用它。关于性能优化我不建议盲目套用。数据量小的项目优化过度反而复杂数据量大的项目又不能不优化。一般判断标准是实时刷新时实际间隔低于50ms的必须用FastLine加点数上限刷新频率在几百毫秒以上且数据量不大的普通Line完全够用。4.4 双Y轴配置的经典翻车现场双Y轴需求也很常见左边显示温度右边显示压力两者的量级差很多共用Y轴会导致量级小的曲线看起来像一条直线。解决方法是把第二个Series挂到次坐标轴Y2上series2.YAxisType AxisType.Secondary; series2.ChartArea ChartArea1; chart1.ChartAreas[0].AxisY2.Enabled AxisEnabled.True; chart1.ChartAreas[0].AxisY2.Title 压力;这个操作本身不难但翻车现场通常在后头。主Y轴AxisY的自动范围会把主、次轴的所有数据都算进去所以即使温度曲线只到100度压力曲线到500主Y轴的Maximum也会被拉到500导致温度曲线被压得非常平。解决办法是分别给AxisY和AxisY2设置合理的Minimum和Maximum不要让它们自动缩放。还有一个细节是AxisY2的标题和刻度格式经常被忽略。跑了双Y轴之后辅助Y轴的LabelStyle和Title如果不设置界面上一片空白用户根本不知道右轴代表什么量。配置完AxisY2之后记得把它的LabelStyle.Format和Title都补齐。双Y轴遇到配色问题时也更麻烦。两条曲线、两个坐标轴、两组刻度如果颜色不对应看图的人很容易混淆。我的习惯是让左边Y轴的标题颜色和左轴曲线颜色一致右边Y轴的标题颜色和右轴曲线颜色一致这样视觉上就很清楚。5. 两个可以直接抄的封装5.1 安全添加数据点线程切换加点数上限实时数据采集经常跑在后台线程里直接在线程里操作Chart控件会报跨线程访问错误。虽然有人用Control.CheckForIllegalCrossThreadCalls false把错误压下去但这是饮鸩止渴不建议学。正确做法是用Invoke切换到UI线程同时处理点数上限。我自己项目里实际在用的一个方法是这样封装的private void AddDataPointSafe(Series series, DateTime time, double value, int maxPoints) { if (chart1.InvokeRequired) { chart1.Invoke(new ActionSeries, DateTime, double, int(AddDataPointSafe), series, time, value, maxPoints); return; } if (series.Points.Count maxPoints) { series.Points.RemoveAt(0); } series.Points.AddXY(time.ToOADate(), value); ChartArea ca chart1.ChartAreas[series.ChartArea]; ca.AxisX.Minimum time.AddSeconds(-30).ToOADate(); ca.AxisX.Maximum time.ToOADate(); }这个方法的思路是不管数据从哪个线程来先Invoke回到UI线程然后控制Points数量不超过maxPoints添加完成后固定X轴窗口为最近30秒。这样就算每秒刷新50帧图表也能保持稳定。注意Invoke的第一个参数要传this.chart1不是普通Control。因为Chart控件是异步刷新机制你Invoke到其他Control上再操作Chart虽然大多数时候没问题但偶尔会遇到刷新不及时的情况。直接Invoke到Chart控件本身最保险。5.2 一键重置图表轴范围、Series、图例一起恢复切换数据源、切换设备、重新初始化界面时经常要把图表恢复到初始状态。很多人只清数据不重置轴导致第二次显示时图表范围还是上一次的。我习惯封装一个ResetChart方法private void ResetChart() { chart1.Series.Clear(); chart1.Titles.Clear(); chart1.Legends.Clear(); foreach (ChartArea ca in chart1.ChartAreas) { ca.AxisX.Minimum double.NaN; ca.AxisX.Maximum double.NaN; ca.AxisY.Minimum double.NaN; ca.AxisY.Maximum double.NaN; ca.AxisY2.Minimum double.NaN; ca.AxisY2.Maximum double.NaN; ca.RecalculateAxesScale(); } chart1.ResetAutoValues(); }ResetAutoValues是Chart控件自带的恢复默认值方法它会把颜色、样式、图例等自动生成的属性全部重置。但轴范围的Minimum和Maximum要提前手动设回double.NaN否则ResetAutoValues不会自动清除你手动设置的定值。我在实际项目里的习惯是新建一个图表页面前先把Series、ChartArea、Legend三个配置在脑子里过一遍所有实时数据都走封装好的AddDataPointSafe绝不在业务代码里到处直接操作Points.AddXY每次切换数据源都调用一次ResetChart。这样项目后面交接给别人也不容易出图表相关的奇奇怪怪的问题。Chart控件的坑不算多但每一个踩起来都挺耗时间。如果你现在正被图表不显示、X轴乱跳、数据一多就卡这几个问题折腾建议按第4节的顺序从数据源查起再回头看Series和Points的设置。把Series的ChartType、XValueType、颜色配好把Points的添加策略和数量上限管好图表这块基本就稳了。后续再遇到新问题大概率也能通过这些基础逻辑推出来。