Chart
uses a different coordinate system for its content than its Control surface, ie the mouse loacation; there are conversion functions but they come with a caveat: They are only guaranteed to work in the Paint events..
Here is an example that translates the pixel points to chart point values. You can see the two graphics overlaying very nicely: The DataPoints
are connected in blue lines and the pixel points by dotted red lines..:
public Form1()
{
InitializeComponent();
chart1.Series[0].ChartType = SeriesChartType.Line;
chart1.ChartAreas[0].AxisX.Minimum = 0;
chart1.ChartAreas[0].AxisX.Maximum = 500;
chart1.ChartAreas[0].AxisY.Minimum = 0;
chart1.ChartAreas[0].AxisY.Maximum = 500;
}
List<Point> points = new List<Point>();
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
points.Add(e.Location);
chart1.Invalidate();
}
private void chart1_Paint(object sender, PaintEventArgs e)
{
chart1.Series[0].Points.Clear();
foreach(Point pt in points)
{
double dx = chart1.ChartAreas[0].AxisX.PixelPositionToValue(pt.X);
double dy = chart1.ChartAreas[0].AxisY.PixelPositionToValue(pt.Y);
chart1.Series[0].Points.AddXY(dx, dy);
}
if (points.Count > 1)
using (Pen pen = new Pen(Color.Red, 2.5f))
e.Graphics.DrawLines(pen, points.ToArray());
}
Note that this will always clear the DataPoints
and recreate them from the pixel points list, according to the current chart layout using the PixelPositionToValue
method. The layout will always change when things like label sizes, other scaling, other minimum/maximum values etc change.
Maybe you really want to work the other way round, that is change the clicked points using the ValueToPixelPosition
.
Here is the modified example that keeps the DataPoints
and recalculates the pixel points:
List<Point> points = new List<Point>();
Point lastPoint = Point.Empty;
private void chart1_MouseClick(object sender, MouseEventArgs e)
{
lastPoint = e.Location;
chart1.Invalidate();
}
private void chart1_Paint(object sender, PaintEventArgs e)
{
// if we have a new point, convert to DataPoint and add to Series.Points:
if (lastPoint != Point.Empty)
{
double dx = chart1.ChartAreas[0].AxisX.PixelPositionToValue(lastPoint.X);
double dy = chart1.ChartAreas[0].AxisY.PixelPositionToValue(lastPoint.Y);
chart1.Series[0].Points.AddXY(dx, dy);
}
lastPoint = Point.Empty;
// now recalculate all pixel points:
points.Clear();
foreach (DataPoint pt in chart1.Series[0].Points)
{
double x = chart1.ChartAreas[0].AxisX.ValueToPixelPosition(pt.XValue);
double y = chart1.ChartAreas[0].AxisY.ValueToPixelPosition(pt.YValues[0]);
points.Add(new Point((int)x, (int)y));
}
if (points.Count > 1)
using (Pen pen = new Pen(Color.Red, 2.5f))
{
pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
e.Graphics.DrawLines(pen, points.ToArray());
}
}
This makes a lot more sense, since the DataPoints
are always bound to the chart's scaling, so they are the 'real thing'. When you resize the Chart the DataPoints
and the Graphic they make up are scaled as usual and the drawn pixel points follow perfectly:
(When you resize the first version you can see how nothing is being scaled up or down and only the chart's grid lines change..)
Note that I set up a few things to start with, so that not every point I add enforces too many layout changes. Also note that sometimes there still occurs a feedback loop when the new points change e.g. the label sizes, which enforces a layout change and the paint loop.. To fix this you should probably control the labels' formats!
Also note that both conversion methods only work (correctly) in the Paint
event(s), probably because only then the current layout is being settled.