MSChart 扩展 - 缩放和平移控件 版本 2.2.0
MSChart 扩展 2.2.0 更新,新增功能
注意:二进制文件和源代码文件已更新至 2.2.4 版本,修复了多项错误。
引言
MSChart 扩展是 Microsoft Chart (MSChart) 控件在 Visual Studio 中用于 WinForms 应用程序的扩展类。该工具于 2012 年 7 月首次发布,旨在克服原始 MSChart 的一些局限性。如果您不熟悉 MSChart 扩展,我们建议您先阅读下方列出的先前文章。
包括本文在内的这些文章旨在作为技术分享和此库的文档。
已知问题
- MSChart 扩展是为具有 X 和 Y 轴的图表类型设计的,该扩展方法不适用于某些图表类型,例如雷达图和饼图。
- 众所周知,缩放功能在对数轴上无法正常工作。我们决定禁用对数轴图表的扩展功能。
- 日期时间轴 - 对于 X 轴值为
DateTime
格式的图表,缩放可能无法正常工作。
有什么新内容
滚动缩放
现在可以通过按住 CTRL + ALT 键并滚动鼠标滚轮来沿 X 轴进行缩放,以放大/缩小图表的 X 轴。这是鼠标滚轮缩放功能的简化版本,Y 轴保持不变。代码同时考虑了 X 轴和 X2 轴。
private static void ChartControl_MouseWheel(object sender, MouseEventArgs e)
{
//Some codes...
if (Form.ModifierKeys == (Keys.Alt | Keys.Control))
{
//Mouse Wheel Zoom: X Axis
ScaleViewZoom(ptrChartArea.AxisX, e.Delta);
ScaleViewZoom(ptrChartArea.AxisX2, e.Delta);
}
//More codes...
}
图表光标系列选择
从 2.2.0 版本开始,用户现在可以选择使用哪个系列来绘制图表光标 1 和 2。在 2.2.0 版本之前,两个图表光标仅适用于主 X 轴和 Y 轴。在“选择 - 光标 n”菜单中添加了一个额外的下拉菜单,让用户选择图表光标要使用的系列。如果选定的图表区域仅包含一个轴,则不会出现此下拉菜单。
在上下文菜单打开事件中创建了额外的下拉菜单。
“选择 - 光标 n”菜单的下拉菜单已清除。接下来,我们使用 foreach
循环过滤出属于选定图表区域的系列。如果系列计数仅为 1,则不创建下拉菜单,图表光标将始终使用图表中唯一的一个系列。否则,将创建一个下拉菜单供用户为每个图表光标选择要使用的系列。此外,还添加了一个清除光标...
函数来从图表中删除所有光标。
private static void ChartContext_Opening(object sender, CancelEventArgs e)
{
//Some code removed...
ptrChartData.ChartToolSelect.DropDownItems.Clear();
ptrChartData.ChartToolSelect2.DropDownItems.Clear();
//Add Series to Context Menu
List<Series> ChartSeries = new List<Series>();
SeriesCollection chartSeries = ((Chart)menuStrip.SourceControl).Series;
if (ptrChartData.ActiveChartArea != null)
{
ToolStripSeparator separator = new ToolStripSeparator();
menuStrip.Items.Add(separator);
separator.Tag = "Series";
foreach (Series ptrSeries in chartSeries)
{
if (ptrSeries.ChartArea != ptrChartData.ActiveChartArea.Name) continue;
ChartSeries.Add(ptrSeries);
if (ptrChartData.Option.ContextMenuAllowToHideSeries) //Option to show /
//hide series controls
{
ToolStripItem ptrItem = menuStrip.Items.Add(ptrSeries.Name);
ToolStripMenuItem ptrMenuItem = (ToolStripMenuItem)ptrItem;
ptrMenuItem.Checked = ptrSeries.Enabled;
ptrItem.Tag = "Series";
}
}
if (ChartSeries.Count == 1)
{
ptrChartData.Cursor1.SelectedChartSeries = ChartSeries[0];
ptrChartData.Cursor2.SelectedChartSeries = ChartSeries[0];
}
else if (chartSeries.Count > 1)
{
//Default cursor to first chart series if previous selected series not exist.
if (!ChartSeries.Contains(ptrChartData.Cursor1.SelectedChartSeries))
ptrChartData.Cursor1.SelectedChartSeries = ChartSeries[0];
if (!ChartSeries.Contains(ptrChartData.Cursor2.SelectedChartSeries))
ptrChartData.Cursor2.SelectedChartSeries = ChartSeries[0];
//Populate Context Menu for user to select series for each Chart Cursor.
if (ChartSeries.Count > 1)
{
foreach (Series s in ChartSeries)
{
//Cursor 1
ToolStripMenuItem ptrItem =
ptrChartData.ChartToolSelect.DropDownItems.Add(s.Name)
as ToolStripMenuItem;
ptrItem.Tag = ptrChartData.ChartToolSelect;
ptrItem.Click += ChartToolSelect_SeriesChanged;
if (s == ptrChartData.Cursor1.SelectedChartSeries) ptrItem.Checked = true;
//Cursor 2
ptrItem = ptrChartData.ChartToolSelect2.DropDownItems.Add(s.Name)
as ToolStripMenuItem;
ptrItem.Tag = ptrChartData.ChartToolSelect2;
ptrItem.Click += ChartToolSelect_SeriesChanged;
if (s == ptrChartData.Cursor2.SelectedChartSeries) ptrItem.Checked = true;
}
}
}
}//Active Chart Area
//Some Code Removed...
}
由于每次打开图表上下文菜单时都会重新创建下拉菜单,因此必须将每个图表光标上次选择的系列保存在其他地方。在 ChartCursor
类中添加了一个名为 SelectedChartSeries
的新属性来存储上次选择的光标。
上下文菜单中的所有菜单项在被点击时都会触发 ChartContext_ItemClicked
事件。但是,我们添加到图表光标的下拉菜单不会触发此事件。因此,我们订阅了此新添加菜单项的 Click
事件。添加到图表光标的系列菜单的 Tag
属性设置为 ChartToolSelect
或 ChartToolSelect2
,以在处理单击事件时标识激活哪个光标。
为选定的图表光标选择系列不仅仅是为了识别使用哪个 X 和 Y 轴,也是为下一个功能做准备。
光标吸附到最近的数据点
图表数据与图表中空白区域的比较总是非常有趣的。因此,图表光标需要吸附到选定系列的最近数据点,以进行更精确的分析。此功能首次在此 2.2.0 版本中引入。如果您喜欢图表光标以前的工作方式,只需在 ChartOption
中将 SnapCursorToData
设置为 true
即可让图表光标恢复自由移动。
此搜索功能在 SnapToNearestData
函数中实现。
private static void SnapToNearestData
(object sender, Series series, Axis xAxis, Axis yAxis, MouseEventArgs e,
ref double XResult, ref double YResult)
{
XResult = YResult = Double.MaxValue;
Chart ptrChart = (Chart)sender;
ChartData ptrChartData = ChartTool[ptrChart];
ChartArea ptrChartArea = ChartTool[ptrChart].ActiveChartArea;
double xMin = xAxis.Minimum;
double xMax = xAxis.Maximum;
//Mouser Pointer Value
double xTarget = xAxis.PixelPositionToValue(e.Location.X);
double yTarget = yAxis.PixelPositionToValue(e.Location.Y);
//Sort data point ascending by X-Values
DataPoint[] datas = series.Points.OrderBy(x => x.XValue).ToArray();
//Get nearest data points
int iLower, iUpper;
iUpper = iLower = 0;
int estIndex = (int)(datas.Length * (xTarget - xMin) / (xMax - xMin));
//iLower --> XValue < xTarget
//iUpper --> XValue > xTarget
if (datas[estIndex].XValue > xTarget)
{
//Search Down
for (int x = estIndex; x > 0; x--)
{
if (datas[x].XValue <= xTarget)
{
iLower = x;
iUpper = x + 1;
break;
}
}
}
else //datas[estIndex].XValue < xTarget
{
//Search Up
for (int x = estIndex; x < datas.Length; x++)
{
if (datas[x].XValue >= xTarget)
{
iUpper = x;
iLower = x - 1;
break;
}
}
}
//Distance = x^2 + y^2
double distLower = Math.Pow(datas[iLower].XValue - xTarget, 2) +
Math.Pow(datas[iLower].YValues[0] - yTarget, 2);
double distUpper = Math.Pow(datas[iUpper].XValue - xTarget, 2) +
Math.Pow(datas[iUpper].YValues[0] - yTarget, 2);
if (distLower > distUpper)
{
XResult = datas[iUpper].XValue;
YResult = datas[iUpper].YValues[0];
}
else
{
XResult = datas[iLower].XValue;
YResult = datas[iLower].YValues[0];
}
}
使用以下方法找到光标最近的数据点
- 按 X 值对数据点(数据)进行排序。
- 假设数据沿 X 轴均匀分布,根据光标的 x 值估算最近的数据索引,其中,
index = 数据计数 x (光标 X 值 - X 最小值) / (X 最大值 - X 最小值)
- 找到 2 个数据点,其中一个的 x 值小于光标的 x 值,另一个大于光标的 x 值。
- 计算每个点到图表光标的距离,距离
d = sqrt(dx^2 + dy^2)
注意:我们省略了平方根函数,因为我们不关心实际距离。 - 距离最短的数据点是光标最近的数据点。
- 根据选定数据点的 X 和 Y 值绘制光标。
一些新的函数
此次发布还包含了一些其他小的更改,如下所示:
- 添加了
IsZoomed
函数以检查是否有任何轴被缩放。 - 添加了
RemoveAnnotation
函数以按名称删除注释。 - 将
SetChartControlState
函数公开为public
,以便以编程方式更改图表控件的状态。
Bug 修复
CursorLineWidth
和CursorDashStyle
不影响光标属性。ZoomChanged
事件不通过鼠标滚轮函数触发。
项目存储库
这是一个活跃的项目,源代码可在 GitHub 上找到,而该库以 NuGet 包的形式发布,可以通过 NuGet 包管理器轻松地将其包含在 Visual Studio 项目中。
历史
- 2019 年 2 月 10 日:初始版本