Skip to content

Interactive Controls

H.P. Gansevoort edited this page Sep 13, 2026 · 10 revisions

Interactive Controls

Native chart controls for Avalonia 12 and Uno Platform (WinUI 3) with 10 interaction modifiers. They need no browser, no WebView and no SignalR.

All listed interactions are enabled by one switch: FigureBuilder.WithBrowserInteraction() in browser hosts, or the IsInteractive property of the Avalonia, Uno and WPF controls in native hosts. The library decides which scripts and handlers to wire. There is no per-feature toggle.

Interaction Gestures (v1.4.1, legend drag added v1.7.2 Phase S)

Gesture Modifier What it does
Left drag (none) Pan the axes
Scroll wheel (none) Zoom in or out
Right drag (on 3D axes) Rotate the 3D camera
Ctrl + left drag Rectangle zoom: draw a box and release to zoom
Shift + left drag Brush select: draw a rectangle, which fires an event
Alt + left drag Span select: selects a horizontal X range
Double-click Reset the axes to auto-scale
Arrow keys (on 3D axes) Rotate the camera by ±5° in azimuth or elevation
Home (on 3D axes) Reset the camera to the default (30°, -60°)
Home / Escape (on 2D axes) Reset the axes
Click legend item Toggle the series' visibility (Enter or Space from the keyboard)
Press-and-hold legend item, drag, release Reposition the legend group (v1.7.2 Phase S; client-only, so the new position is lost when the server re-renders)
Click a data point Pin an annotation to it, and raise DataPointClicked with the point
Hover Show a tooltip for the nearest point, and say which point it is
Mouse move (passive) Draw crosshair lines at the data coordinates

Quick start

Avalonia

dotnet add package MatPlotLibNet.Avalonia
<local:MplChartControl Figure="{Binding MyFigure}" IsInteractive="True" />

Uno Platform

dotnet add package MatPlotLibNet.Uno
<local:MplChartElement Figure="{x:Bind MyFigure}" IsInteractive="True" />

Architecture

The managed interaction layer lives entirely in the zero-dependency core package (MatPlotLibNet). Platform controls translate native pointer and keyboard events into neutral PointerInputArgs, ScrollInputArgs and KeyInputArgs records, then delegate to InteractionController.

Native event (Avalonia / Uno / WinUI)
    │
    ▼
AvaloniaInputAdapter / UnoInputAdapter   ← platform-specific, ~30 lines each
    │
    ▼
InteractionController                    ← core, composes 6 modifiers
    │
    ▼
IInteractionModifier (first match wins)  ← core, one per gesture
    │
    ▼
FigureInteractionEvent                   ← core, same hierarchy as SignalR path
    │
    ▼
evt.ApplyTo(figure)  →  InvalidateVisual()   ← local mode (default)
    or
Action<FigureInteractionEvent> sink           ← server mode (opt-in)

Controller

InteractionController is the central dispatcher. It has two factory methods:

Factory Mode Use case
CreateLocal(figure, layout) Local In-process mutation; the control repaints automatically
Create(figure, layout, sink) Custom Forwards events to SignalR, to logging, or to any Action<FigureInteractionEvent>

Modifiers (priority order)

# Modifier Gesture Event Guard
1 LegendToggleModifier Click on legend item LegendToggleEvent Hit-test in legend bounds
2 ResetModifier Double-click or Home / Escape ResetEvent ClickCount >= 2 or key match
3 BrushSelectModifier Shift + left-drag BrushSelectEvent Button == Left && Shift
4 PanModifier Left-drag (no Shift) PanEvent Button == Left && !Shift
5 ZoomModifier Scroll wheel ZoomEvent Scroll inside plot area
6 HoverModifier Mouse move, no button HoverEvent Button == None in plot area

The first matching modifier wins. Modifiers 1–4 are mutually exclusive: only one of them captures at a time. Modifiers 5–6 are passive and process events independently.

ChartLayout

ChartLayout converts pixel positions to data-space coordinates. It is built from ChartRenderer.ComputeLayout() after each render pass.

Method Purpose
HitTestAxes(pixelX, pixelY) Finds which subplot contains the point
PixelToData(pixelX, pixelY, axesIndex) Converts pixel coordinates to data coordinates
GetDataRange(axesIndex) Returns the current axis limits (XMin, XMax, YMin, YMax)
HitTestLegendItem(pixelX, pixelY, axesIndex) Finds which legend entry was clicked

Event hierarchy

FigureInteractionEvent (abstract)
├── AxisRangeEvent (abstract, sealed ApplyTo)
│   ├── ZoomEvent        ← sets axis limits
│   └── ResetEvent       ← restores original limits
├── PanEvent             ← translates axis limits by delta
├── LegendToggleEvent    ← flips series visibility
└── FigureNotificationEvent (abstract, sealed no-op ApplyTo)
    ├── BrushSelectEvent ← rectangular selection (fire-and-forget)
    └── HoverEvent       ← data-space position (fire-and-forget)

Custom event sink (server mode)

For SignalR-connected charts, construct the controller with a custom sink:

var ctrl = InteractionController.Create(figure, layout, evt =>
{
    hubConnection.SendAsync("ApplyEvent", evt);
});

Or use the built-in extension method:

chartControl.WithServerInteraction(hubConnection);

Rubber-band visual

During a Shift + left drag, the control draws a semi-transparent blue rectangle on the Skia canvas over the chart. The rectangle tracks the current selection. It is driven by the BrushSelectState that the controller exposes.

Hover tooltip

Hovering shows a tooltip with the nearest data point's series name and coordinates. NearestPointFinder searches all visible series within a configurable pixel distance (default 20px).

The point the reader clicked

Clicking a data point pins an annotation to it, and it also tells your application which point it was. Subscribe to DataPointClicked on the control to open a detail panel, select a row in a grid beside the chart, or navigate:

chartControl.DataPointClicked += point =>
{
    // point.SeriesLabel, point.DataX, point.DataY, point.PixelX, point.PixelY, point.AxesIndex,
    // and point.PointIndex / point.SeriesIndex: where the point sits in the arrays you passed in.
    ShowDetail(myRows[point.PointIndex]);
};

The WPF, Avalonia and Uno controls all carry the event under that name, and InteractionController raises it whether the chart applies its events in the control or publishes them to a server. A chart whose figure lives on the server reports the same click over SignalR: opt in with WithServerInteraction(id, i => i.EnableDataCursor()) and receive it with ChartSessionOptions.OnDataCursor. It needs IsInteractive, and a click counts when it lands within ten pixels of an actual point — a click that misses is a pan, as before.

Platform details

Avalonia (MplChartControl)

  • Inherits Control. It renders through ICustomDrawOperation, drawing on ISkiaSharpApiLeaseFeature.Lease().SkCanvas.
  • AffectsRender<MplChartControl>(FigureProperty) invalidates the control automatically when Figure changes.
  • The layout update is marshalled to the UI thread with Dispatcher.UIThread.Post().
  • Targets .NET 10 and .NET 8. Requires Avalonia 12.* with the Skia backend.

Uno (MplChartElement)

  • Inherits SKCanvasElement. RenderOverride(SKCanvas, Size) hands over the canvas directly.
  • Figure and IsInteractive are each a DependencyProperty.
  • The layout update is marshalled with DispatcherQueue.TryEnqueue().
  • Targets Windows 10 19041+, Android 21+, iOS 15+ and macCatalyst 15+.

See also

Clone this wiki locally