Pressed and hover events

You can request a chart to find the elements in a given position using the Chart.GetPointsAt() or Chart.GetVisualsAt() methods.

Finding hovered or pressed points

When a series point is drawn, it also defines a virtual area called HoverArea, this area is what determines when a point is pressed/hovered; For example in the next gif, the tooltip opens for both Mary and Ana even when the pointer is not in the drawn shape, this is because the HoverArea is not the same as the drawn column.

sample image

FindingStrategy

The Chart.FindingStrategy property, defines the method to use to find points in the chart, this strategy is used for tooltips, hover events and pressed events. By default it is FindingStrategy.Automatic, and it means that the chart will decide the best strategy based on the defaults on each series type, in the next example we use the default automatic strategy in a couple of column series, you can learn more about the supported strategies here.

Chart events

This is the recommended way to detect when a ChartPoint is pressed or hovered by the user, you can use the Pressed or HoveredPointsChanged commands/events to run actions as the user interacts with the chart.

using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using CommunityToolkit.Mvvm.Input;
using LiveChartsCore;
using LiveChartsCore.Drawing;
using LiveChartsCore.Kernel;
using LiveChartsCore.Kernel.Events;
using LiveChartsCore.Measure;
using LiveChartsCore.SkiaSharpView;
using LiveChartsCore.SkiaSharpView.Painting;
using SkiaSharp;

namespace ViewModelsSamples.Events.Tutorial;

public partial class ViewModel
{
    private readonly HashSet<ChartPoint> _activePoints = [];

    public FindingStrategy Strategy { get; } = FindingStrategy.ExactMatch;

    public ISeries[] SeriesCollection { get; set; } = [
        new ColumnSeries<int>([1, 5, 4, 3])
        {
            Stroke = new SolidColorPaint { Color = SKColors.Transparent }
        },
        new ColumnSeries<int>([3, 2, 6, 2])
        {
            Stroke = new SolidColorPaint { Color = SKColors.Transparent }
        }
    ];

    [RelayCommand]
    public void OnPressed(PointerCommandArgs args)
    {
        var foundPoints = args.Chart.GetPointsAt(args.PointerPosition);

        foreach (var point in foundPoints)
        {
            var geometry = (DrawnGeometry)point.Context.Visual!;

            if (!_activePoints.Contains(point))
            {
                geometry.Fill = new SolidColorPaint { Color = SKColors.Yellow };
                _activePoints.Add(point);
            }
            else
            {
                // clear the fill to the default value
                geometry.Fill = null;
                _activePoints.Remove(point);
            }

            Trace.WriteLine($"found {point.Context.DataSource}");
        }
    }

    [RelayCommand]
    public void OnHoveredPointsChanged(HoverCommandArgs args)
    {
        // the NewPoints contains the new hovered points // mark
        foreach (var hovered in args.NewPoints ?? [])
        {
            // in this case, we will set a black stroke on the drawn gemetry. // mark
            var geometry = (DrawnGeometry)hovered.Context.Visual!;
            geometry.Stroke = new SolidColorPaint(SKColors.Black, 3);
        }

        // the OldPoints contains the points that are not hovered anymore // mark
        foreach (var hovered in args.OldPoints ?? [])
        {
            // now, we will clear the stroke. // mark
            var geometry = (DrawnGeometry)hovered.Context.Visual!;
            geometry.Stroke = null;
        }

        Trace.WriteLine(
            $"hovered, {args.NewPoints?.Count()} added, {args.OldPoints?.Count()} removed");
    }
}

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage
    x:Class="MauiSample.Events.Tutorial.View"
    xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:lvc="clr-namespace:LiveChartsCore.SkiaSharpView.Maui;assembly=LiveChartsCore.SkiaSharpView.Maui"
    xmlns:vms="clr-namespace:ViewModelsSamples.Events.Tutorial;assembly=ViewModelsSamples">

    <ContentPage.BindingContext>
        <vms:ViewModel/>
    </ContentPage.BindingContext>

    <lvc:CartesianChart
        Series="{Binding SeriesCollection}"
        FindingStrategy="{Binding Strategy}"
        PressedCommand="{Binding PressedCommand}"
        HoveredPointsChangedCommand="{Binding HoveredPointsChangedCommand}">
    </lvc:CartesianChart>

</ContentPage>

In that example, we created the OnHoveredPointsChanged method, this method is called every time the "hovered" points change, hover occurs when the pointer is over a ChartPoint, we toggle the Stroke from null to black when the pointer is over a column.

Also in the OnPressed method, we toggle the Fill from yellow to the default colors of the series, the Pressed command/event is a standard in the library for all the supported UI frameworks, but you can also use the events/commands provided in your UI framework.

Both OnHoveredPointsChanged and OnPressed are marked with the RelayCommand attribute, this generates the PressedCommand and HoveredPointsChangedCommand properties.

When running that example on the FindingStrategy.Automatic we get:

sample image

But changing the strategy to FindingStrategy.ExactMatch, will only trigger only the points whose drawn column contains the pointer:

sample image

Override the find logic

You can also build your own logic by overriding the Series.FindPointsInPosition method, for example in the next case, when the find request is made by a hover action, we return only the points whose hover area contains the pointer in the X axis, but when the request is made by any other source, we evaluate whether the pointer is inside the Y axis.

using System.Collections.Generic;
using System.Linq;
using LiveChartsCore;
using LiveChartsCore.Drawing;
using LiveChartsCore.Kernel;
using LiveChartsCore.Kernel.Drawing;
using LiveChartsCore.Measure;
using LiveChartsCore.SkiaSharpView;

namespace ViewModelsSamples.Events.OverrideFind;

public class ViewModel
{
    public ISeries[] Series { get; set; } = [
        new CustomColumnSeries<int> { Values = [9, 5, 7, 3, 7, 3] },
        new CustomColumnSeries<int> { Values = [8, 2, 3, 2, 5, 2] }
    ];

    public class CustomColumnSeries<T> : ColumnSeries<T>
    {
        protected override IEnumerable<ChartPoint> FindPointsInPosition(
            Chart chart, LvcPoint pointerPosition, FindingStrategy strategy, FindPointFor findPointFor)
        {
            return Fetch(chart).Where(point =>
            {
                var ha = (RectangleHoverArea?)point.Context.HoverArea;
                if (ha is null) return false;

                var isInsideX = ha.X <= pointerPosition.X && pointerPosition.X <= ha.X + ha.Width;
                var isInsideY = ha.Y <= pointerPosition.Y && pointerPosition.Y <= ha.Y + ha.Height;

                return findPointFor == FindPointFor.HoverEvent
                    ? isInsideX
                    : isInsideY;
            });
        }
    }
}