Here are two ways to solve the problem; none can actually make the HitTest
ignore clicking on the connecting lines.
But they should be fine, espacially when you implement them both.
The first provides feedback to the user so he can see in advance which point the mouse is over and he is about to click:
DataPoint dpCurrent = null;
int normalMarkerSize = 8;
int largeMarkerSize = 12;
private void chart1_MouseMove(object sender, MouseEventArgs e)
{
HitTestResult hit = chart1.HitTest(e.X, e.Y);
if (hit.ChartElementType == ChartElementType.DataPoint)
{
dpCurrent = hit.Series.Points[hit.PointIndex];
dpCurrent.MarkerSize = largeMarkerSize;
}
else
{
if (dpCurrent != null) dpCurrent.MarkerSize = normalMarkerSize;
dpCurrent = null;
}
Unfortunately the HitTest
will still trigger a DataPoint-hit even if you only hit the connecting lines, no matter how thin you make them or what color they have..
..enter solution two:
One can write a custom HitTest by calculating the pixel-coordinates of the DataPoints
; this is not as simple as calling the Axis.ValueToPixelPosition
methods as it involves some modest amount of math..:
Now before processing the hit you would do an additional check, maybe like this:
if (distance(PolarValueToPixelPosition(dpCurrent, chart1,
hit.ChartArea), e.Location) <= markerRadius) ...//do the hit stuff
Here is the coordinate transformation function:
PointF PolarValueToPixelPosition(DataPoint dp, Chart chart, ChartArea ca)
{
RectangleF ipp = InnerPlotPositionClientRectangle(chart, ca);
double crossing = ca.AxisX.Crossing != double.NaN ? ca.AxisX.Crossing : 0;
// for RangeChart change 90 zo 135 !
float phi = (float)(360f / ca.AxisX.Maximum / 180f * Math.PI *
(dp.XValue - 90 + crossing ) );
float yMax = (float)ca.AxisY.Maximum;
float yMin = (float)ca.AxisY.Minimum;
float radius = ipp.Width / 2f;
float len = (float)(dp.YValues[0] - yMin) / (yMax - yMin);
PointF C = new PointF(ipp.X + ipp.Width / 2f, ipp.Y + ipp.Height / 2f);
float xx = (float)(Math.Cos(phi) * radius * len);
float yy = (float)(Math.Sin(phi) * radius * len);
return new PointF(C.X + xx, C.Y + yy);
}
It makes use of a simple distance function..:
float distance(PointF pt1, PointF pt2)
{
float d = (float)Math.Sqrt((pt1.X - pt2.X) * (pt1.X - pt2.X)
+ (pt1.Y - pt2.Y) * (pt1.Y - pt2.Y));
return d;
}
Also of two other useful functions to calculate the pixel size of the InnerPlotPosition
, which I have used in quite a few answers now..:
RectangleF ChartAreaClientRectangle(Chart chart, ChartArea CA)
{
RectangleF CAR = CA.Position.ToRectangleF();
float pw = chart.ClientSize.Width / 100f;
float ph = chart.ClientSize.Height / 100f;
return new RectangleF(pw * CAR.X, ph * CAR.Y, pw * CAR.Width, ph * CAR.Height);
}
RectangleF InnerPlotPositionClientRectangle(Chart chart, ChartArea CA)
{
RectangleF IPP = CA.InnerPlotPosition.ToRectangleF();
RectangleF CArp = ChartAreaClientRectangle(chart, CA);
float pw = CArp.Width / 100f;
float ph = CArp.Height / 100f;
return new RectangleF(CArp.X + pw * IPP.X, CArp.Y + ph * IPP.Y,
pw * IPP.Width, ph * IPP.Height);
}