-
Notifications
You must be signed in to change notification settings - Fork 2
Interactive Controls
Native chart controls for Avalonia 12 and Uno Platform (WinUI 3) with 10 interaction modifiers — no browser, no WebView, no SignalR required.
All listed interactions are enabled in one switch (
FigureBuilder.WithBrowserInteraction()in browser hosts; the Avalonia / Uno / WPF controls'IsInteractiveproperty in native hosts). The library decides which scripts/handlers to wire per chart and which to omit. There is no per-feature toggle for the user to manage.
| Gesture | Modifier | What it does |
|---|---|---|
| Left drag | (none) | Pan axes |
| Scroll wheel | (none) | Zoom in/out |
| Right drag | (on 3D axes) | Rotate 3D camera |
| Ctrl + left drag | Rectangle zoom — draw box, release zooms | |
| Shift + left drag | Brush select — draw rectangle, fires event | |
| Alt + left drag | Span select — horizontal X-range selection | |
| Double-click | Reset axes to auto-scale | |
| Arrow keys | (on 3D axes) | Rotate ±5° azimuth/elevation |
| Home | (on 3D axes) | Reset camera to default (30°, -60°) |
| Home / Escape | (on 2D axes) | Reset axes |
| Click legend item | Toggle series visibility (or Enter / Space for keyboard) |
|
| Press-and-hold legend item, drag, release | Reposition the legend group (v1.7.2 Phase S; client-only, lost on server re-render) | |
| Hover | Show nearest-point tooltip | |
| Mouse move | (passive) | Crosshair lines at 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/keyboard events into neutral PointerInputArgs / ScrollInputArgs / 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. Two factory methods:
| Factory | Mode | Use case |
|---|---|---|
CreateLocal(figure, layout) |
Local | In-process mutation, control repaints automatically |
Create(figure, layout, sink) |
Custom | Forward events to SignalR, logging, or 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 |
First matching modifier wins. Modifiers 1–4 are mutually exclusive (only one captures at a time). Modifiers 5–6 are passive (process independently).
ChartLayout converts pixel positions to data-space coordinates. Built from ChartRenderer.ComputeLayout() after each render pass.
| Method | Purpose |
|---|---|
HitTestAxes(pixelX, pixelY) |
Which subplot contains the point? |
PixelToData(pixelX, pixelY, axesIndex) |
Convert pixel → data coordinates |
GetDataRange(axesIndex) |
Current axis limits (XMin, XMax, YMin, YMax) |
HitTestLegendItem(pixelX, pixelY, axesIndex) |
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)
Mutation events (ZoomEvent, PanEvent, ResetEvent, LegendToggleEvent) modify the figure and trigger a repaint. Notification events (BrushSelectEvent, HoverEvent) observe without mutating — ApplyTo is sealed empty.
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 (brush select), a semi-transparent blue rectangle is drawn on the chart surface showing the current selection area. The rectangle is rendered directly on the Skia canvas after the chart, using the BrushSelectState exposed by the controller.
When hovering over data in an interactive chart, a tooltip appears showing the nearest data point's series name and coordinates. The NearestPointFinder searches all visible series within a configurable pixel distance (default: 20px) and returns the closest match.
- Inherits
Control; renders viaICustomDrawOperation→ISkiaSharpApiLeaseFeature.Lease().SkCanvas -
AffectsRender<MplChartControl>(FigureProperty)— auto-invalidates onFigurechange - Layout update marshalled to UI thread via
Dispatcher.UIThread.Post() - Targets
.NET 10+.NET 8; requiresAvalonia 12.*with Skia backend
- Inherits
SKCanvasElement;RenderOverride(SKCanvas, Size)hands the canvas directly -
DependencyPropertyforFigureandIsInteractive - Layout update marshalled via
DispatcherQueue.TryEnqueue() - Targets Windows 10 19041+, Android 21+, iOS 15+, macCatalyst 15+
- Package-Map — all 11+ packages at a glance
- Roadmap — shipped and planned releases
- MatPlotLibNet.Avalonia README
- MatPlotLibNet.Uno README