/// <summary> /// Gets the nearest point but allows for normalising the x and y /// by different distances, which is required if you scale the x /// and y seperately, but want to get the visually closest point. /// </summary> /// <param name="points"></param> /// <param name="point"></param> /// <param name="inverseNormalisation"></param> /// <param name="nearestDistanceSquared"></param> /// <returns></returns> public static PointAndPrimitive GetEllipseScaledNearestPoint(IEnumerable <PointAndPrimitive> points, Point point, Vector ratio, out double nearestDistanceSquared) { Point inverseNormalisation = new Point(1 / ratio.X, 1 / ratio.Y); nearestDistanceSquared = 0; PointAndPrimitive nearestPoint = new PointAndPrimitive(point, null); try { // Just pick off the first point to initialize stuff foreach (PointAndPrimitive testPoint in points) { nearestDistanceSquared = new Vector((testPoint.X - point.X) * inverseNormalisation.X, (testPoint.Y - point.Y) * inverseNormalisation.Y).LengthSquared; nearestPoint = testPoint; break; } // Loop through all points to find the closest foreach (PointAndPrimitive testPoint in points) { double distanceSquared = new Vector((testPoint.X - point.X) * inverseNormalisation.X, (testPoint.Y - point.Y) * inverseNormalisation.Y).LengthSquared; if (distanceSquared < nearestDistanceSquared) { nearestDistanceSquared = distanceSquared; nearestPoint = testPoint; } } } catch (Exception) { } return(nearestPoint); }
/// <summary> /// Handles when the mouse moves in a way that that it can execute on a thread that isn't /// the GUI thread. /// </summary> /// <param name="mousePos"></param> /// <param name="minimumBounds"></param> /// <returns></returns> public Point MouseMoved(Point mousePos, Rect minimumBounds) { bool newLocked = false; PointAndPrimitive newPoint = new PointAndPrimitive(mousePos, null); lock (_points) { if (_points.Count > 0) { double nearestDistanceSquared; newPoint = GetEllipseScaledNearestPoint(_points, mousePos, (Vector)(minimumBounds.Size), out nearestDistanceSquared); newLocked = nearestDistanceSquared <= 1; } } bool lockedChanged = newLocked != _locked; bool pointChanged = newPoint.Point != _closestPoint.Point; if (_closestPoint.Primitive != null && _closestPoint.Primitive.LegendLabel != null) { _closestPoint.Primitive.LegendLabel.IsHighlighted = false; } _locked = newLocked; _closestPoint = newPoint; Action updateGui = () => { if (_closestPoint.Primitive != null) { ChartPrimitive primitive = _closestPoint.Primitive; if (primitive.LegendColor != Colors.Transparent && primitive.LegendLabel != null && _locked) { primitive.LegendLabel.IsHighlighted = true; } } if ((pointChanged && _locked) || lockedChanged) { OnClosestPointChanged(); } }; if (_dispatcher.Thread != Thread.CurrentThread) { _dispatcher.Invoke(updateGui); } else { updateGui(); } return(_locked ? _closestPoint.Point : mousePos); }
/// <summary> /// Gets the nearest point but allows for normalising the x and y /// by different distances, which is required if you scale the x /// and y seperately, but want to get the visually closest point. /// </summary> /// <param name="points"></param> /// <param name="point"></param> /// <param name="inverseNormalisation"></param> /// <param name="nearestDistanceSquared"></param> /// <returns></returns> public static PointAndPrimitive GetEllipseScaledNearestPoint(IEnumerable<PointAndPrimitive> points, Point point, Vector ratio, out double nearestDistanceSquared) { Point inverseNormalisation = new Point(1 / ratio.X, 1 / ratio.Y); nearestDistanceSquared = 0; PointAndPrimitive nearestPoint = new PointAndPrimitive(point, null); // Just pick off the first point to initialize stuff foreach(PointAndPrimitive testPoint in points) { nearestDistanceSquared = new Vector((testPoint.X - point.X) * inverseNormalisation.X, (testPoint.Y - point.Y) * inverseNormalisation.Y).LengthSquared; nearestPoint = testPoint; break; } // Loop through all points to find the closest foreach(PointAndPrimitive testPoint in points) { double distanceSquared = new Vector((testPoint.X - point.X) * inverseNormalisation.X, (testPoint.Y - point.Y) * inverseNormalisation.Y).LengthSquared; if(distanceSquared < nearestDistanceSquared) { nearestDistanceSquared = distanceSquared; nearestPoint = testPoint; } } return nearestPoint; }
/// <summary> /// Handles when the mouse moves in a way that that it can execute on a thread that isn't /// the GUI thread. /// </summary> /// <param name="mousePos"></param> /// <param name="minimumBounds"></param> /// <returns></returns> public Point MouseMoved(Point mousePos, Rect minimumBounds) { bool newLocked = false; PointAndPrimitive newPoint = new PointAndPrimitive(mousePos, null); if(_points.Count > 0) { double nearestDistanceSquared; newPoint = GetEllipseScaledNearestPoint(_points, mousePos, (Vector)(minimumBounds.Size), out nearestDistanceSquared); newLocked = nearestDistanceSquared <= 1; } bool lockedChanged = newLocked != _locked; bool pointChanged = newPoint.Point != _closestPoint.Point; if(_closestPoint.Primitive != null && _closestPoint.Primitive.LegendLabel != null) { _closestPoint.Primitive.LegendLabel.IsHighlighted = false; } _locked = newLocked; _closestPoint = newPoint; Action updateGui = () => { if(_closestPoint.Primitive != null) { ChartPrimitive primitive = _closestPoint.Primitive; if(primitive.LegendColor != Colors.Transparent && primitive.LegendLabel != null && _locked) { primitive.LegendLabel.IsHighlighted = true; } } if((pointChanged && _locked) || lockedChanged) { OnClosestPointChanged(); } }; if(_dispatcher.Thread != Thread.CurrentThread) { _dispatcher.Invoke(updateGui); } else { updateGui(); } return _locked ? _closestPoint.Point : mousePos; }