NOTE: Binary and source file updated to version 2.2.4 with several bug fixes.
MSChart Extension is an extension class for Microsoft Chart (MSChart) control in Visual Studio for WinForms applications. The tool was first published on July 2012 with the intention to overcome some of the limitation from the original MSChart. If you are new to MSChart Extension, we recommend you to read the previous articles listed below first.
These articles including this is created as technical sharing as well as documentation for this library.
- MSChart Extensions is designed for chart type with X and Y Axis, the extension method will not work with some of the chart type such as Radar and Pie.
- It is known that zoom does not works properly with Log Axis. We decided to disable extensions functions for chart with LOG Axis.
- Date Time Axis - Zoom may not behave correctly for chart where XAxis values in DateTime format.
Zoom along X-Axis can now be done by pressing the CTRL + ALT Key while moving the Mouse wheel to zoom in / out X-Axis of the chart. This is the simplified version of the Mouser Wheel Zoom function with Y-Axis remain untouched. The code take consideration both X and X2 Axis.
private static void ChartControl_MouseWheel(object sender, MouseEventArgs e)
if (Form.ModifierKeys == (Keys.Alt | Keys.Control))
Series Selection for Chart Cursor
From Version 2.2.0 onward, user can now select which series to use for chart cursor 1 and 2. Prior to Version 2.2.0, both chart cursors is only works with primary X-Axis and Y-Axis. An additional drop down menu is added to "Select - Cursor n" menu to let user to choose which series to use by chart cursor. This drop down menu will not exist if the selected chart area contains only one axis.
The additional drop down menu is created in context menu opening event.
Drop down menu for "Select - Cursor n" menu is cleared. Next, we filter out series which belongs to selected chart area using
foreach loop. If series count is only 1, drop down menu is not created and chart cursor will always use the one and only one series in chart. Otherwise, a drop down menu is created for user to select which series to use for each chart cursor. On top of that , a
Clear Cursors... function is added to remove all cursors from chart.
private static void ChartContext_Opening(object sender, CancelEventArgs e)
List<Series> ChartSeries = new List<Series>();
SeriesCollection chartSeries = ((Chart)menuStrip.SourceControl).Series;
if (ptrChartData.ActiveChartArea != null)
ToolStripSeparator separator = new ToolStripSeparator();
separator.Tag = "Series";
foreach (Series ptrSeries in chartSeries)
if (ptrSeries.ChartArea != ptrChartData.ActiveChartArea.Name) continue;
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;
ptrChartData.Cursor2.SelectedChartSeries = ChartSeries;
else if (chartSeries.Count > 1)
if (!ChartSeries.Contains(ptrChartData.Cursor1.SelectedChartSeries)) ptrChartData.Cursor1.SelectedChartSeries = ChartSeries;
if (!ChartSeries.Contains(ptrChartData.Cursor2.SelectedChartSeries)) ptrChartData.Cursor2.SelectedChartSeries = ChartSeries;
if (ChartSeries.Count > 1)
foreach (Series s in ChartSeries)
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;
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;
Since the drop down menu is recreated each time chart context menu open, it's important to remember the last selected series for each chart cursor somewhere else. A new property named
SelectedChartSeries is added to
ChartCursor class to store the last selected cursor.
All the menu item in context menu will trigger the event
ChartContext_ItemClicked when clicked. However, the drop down menu which we added to chart cursors does not trigger this event. Hence, we subscribed to the
Click event for this newly added menu item. The
Tag property of the series menu added to chart cursor is set to eithe
ChartToolSelect2 to identify which cursor is activate when serving the click event.
Selecting series for selected chart cursor is not solely to identify which X and Y Axis to use, but also preparation for the next feature.
Snap Cursor to Nearest Data Point
The chart data is always much interesting compare to those empty area of the chart. Hence, it's important for the chart cursor to snap to the nearest data point of the selected series for more precise analysis. This feature is first introduced in this Version 2.2.0. Should anyone prefer the old way of how chart cursor works, simply set the
ChartOption to set chart cursor free again.
This search function is implemented in
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;
double xTarget = xAxis.PixelPositionToValue(e.Location.X);
double yTarget = yAxis.PixelPositionToValue(e.Location.Y);
DataPoint datas = series.Points.OrderBy(x => x.XValue).ToArray();
int iLower, iUpper;
iUpper = iLower = 0;
int estIndex = (int)(datas.Length * (xTarget - xMin) / (xMax - xMin));
if (datas[estIndex].XValue > xTarget)
for (int x = estIndex; x > 0; x--)
if (datas[x].XValue <= xTarget)
iLower = x;
iUpper = x + 1;
for (int x = estIndex; x < datas.Length; x++)
if (datas[x].XValue >= xTarget)
iUpper = x;
iLower = x - 1;
double distLower = Math.Pow(datas[iLower].XValue - xTarget, 2) + Math.Pow(datas[iLower].YValues - yTarget, 2);
double distUpper = Math.Pow(datas[iUpper].XValue - xTarget, 2) + Math.Pow(datas[iUpper].YValues - yTarget, 2);
if (distLower > distUpper)
XResult = datas[iUpper].XValue;
YResult = datas[iUpper].YValues;
XResult = datas[iLower].XValue;
YResult = datas[iLower].YValues;
The nearest data point to cursor is find using the following method:
- Sort data points (data) by X value.
- Assume that data is evenly distributed along X-Axis, estimate the nearest data index based on x value of cursor, where:
index = Data_Count x ( Cursor_X_Value - X_Minimum ) / ( X_Maximum - X_Minimum)
- Find 2 data point where 1 with x value less than cursor's x value and another with x value greater than cursor's x value.
- Calculate distance of each point to chart cursor where distance
d = sqrt(dx^2 + dy^2)
Note: We omitted the square root function since we are not interested with the actual distance.
- Data with shortest distance is the nearest data point to cursor.
- Draw cursor based on the X and Y value of selected data point.
Some more new functions
Some other minor changes included together with this release as follow:
- Added function
IsZoomed to check if any of the axis is zoomed.
- Added function
RemoveAnnotation to remove annotation by name.
- Exposed function
SetChartControlState as public for changing chart control state programmatically.
CursorDashStyle does not effect cursor property.
ZoomChanged event not trigger on Mouse Scroll function.
This is an active project where the source code is available in GitHub, while the library is released as NuGet Package which can be easily included in Visual Studio project via NuGet Package Manager.