用于绘制线系列图的 MSChart 类





5.00/5 (1投票)
本文介绍了一个用于利用 Microsoft 图表控件以折线图形式绘制数据的 C# 类。
引言
本文将介绍一个用于利用 Microsoft 图表控件以折线图形式绘制数据的 C# 类。 .NET 中提供的 MSChart 控件功能非常强大。Microsoft Office 产品也使用该控件的一个版本来绘制数据图表,可以使用 VBA 代码进行访问。其他人已经写过一些关于使用 MSChart 控件绘制各种图表类型的基础知识,但当我研究绘制折线图时,帮助信息很少,所以我决定写这篇文章。
使用代码
类变量和构造函数如下所示:
//--------------------------------------------------------------------------
// Class CGraph
//--------------------------------------------------------------------------
public class CGraph {
public TabPage Tab { get; set; }
private Chart chart;
private ChartArea chArea;
//--------------------------------------------------------------------------
// Class constructor
//--------------------------------------------------------------------------
public CGraph(TabPage Contr) {
chart = new Chart();
Contr.Controls.Add(chart); // Add chart to control
Tab = Contr; // Local global
chart.Dock = DockStyle.Fill; // Size to the control
chart.GetToolTipText += new EventHandler<ToolTipEventArgs>(this.chart_ToolTip);
Legend leg = chart.Legends.Add(null);
leg.Docking = Docking.Bottom;
leg.Font = new Font(chart.Legends[0].Font.FontFamily, 8); // Font size
leg.IsTextAutoFit = true;
chart.Palette = ChartColorPalette.None;
chart.PaletteCustomColors = new Color[] {Color.Blue, Color.DarkRed,
Color.Green, Color.Orange,
Color.SteelBlue, Color.Magenta,
Color.Purple, Color.OliveDrab,
Color.DodgerBlue, Color.Sienna,
Color.Teal, Color.YellowGreen,
Color.RoyalBlue, Color.Black,
Color.Teal, Color.OrangeRed};
chArea = chart.ChartAreas.Add(null);
// Enable range selection and zooming
chArea.CursorX.IsUserEnabled = true;
chArea.CursorX.IsUserSelectionEnabled = true;
chArea.AxisX.ScaleView.Zoomable = true;
chArea.AxisX.ScrollBar.IsPositionedInside = true;
chArea.AxisX.ScaleView.SmallScrollSize = 1;
chArea.AxisX.IntervalAutoMode = IntervalAutoMode.VariableCount;
chArea.AxisY.IntervalAutoMode = IntervalAutoMode.FixedCount;
chArea.AxisX.LabelStyle.Format = "F0";
chArea.AxisX.LabelStyle.IsEndLabelVisible = true;
chArea.AxisX.MajorGrid.LineColor = Color.FromArgb(200, 200, 200);
chArea.AxisY.MajorGrid.LineColor = Color.FromArgb(200, 200, 200);
chArea.AxisX.LabelStyle.Font = new Font(chArea.AxisX.LabelStyle.Font.Name, 9, FontStyle.Regular);
chArea.AxisY.LabelStyle.Font = new Font(chArea.AxisY.LabelStyle.Font.Name, 9, FontStyle.Regular);
chArea.AxisX.TitleFont = new Font(chArea.AxisX.TitleFont.Name, 9, FontStyle.Regular);
chArea.AxisY.TitleFont = new Font(chArea.AxisY.TitleFont.Name, 9, FontStyle.Regular);
}
}
构造函数接收一个 TabPage
控件作为参数,以便将新图表添加到其中,但这可以轻松地更改为其他类型的控件,或者使其通用化为对象。该控件被保存到名为 Tab
的公共类变量中,以便其他方法可以访问。新图表对象被添加并保存在名为 chart
的私有类变量中。定义了一个事件处理程序来处理图表的 ToolTip(稍后详述)。图例被添加并停靠在图表的底部,并设置了其字体。除了图表声明之外,还需要一个图表区域,该区域被添加到图表中并保存为私有变量 chArea
(一个图表可能包含多个图表区域)。通过调用 PaletteCustomColors
将默认的 MSChart 绘图调色板更改为自定义颜色。这并非必需,因为图表控件有自己的调色板默认设置,但这表明了如何定义自定义颜色。图表中的第一个系列将使用第一个调色板颜色,第二个系列将使用第二个颜色,依此类推。
接下来的几行设置了图表的 x 轴缩放。 y 轴也可以以类似的方式进行缩放设置。这是折线图的一个很好的特性,由控件自动处理。如果用户单击并拖动图表上的某个区域,它将自动缩放并添加一个滚动条进行定位。如果需要,用户甚至可以再次缩放以获得更高分辨率的视图。滚动条上的一个小按钮将图表重置到之前的缩放级别。每次单击该按钮,图表都会回退一个级别,直到恢复到原始未缩放的图表(类似于撤销)。设置了图表的 x 轴和 y 轴间隔,这些间隔决定了绘制多少个轴标签和“刻度线”。其余的行将标签样式、字体和网格颜色设置为灰色级别。
Title
方法(如下所示)以指定的颜色向图表添加主标题,并可以停靠在各种定义的图表位置(顶部、底部等)。它首先检查传递给它的标题字符串是否已在图表上的同一停靠位置使用过。如果它已在同一停靠位置使用过,则该方法返回而不将其添加到图表,否则,该标题将被追加到当前标题,或者如果该停靠位置没有标题,则会添加它。这是一个有用的功能,因为调用函数可以选择将多个数据系列绘制到同一个图表上,并且可能例如在循环中遇到系列名称时添加它。因此,虽然此方法会添加每个新标题,但它也确保标题行不会重复。更改标题字体的行在此处未使用,但已注释掉以显示如何进行。 x 轴和 y 轴标题的添加方式与类中提供的函数类似。
//--------------------------------------------------------------------------
// Add graph title. Repeated calls append new titles if unique or if new pos.
//--------------------------------------------------------------------------
public void Title(string title, Docking position, Color Colr) {
foreach (Title t in chart.Titles) {
if (t.Docking == position) {
if (!t.Text.Contains(title)) t.Text += "\n" + title; // Append
return;
}
}
// If here add new title
Title newtitle = new Title(title, position);
chart.Titles.Add(newtitle);
newtitle.ForeColor = Colr;
//newtitle.Font = new Font(newtitle.Font.Name, 8, FontStyle.Bold);
}
方法 GraphLineSeries
(如下所示)执行实际的折线图绘制到图表中。此函数可以调用多次以向同一图表添加更多数据系列。该函数接收系列名称、x 和 y 数据数组、一个布尔值(指示是否添加最小/最大标签)、系列图表颜色(以防万一要覆盖默认值)以及另一个布尔值(是否为该系列添加图例)。如果成功添加系列,则该函数返回已添加的系列对象,如果无法添加系列,则返回 null。
使用传递的字符串 Name
声明新系列。将系列添加到图表中的代码包含在一个 try 块中,因为如果系列名称已在图表中被使用,则添加系列调用将引发异常。这对于“捕获”具有相同系列名称的冗余调用很有用,这样调用者就不必进行检查。如果系列名称已被使用,则函数将在不绘制任何内容的情况下返回 null。对于折线图,必须有一个一维数组包含 x 值和 y 值。数据数组通过简单的 DataBindXY(x, y)
调用附加到系列并绘制到图表中。请注意,一个图表控件可以包含多个图表区域。检查 ChartAreas.Count
的 if 语句是为了以防万一图表控件上有多个图表区域,在这种情况下,数据将绑定到最后一个图表区域。通常我每个控件只使用一个图表区域,但如果需要,这里提供了相应的处理。
//--------------------------------------------------------------------------
// Adds line series to chart.
//--------------------------------------------------------------------------
public Series GraphLineSeries(string Name, double[] x, double[] y,
bool lblminmax, Color colr, bool inLegend) {
Series ser = new Series(Name);
try { // Traps redundant series names
chart.Series.Add(ser);
ser.ChartType = SeriesChartType.Line;
if (chart.ChartAreas.Count() > 1) // Bind to last chart area
ser.ChartArea = chart.ChartAreas[chart.ChartAreas.Count() - 1].Name;
ser.Points.DataBindXY(x, y);
}
catch (ArgumentException) { return null; } // Handle redundant series
catch (OutOfMemoryException err) {
MessageBox.Show("Error - " + err.Message, "GraphLineSeries()",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
ser.IsVisibleInLegend = inLegend;
if (colr != null) ser.Color = colr;
if (lblminmax) { // Add min, max labels
chart.ApplyPaletteColors(); // Force color assign so labels match
SmartLabels(ser, ser.Color, ser.Color, FontStyle.Regular);
ser.SmartLabelStyle.MovingDirection = LabelAlignmentStyles.TopLeft;
DataPoint p = ser.Points.FindMaxByValue();
p.Label = "Max = " + p.YValues[0].ToString("F1");
p = ser.Points.FindMinByValue();
p.Label = "Min = " + p.YValues[0].ToString("F1");
}
return ser;
}
如果传递的布尔值 lblminmax
为 true,则将在图表上标记最小和最大数据值。标签颜色设置为与系列相同的颜色,以便轻松识别,以防同一个图表绘制了多个系列。请注意,此处必须调用 ApplyPaletteColors()
,以便使用构造函数中定义的调色板更改来设置标签。调用 FindMaxByValue()
和 FindMinByValue()
MSChart 方法分别查找最大值和最小值的数据点的索引位置,以便将标签分配给相应的数据点。这些标签将显示为 ToolTip,如下文所述。
调用 SmartLabels()
函数(如下所示)来处理该类的 SmartLabel 设置。MSChart SmartLabels 函数会自动重新定位图表上的标签,以免它们干扰其他图表标签。SmartLabels 还具有“引导线”功能,该功能在需要时绘制一条从数据点到标签的线段。这有助于识别图表中每个系列的标记的最小/最大点,当点或标签彼此靠近时。调用 SmartLabelStyle.MovingDirection
(定义了标签如何远离点移动的方向)未包含在类的 SmartLabels
方法中,以便将控制权交给外部函数,这些函数可能需要根据某些变量的值来定义移动方向,例如,如果为正,则向上移动,如果为负,则向下移动。
SmartLabels
将系列对象作为输入,以及标签字体、颜色和“引导线”颜色的属性。有关此处用于设置 SmartLabels 的函数调用的更多信息,请参阅 Microsoft 文档。
//--------------------------------------------------------------------------
// Setup Smart Labels
//--------------------------------------------------------------------------
private void SmartLabels(Series ser, Color Fcolor, Color Linecolor,
FontStyle FontStyle, int FontSize = 7) {
if (ser == null) return;
ser.LabelForeColor = Fcolor;
ser.Font = new Font(ser.Font.Name, FontSize, FontStyle);
ser.SmartLabelStyle.Enabled = true;
ser.SmartLabelStyle.CalloutLineColor = Linecolor;
ser.SmartLabelStyle.CalloutStyle = LabelCalloutStyle.None;
ser.SmartLabelStyle.IsMarkerOverlappingAllowed = false;
ser.SmartLabelStyle.MinMovingDistance = 1;
ser.SmartLabelStyle.IsOverlappedHidden = false;
ser.SmartLabelStyle.AllowOutsidePlotArea = LabelOutsidePlotAreaStyle.No;
ser.SmartLabelStyle.CalloutLineAnchorCapStyle = LineAnchorCapStyle.None;
}
下面显示的的代码是该类的 ToolTip 处理程序。如果用户将鼠标悬停在图表的数据点上,则会触发此 ToolTip 处理程序(从构造函数中定义的事件处理程序触发),并显示该点的系列名称以及 x 和 y 数据值。可以为每个数据点添加 ToolTip,但这会为图表渲染带来相当大的开销,尤其是在一个系列中有很多数据点的情况下。通过此鼠标中断仅在悬停在数据点上时添加 ToolTip,仅按需启用它们,因此在时间上成本较低。该方法还提供了一些 ToolTip 帮助,当悬停在图表添加的滚动条上(当发生缩放时)以及轴和轴标签区域时,会告知用户有关单击拖动进行缩放和缩放撤销功能的信息。
//--------------------------------------------------------------------------
// Chart tooltip handler.
//--------------------------------------------------------------------------
private void chart_ToolTip(object sender, ToolTipEventArgs e) {
HitTestResult h = e.HitTestResult;
switch (h.ChartElementType) {
case ChartElementType.Axis:
case ChartElementType.AxisLabels:
e.Text = "Click-drag in graph area to zoom";
break;
case ChartElementType.ScrollBarZoomReset:
e.Text = "Zoom undo";
break;
case ChartElementType.DataPoint:
e.Text = h.Series.Name + '\n' + h.Series.Points[h.PointIndex];
break;
}
}
我将包含下面的方法,因为它可能对绘制范围类型图表有用,而关于这些图表类型的资料很少。它允许您绘制具有计算宽度线段的图。这与仅仅使用更宽的“画笔”制作粗线段不同,因为线段所需的宽度可能不是画笔宽度的整数倍。对于范围类型图表,可以将宽度设置为一个可以在图表上缩放的值。下面的示例图显示了范围系列为黄色,表示峰值范围级别。通过使用 GraphLineSeries()
添加了红线来显示最大级别所在的位置。
GraphRangeSeries()
方法看起来与 GraphLineSeries()
非常相似,但它需要高度数组,该数组通过变量 yht
传递,它定义了系列的高度(即 y
– yht
就是高度)。请注意,调用 DataBindXY
将此第三个数组作为参数。绘制的系列将自动填充为指定的颜色。
//--------------------------------------------------------------------------
// Adds Range type series to chart.
//--------------------------------------------------------------------------
public Series GraphRangeSeries(string sername, double[] x, double[] y,
double[] yht, Color colr) {
System.Windows.Forms.Cursor.Current = Cursors.WaitCursor;
Series ser = new Series(sername);
try {
chart.Series.Add(ser);
if (chart.ChartAreas.Count() > 1) // Bind to last chart area
ser.ChartArea = chart.ChartAreas[chart.ChartAreas.Count() - 1].Name;
ser.ChartType = SeriesChartType.Range;
ser.IsVisibleInLegend = false;
ser.Color = colr;
ser.Points.DataBindXY(x, y, yht);
}
catch (ArgumentException) { return null; } // Handle redundant series
catch (Exception err) {
MessageBox.Show("Error - " + err.Message, "GraphRangeSeries()",
MessageBoxButtons.OK, MessageBoxIcon.Error);
return null;
}
System.Windows.Forms.Cursor.Current = Cursors.Default;
return ser;
}
结论
这个类远非详尽无遗地涵盖了 MSChart 控件的功能,但希望它能为开发人员提供一个良好的起点,用于绘制折线图数据。下载文件中还包含了一些其他方法,用于绘制点系列、添加垂直标记注释线以及标记 x 和 y 轴。