-
Notifications
You must be signed in to change notification settings - Fork 3
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 theIsInteractiveproperty 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.
| 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 |
dotnet add package MatPlotLibNet.Avalonia<local:MplChartControl Figure="{Binding MyFigure}" IsInteractive="True" />dotnet add package MatPlotLibNet.Uno<local:MplChartElement Figure="{x:Bind MyFigure}" IsInteractive="True" />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)
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>
|
| # | 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 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 |
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)
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);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.
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).
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.
- Inherits
Control. It renders throughICustomDrawOperation, drawing onISkiaSharpApiLeaseFeature.Lease().SkCanvas. -
AffectsRender<MplChartControl>(FigureProperty)invalidates the control automatically whenFigurechanges. - The layout update is marshalled to the UI thread with
Dispatcher.UIThread.Post(). - Targets
.NET 10and.NET 8. RequiresAvalonia 12.*with the Skia backend.
- Inherits
SKCanvasElement.RenderOverride(SKCanvas, Size)hands over the canvas directly. -
FigureandIsInteractiveare each aDependencyProperty. - The layout update is marshalled with
DispatcherQueue.TryEnqueue(). - Targets Windows 10 19041+, Android 21+, iOS 15+ and macCatalyst 15+.
- Package-Map — all 14 NuGet and 3 npm packages at a glance
- Roadmap — shipped and planned releases
- MatPlotLibNet.Avalonia README
- MatPlotLibNet.Uno README
MatPlotLibNet 1.18.0