Mouse events for arbitrary points on CartesianChart
See original GitHub issueIs your feature request related to a problem? Please describe. CartesianChart already has mouse events for individual data points, but it would be nice to also have similar events for any position on the chart. The usefulness of these events is that they can provide event arguments with the point converted to the chart coordinate system.
For example, something like the AddPointOnClick
example in the repository could be simplified by moving all the logic to the view model. Currently this example needs a separate implementation for each platform.
Describe the solution you’d like
I have a custom CartesianChart
class I use in my own WPF project that adds these events. The same functionality could be added directly to the charts in the core library. I’m not too familiar with any of the platforms other that WPF, but I assume there would have to be a small platform-specific part to add handlers to the relevant mouse events for the platform and get the event coordinates.
The implementation is fairly simple. Basically the native events are forwarded directly with a custom event arguments object that also includes the chart-relative coordinates. The custom sub-class I am using is included below:
using System;
using System.Windows;
using System.Windows.Input;
using LiveCharts.Wpf;
namespace Foo;
/// <summary>
/// Extension of the <see cref="CartesianChart"/> component that adds mouse events that report their event position
/// as chart values using <see cref="Extentions.ConvertToChartValues(LiveCharts.Wpf.Charts.Base.Chart, Point, int, int)"/>.
/// </summary>
public class CustomCartesianChart : CartesianChart
{
#region Dependency Properties
/// <summary>
/// Dependency property for <see cref="MouseDownPointCommand"/>.
/// </summary>
public static readonly DependencyProperty MouseDownPointCommandProperty =
DependencyProperty.Register(nameof(MouseDownPointCommand), typeof(ICommand), typeof(CustomCartesianChart));
/// <summary>
/// Dependency property for <see cref="MouseUpPointCommand"/>.
/// </summary>
public static readonly DependencyProperty MouseUpPointCommandProperty =
DependencyProperty.Register(nameof(MouseUpPointCommand), typeof(ICommand), typeof(CustomCartesianChart));
/// <summary>
/// Dependency property for <see cref="DoubleClickPointCommand"/>.
/// </summary>
public static readonly DependencyProperty DoubleClickPointCommandProperty =
DependencyProperty.Register(nameof(DoubleClickPointCommand), typeof(ICommand), typeof(CustomCartesianChart));
#endregion
#region Events
/// <summary>
/// Event triggered when the <see cref="UIElement.MouseDown"/> event occurs on a point in the graph.
/// </summary>
public event EventHandler<MouseButtonPointEventArgs>? MouseDownPoint;
/// <summary>
/// Event triggered when the <see cref="UIElement.MouseUp"/> event occurs on a point in the graph.
/// </summary>
public event EventHandler<MouseButtonPointEventArgs>? MouseUpPoint;
/// <summary>
/// Event triggered when the <see cref="System.Windows.Controls.Control.MouseDoubleClick"/> event occurs on a point in the graph.
/// </summary>
public event EventHandler<MouseButtonPointEventArgs>? DoubleClickPoint;
#endregion
#region Command Properties
/// <summary>
/// Gets or sets a command to be executed after the <see cref="MouseDownPoint"/> event is triggered.
/// </summary>
public ICommand? MouseDownPointCommand {
get => (ICommand?)GetValue(MouseDownPointCommandProperty);
set => SetValue(MouseDownPointCommandProperty, value);
}
/// <summary>
/// Gets or sets a command to be executed after the <see cref="MouseUpPoint"/> event is triggered.
/// </summary>
public ICommand? MouseUpPointCommand {
get => (ICommand?)GetValue(MouseUpPointCommandProperty);
set => SetValue(MouseUpPointCommandProperty, value);
}
/// <summary>
/// Gets or sets a command to be executed after the <see cref="DoubleClickPoint"/> event is triggered.
/// </summary>
public ICommand? DoubleClickPointCommand {
get => (ICommand?)GetValue(DoubleClickPointCommandProperty);
set => SetValue(DoubleClickPointCommandProperty, value);
}
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="CustomCartesianChart"/> class.
/// </summary>
public CustomCartesianChart()
: base()
{
// Create mouse event handlers
MouseDown += (sender, e) => HandleMouseEvent(sender, e, MouseDownPoint, MouseDownPointCommand);
MouseUp += (sender, e) => HandleMouseEvent(sender, e, MouseUpPoint, MouseUpPointCommand);
MouseDoubleClick += (sender, e) => HandleMouseEvent(sender, e, DoubleClickPoint, DoubleClickPointCommand);
}
/// <summary>
/// Handle a mouse event by invoking the specified event handler and command. The arguments passed to the
/// invoked methods includes the graph point that was clicked.
/// </summary>
private void HandleMouseEvent(object sender, MouseButtonEventArgs e, EventHandler<MouseButtonPointEventArgs>? eventHandler, ICommand? command)
{
// Do nothing if there is no handler or command listening to this event
if (eventHandler == null && command == null) return;
// Get the data point that was clicked
// TODO: Make sure the point is within the graph?
var point = this.ConvertToChartValues(e.GetPosition(this));
// Invoke event handler, if any
var pointArgs = new MouseButtonPointEventArgs(point, e);
eventHandler?.Invoke(sender, pointArgs);
// Invoke command, if possible
if (command != null && command.CanExecute(pointArgs)) command.Execute(pointArgs);
}
}
And this is the custom event argument class:
/// <summary>
/// Event arguments that describe a mouse event at a specific point in a LiveCharts chart.
/// </summary>
public class MouseButtonPointEventArgs : MouseButtonEventArgs
{
/// <summary>
/// Gets or sets the point on the chart associated with the event.
/// </summary>
public Point Point { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MouseButtonPointEventArgs"/> class from an existing
/// <see cref="MouseButtonEventArgs"/> instance.
/// </summary>
/// <param name="point">The point on the graph where the event occurred.</param>
/// <param name="baseEvent">The arguments from the mouse event that occurred.</param>
public MouseButtonPointEventArgs(Point point, MouseButtonEventArgs baseEvent)
: this(point, (baseEvent ?? throw new ArgumentNullException(nameof(baseEvent))).MouseDevice, baseEvent.Timestamp, baseEvent.ChangedButton, baseEvent.StylusDevice)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MouseButtonPointEventArgs"/> class.
/// </summary>
/// <param name="point">The point on the graph where the event occurred.</param>
/// <param name="mouse">The logical Mouse device associated with this event.</param>
/// <param name="timestamp">The time when the input occurred.</param>
/// <param name="button">The mouse button whose state is being described.</param>
/// <param name="stylusDevice">The stylus device associated with this event.</param>
public MouseButtonPointEventArgs(Point point, MouseDevice mouse, int timestamp, MouseButton button, StylusDevice stylusDevice)
: base(mouse, timestamp, button, stylusDevice)
{
Point = point;
}
}
Describe alternatives you’ve considered The alternate solution above with a custom sub-class works well. I am just suggesting this to see if it seems like a good idea to add to the core library.
Additional context The following events could also be potentially useful to add in a similar manner
MouseLeftButtonDown
MouseLeftButtonUp
MouseRightButtonDown
MouseRightButtonUp
MouseWheel
Issue Analytics
- State:
- Created 2 years ago
- Comments:10 (3 by maintainers)
This is a great idea., It will simplify the API, and yes, you are right, we can define a core event handler, then all the platforms could use the same handler/command to know the coordinates of the point that was clicked.
But at this point there are some missing features, we need some extra work before this can be added, I will keep this issue open as a reminder to improve this.
That was a long and repetitive PR! but that should fix this issue, I updated the
Events/AddPointOnClick
andGeneral/Scrollable
samples to use commands instead, and yes, the same command is shared for all the supported platforms, this is definitely much cleaner.This will be included in the next version of the library, the samples in the web site will be updated automatically with the changes in this PR, but we still missing a good article about events in the site.