Class DirectCanvasRenderer
A high-performance graph renderer that draws directly to a DrawingContext, bypassing the Avalonia visual tree for nodes and edges. This is optimized for large graphs (500+ nodes) where per-element Controls are too slow.
Uses GraphRenderModel for all geometry calculations to ensure 100% visual parity with the normal VisualTree renderer.
public class DirectCanvasRenderer : Control, INotifyPropertyChanged, IDataContextProvider, ILogical, IThemeVariantHost, IResourceHost, IResourceNode, IStyleHost, ISetLogicalParent, ISetInheritanceParent, ISupportInitialize, IStyleable, INamed, IInputElement, IDataTemplateHost, ISetterValue, IRenderLayer
- Inheritance
-
AvaloniaObjectAnimatableStyledElementVisualLayoutableInteractiveInputElementControlDirectCanvasRenderer
- Implements
-
IDataContextProviderILogicalIThemeVariantHostIResourceHostIResourceNodeIStyleHostISetLogicalParentISetInheritanceParentIStyleableINamedIInputElementIDataTemplateHostISetterValue
- Inherited Members
-
Control.FocusAdornerPropertyControl.TagPropertyControl.ContextMenuPropertyControl.ContextFlyoutPropertyControl.RequestBringIntoViewEventControl.ContextRequestedEventControl.LoadedEventControl.UnloadedEventControl.SizeChangedEventControl.GetTemplateFocusTarget()Control.OnLoaded(RoutedEventArgs)Control.OnUnloaded(RoutedEventArgs)Control.OnSizeChanged(SizeChangedEventArgs)Control.OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs)Control.OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs)Control.OnGotFocus(GotFocusEventArgs)Control.OnLostFocus(RoutedEventArgs)Control.OnCreateAutomationPeer()Control.OnPointerReleased(PointerReleasedEventArgs)Control.OnKeyUp(KeyEventArgs)Control.OnPropertyChanged(AvaloniaPropertyChangedEventArgs)Control.FocusAdornerControl.DataTemplatesControl.ContextMenuControl.ContextFlyoutControl.IsLoadedControl.TagControl.ContextRequestedControl.LoadedControl.UnloadedControl.SizeChangedInputElement.FocusablePropertyInputElement.IsEnabledPropertyInputElement.IsEffectivelyEnabledPropertyInputElement.CursorPropertyInputElement.IsKeyboardFocusWithinPropertyInputElement.IsFocusedPropertyInputElement.IsHitTestVisiblePropertyInputElement.IsPointerOverPropertyInputElement.IsTabStopPropertyInputElement.GotFocusEventInputElement.LostFocusEventInputElement.KeyDownEventInputElement.KeyUpEventInputElement.TabIndexPropertyInputElement.TextInputEventInputElement.TextInputMethodClientRequestedEventInputElement.PointerEnteredEventInputElement.PointerExitedEventInputElement.PointerMovedEventInputElement.PointerPressedEventInputElement.PointerReleasedEventInputElement.PointerCaptureLostEventInputElement.PointerWheelChangedEventInputElement.TappedEventInputElement.HoldingEventInputElement.DoubleTappedEventInputElement.Focus(NavigationMethod, KeyModifiers)InputElement.OnAccessKey(RoutedEventArgs)InputElement.OnKeyDown(KeyEventArgs)InputElement.OnTextInput(TextInputEventArgs)InputElement.OnPointerEntered(PointerEventArgs)InputElement.OnPointerExited(PointerEventArgs)InputElement.OnPointerMoved(PointerEventArgs)InputElement.OnPointerPressed(PointerPressedEventArgs)InputElement.OnPointerCaptureLost(PointerCaptureLostEventArgs)InputElement.OnPointerWheelChanged(PointerWheelEventArgs)InputElement.UpdateIsEffectivelyEnabled()InputElement.FocusableInputElement.IsEnabledInputElement.CursorInputElement.IsKeyboardFocusWithinInputElement.IsFocusedInputElement.IsHitTestVisibleInputElement.IsPointerOverInputElement.IsTabStopInputElement.IsEffectivelyEnabledInputElement.TabIndexInputElement.KeyBindingsInputElement.IsEnabledCoreInputElement.GestureRecognizersInputElement.GotFocusInputElement.LostFocusInputElement.KeyDownInputElement.KeyUpInputElement.TextInputInputElement.TextInputMethodClientRequestedInputElement.PointerEnteredInputElement.PointerExitedInputElement.PointerMovedInputElement.PointerPressedInputElement.PointerReleasedInputElement.PointerCaptureLostInputElement.PointerWheelChangedInputElement.TappedInputElement.HoldingInputElement.DoubleTappedInteractive.RaiseEvent(RoutedEventArgs)Interactive.BuildEventRoute(RoutedEvent)Layoutable.DesiredSizePropertyLayoutable.WidthPropertyLayoutable.HeightPropertyLayoutable.MinWidthPropertyLayoutable.MaxWidthPropertyLayoutable.MinHeightPropertyLayoutable.MaxHeightPropertyLayoutable.MarginPropertyLayoutable.HorizontalAlignmentPropertyLayoutable.VerticalAlignmentPropertyLayoutable.UseLayoutRoundingPropertyLayoutable.UpdateLayout()Layoutable.ApplyTemplate()Layoutable.Measure(Size)Layoutable.Arrange(Rect)Layoutable.InvalidateMeasure()Layoutable.InvalidateArrange()Layoutable.AffectsMeasure<T>(params AvaloniaProperty[])Layoutable.AffectsArrange<T>(params AvaloniaProperty[])Layoutable.MeasureCore(Size)Layoutable.MeasureOverride(Size)Layoutable.ArrangeCore(Rect)Layoutable.ArrangeOverride(Size)Layoutable.OnMeasureInvalidated()Layoutable.OnVisualParentChanged(Visual, Visual)Layoutable.WidthLayoutable.HeightLayoutable.MinWidthLayoutable.MaxWidthLayoutable.MinHeightLayoutable.MaxHeightLayoutable.MarginLayoutable.HorizontalAlignmentLayoutable.VerticalAlignmentLayoutable.DesiredSizeLayoutable.IsMeasureValidLayoutable.IsArrangeValidLayoutable.UseLayoutRoundingLayoutable.EffectiveViewportChangedLayoutable.LayoutUpdatedVisual.BoundsPropertyVisual.ClipToBoundsPropertyVisual.ClipPropertyVisual.IsVisiblePropertyVisual.OpacityPropertyVisual.OpacityMaskPropertyVisual.EffectPropertyVisual.HasMirrorTransformPropertyVisual.RenderTransformPropertyVisual.RenderTransformOriginPropertyVisual.FlowDirectionPropertyVisual.VisualParentPropertyVisual.ZIndexPropertyVisual.GetFlowDirection(Visual)Visual.SetFlowDirection(Visual, FlowDirection)Visual.InvalidateVisual()Visual.AffectsRender<T>(params AvaloniaProperty[])Visual.OnAttachedToVisualTree(VisualTreeAttachmentEventArgs)Visual.OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs)Visual.InvalidateMirrorTransform()Visual.BoundsVisual.ClipToBoundsVisual.ClipVisual.IsEffectivelyVisibleVisual.IsVisibleVisual.OpacityVisual.OpacityMaskVisual.EffectVisual.HasMirrorTransformVisual.RenderTransformVisual.RenderTransformOriginVisual.FlowDirectionVisual.ZIndexVisual.VisualChildrenVisual.VisualRootVisual.BypassFlowDirectionPoliciesVisual.AttachedToVisualTreeVisual.DetachedFromVisualTreeStyledElement.DataContextPropertyStyledElement.NamePropertyStyledElement.ParentPropertyStyledElement.TemplatedParentPropertyStyledElement.ThemePropertyStyledElement.BeginInit()StyledElement.EndInit()StyledElement.ApplyStyling()StyledElement.InitializeIfNeeded()StyledElement.OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs)StyledElement.OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs)StyledElement.OnDataContextBeginUpdate()StyledElement.OnDataContextEndUpdate()StyledElement.OnInitialized()StyledElement.NameStyledElement.ClassesStyledElement.DataContextStyledElement.IsInitializedStyledElement.StylesStyledElement.StyleKeyStyledElement.ResourcesStyledElement.TemplatedParentStyledElement.ThemeStyledElement.LogicalChildrenStyledElement.PseudoClassesStyledElement.StyleKeyOverrideStyledElement.ParentStyledElement.ActualThemeVariantStyledElement.AttachedToLogicalTreeStyledElement.DetachedFromLogicalTreeStyledElement.DataContextChangedStyledElement.InitializedStyledElement.ResourcesChangedStyledElement.ActualThemeVariantChangedAnimatable.TransitionsPropertyAnimatable.OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs)Animatable.TransitionsAvaloniaObject.CheckAccess()AvaloniaObject.VerifyAccess()AvaloniaObject.ClearValue(AvaloniaProperty)AvaloniaObject.ClearValue<T>(AvaloniaProperty<T>)AvaloniaObject.ClearValue<T>(StyledProperty<T>)AvaloniaObject.ClearValue<T>(DirectPropertyBase<T>)AvaloniaObject.GetHashCode()AvaloniaObject.GetValue(AvaloniaProperty)AvaloniaObject.GetValue<T>(StyledProperty<T>)AvaloniaObject.GetValue<T>(DirectPropertyBase<T>)AvaloniaObject.GetBaseValue<T>(StyledProperty<T>)AvaloniaObject.IsAnimating(AvaloniaProperty)AvaloniaObject.IsSet(AvaloniaProperty)AvaloniaObject.SetValue<T>(StyledProperty<T>, T, BindingPriority)AvaloniaObject.SetValue<T>(DirectPropertyBase<T>, T)AvaloniaObject.SetCurrentValue<T>(StyledProperty<T>, T)AvaloniaObject.Bind(AvaloniaProperty, IBinding)AvaloniaObject.CoerceValue(AvaloniaProperty)AvaloniaObject.RaisePropertyChanged<T>(DirectPropertyBase<T>, T, T)AvaloniaObject.SetAndRaise<T>(DirectPropertyBase<T>, ref T, T)AvaloniaObject.InheritanceParentAvaloniaObject.this[AvaloniaProperty]AvaloniaObject.this[IndexerDescriptor]AvaloniaObject.PropertyChanged
Remarks
DirectCanvasRenderer supports custom node rendering via the IDirectNodeRenderer interface. When a node's renderer implements IDirectNodeRenderer, it delegates drawing to that implementation.
Edge endpoint handles are rendered when ShowEdgeEndpointHandles is true and an edge is selected, allowing reconnection/disconnection.
Transform Mode: This renderer uses SelfTransformed mode. It performs its own coordinate transforms internally and MUST be placed in an untransformed container (e.g., RootPanel, not MainCanvas) to avoid double-transformation.
Constructors
DirectCanvasRenderer(FlowCanvasSettings)
Creates a new DirectCanvasRenderer with the specified settings.
public DirectCanvasRenderer(FlowCanvasSettings settings)
Parameters
settingsFlowCanvasSettingsCanvas settings for rendering configuration.
DirectCanvasRenderer(FlowCanvasSettings, NodeRendererRegistry?)
Creates a new DirectCanvasRenderer with the specified settings and node renderer registry.
public DirectCanvasRenderer(FlowCanvasSettings settings, NodeRendererRegistry? nodeRenderers)
Parameters
settingsFlowCanvasSettingsCanvas settings for rendering configuration.
nodeRenderersNodeRendererRegistryOptional registry of custom node renderers.
Properties
DisplayName
Display name for debugging and UI purposes.
public string DisplayName { get; }
Property Value
EditingEdgeId
Gets the ID of the edge currently being edited, or null if no edge is being edited.
public string? EditingEdgeId { get; }
Property Value
EditingNodeId
Gets the ID of the node currently being edited, or null if no node is being edited.
public string? EditingNodeId { get; }
Property Value
IsEditing
Gets whether inline editing is currently active.
public bool IsEditing { get; }
Property Value
LayerId
Unique identifier for this layer.
public string LayerId { get; }
Property Value
Model
Gets the render model used for geometry calculations. This model is shared with other renderers to ensure visual parity.
public CanvasRenderModel Model { get; }
Property Value
NodeRenderers
Gets or sets the node renderer registry for custom node types.
public NodeRendererRegistry? NodeRenderers { get; set; }
Property Value
TransformMode
How this layer participates in viewport transformations.
public LayerTransformMode TransformMode { get; }
Property Value
Remarks
DirectCanvasRenderer uses SelfTransformed because it performs its own coordinate transforms internally via ScreenToCanvas(double, double). It MUST be placed outside any transformed container (e.g., as a child of RootPanel, not MainCanvas) to avoid double-transformation bugs.
Methods
BeginEditEdge(string)
Begins editing an edge's label. The label will not be drawn while editing.
public void BeginEditEdge(string edgeId)
Parameters
edgeIdstringID of the edge to edit.
BeginEditNode(string)
Begins editing a node's label. The label will not be drawn while editing.
public void BeginEditNode(string nodeId)
Parameters
nodeIdstringID of the node to edit.
ClearHoveredEndpointHandle()
Clears the hovered endpoint handle state.
public void ClearHoveredEndpointHandle()
ClearHoveredPort()
Clears the hovered port state.
public void ClearHoveredPort()
EndEditEdge()
Ends editing an edge's label.
public void EndEditEdge()
EndEditNode()
Ends editing a node's label.
public void EndEditNode()
GetEdgeEndpointViewportPosition(Edge, bool)
Gets the viewport position for an edge endpoint (source or target).
public Point GetEdgeEndpointViewportPosition(Edge edge, bool isSource)
Parameters
Returns
- Point
GetPortScreenPosition(Node, Port, bool)
Gets the screen position for a port.
[Obsolete("Use GetPortViewportPosition or GetPortCanvasPosition instead.")]
public Point GetPortScreenPosition(Node node, Port port, bool isOutput)
Parameters
Returns
- Point
GetPortViewportPosition(Node, Port, bool)
Gets the viewport position for a port.
public Point GetPortViewportPosition(Node node, Port port, bool isOutput)
Parameters
Returns
- Point
Remarks
WARNING: For distance calculations with pointer events, prefer using canvas coordinates
(via _model.GetPortPosition) since pointer events give canvas coords via e.GetPosition(MainCanvas).
HitTestEdge(double, double)
Performs hit testing to find an edge at the given screen coordinates.
public Edge? HitTestEdge(double screenX, double screenY)
Parameters
screenXdoubleX coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
screenYdoubleY coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
Returns
- Edge
The hit edge, or null if no edge was hit.
HitTestEdgeEndpointHandle(double, double)
Performs hit testing to find an edge endpoint handle at the given screen coordinates.
public (Edge edge, bool isSource)? HitTestEdgeEndpointHandle(double screenX, double screenY)
Parameters
screenXdoubleX coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
screenYdoubleY coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
Returns
HitTestNode(double, double)
Performs hit testing to find a node at the given screen coordinates. Uses quadtree for O(log N) spatial query instead of linear scan.
public Node? HitTestNode(double screenX, double screenY)
Parameters
screenXdoubleX coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
screenYdoubleY coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
Returns
- Node
The hit node, or null if no node was hit.
HitTestPort(double, double)
Performs hit testing to find a port at the given screen coordinates. Uses quadtree range query for O(log N) spatial lookup.
public (Node node, Port port, bool isOutput)? HitTestPort(double screenX, double screenY)
Parameters
screenXdoubleX coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
screenYdoubleY coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
Returns
HitTestResizeHandle(double, double)
Performs hit testing to find a resize handle at the given screen coordinates.
public (Node node, ResizeHandlePosition position)? HitTestResizeHandle(double screenX, double screenY)
Parameters
screenXdoubleX coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
screenYdoubleY coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
Returns
- (Node node, ResizeHandlePosition position)?
Tuple of (node, position) or null if no handle hit.
HitTestShape(double, double)
Hit tests for shapes at the given screen coordinates. Shapes with higher ZIndex are tested first (topmost shapes have priority).
public ShapeElement? HitTestShape(double screenX, double screenY)
Parameters
Returns
- ShapeElement
The hit shape, or null if no shape was hit.
HitTestShapeResizeHandle(double, double)
Performs hit testing to find a shape resize handle at the given screen coordinates. Shape resize handles are shown when a shape is selected and resizable.
public (ShapeElement shape, ResizeHandlePosition position)? HitTestShapeResizeHandle(double screenX, double screenY)
Parameters
screenXdoubleX coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
screenYdoubleY coordinate relative to the root panel (not canvas). Will be converted internally via ScreenToCanvas.
Returns
- (ShapeElement shape, ResizeHandlePosition position)?
Tuple of (shape, position) or null if no handle hit.
InvalidateIndex()
Marks the spatial index as dirty, forcing rebuild on next hit test. Call this when nodes are added, removed, or moved.
public void InvalidateIndex()
InvalidatePositions()
Marks the spatial index as dirty due to node position changes. Unlike InvalidateIndex(), this preserves the node lookup dictionary since the nodes themselves haven't changed, only their positions. Call this during dragging operations for better performance.
public void InvalidatePositions()
Render(DrawingContext)
Renders the visual to a Avalonia.Media.DrawingContext.
public override void Render(DrawingContext context)
Parameters
contextDrawingContextThe drawing context.
SetHoveredEndpointHandle(string?, bool)
Sets the currently hovered edge endpoint handle for visual feedback.
public void SetHoveredEndpointHandle(string? edgeId, bool isSource)
Parameters
edgeIdstringID of the edge, or null to clear.
isSourceboolTrue if hovering the source handle, false for target.
SetHoveredPort(string?, string?)
Sets the currently hovered port for visual feedback.
public void SetHoveredPort(string? nodeId, string? portId)
Parameters
nodeIdstringID of the node containing the port, or null to clear.
portIdstringID of the port, or null to clear.
Update(Graph?, ViewportState, ThemeResources)
Updates the renderer with the current graph state and triggers a redraw.
public void Update(Graph? graph, ViewportState viewport, ThemeResources theme)
Parameters
graphGraphThe graph to render.
viewportViewportStateCurrent viewport state for zoom/pan.
themeThemeResourcesTheme resources for styling.
UpdateSettings(FlowCanvasSettings)
Updates the settings and propagates to the render model.
public void UpdateSettings(FlowCanvasSettings settings)
Parameters
settingsFlowCanvasSettingsThe new settings.