Skip to content
Shiny.NET
Shiny MAUI Shell v7 - App Links, App Shortcuts, & Navigation Interception!Shortcut me to it

Diagram

DiagramView draws a graph of shapes and connections: an org chart, a decision tree, a flowchart, a mindmap. It lays the graph out for you, routes the lines so they meet the real outline of each shape, and gives you a surface to pan, zoom, select on, drag nodes around and author connections in.

  • NuGet downloads for Shiny.Maui.Controls.Diagram
  • NuGet downloads for Shiny.Blazor.Controls.Diagram
  • NuGet downloads for Shiny.Controls.Diagram.Shared
Frameworks
.NET MAUI
Blazor

MAUI

Decision tree Flowchart with a retry loop
A decision tree with diamond branches on .NET MAUI A build-pipeline flowchart looping back to an earlier step on .NET MAUI

Blazor

Mind map Org chart
A mind map fanned out to both sides on Blazor An org chart laid out as a tidy tree on Blazor

Mermaid Diagrams renders a picture from mermaid text. This renders a graph you bind to and edit. If your source of truth is markup, use Mermaid; if it is a collection of nodes — rows from a database, a view model — use this one.

A diagram is mostly arithmetic. Packing a tidy tree so a parent sits centred over its children and no two subtrees collide, assigning layers to a graph that loops back on itself, working out where a line leaving a diamond actually crosses its edge — none of it wants to know whether it is drawing into a MAUI canvas or an SVG viewBox.

So it doesn’t. Shiny.Controls.Diagram.Shared owns the model, all five layouts, the connector routing and the shape geometry — down to the exact box every node occupies and the exact polyline every connection follows. Both controls are thin: they turn gestures into a DiagramEditPlan and paint what the engine tells them to.

A layout that resolved differently on the two hosts would be a bug neither host’s own tests would notice, and this removes the possibility rather than testing for it. The package is dependency-free, trimmable and AOT-clean, and is perfectly usable with no UI at all.

Terminal window
dotnet add package Shiny.Maui.Controls.Diagram
# or
dotnet add package Shiny.Blazor.Controls.Diagram
using Shiny.Controls.Diagramming;
var nodes = new ObservableCollection<DiagramNode>
{
new("start", "Ticket raised") { Shape = DiagramNodeShape.Stadium },
new("paid", "Paid plan?") { Shape = DiagramNodeShape.Diamond },
new("queue", "Support queue"),
new("forum", "Community forum")
};
var connections = new ObservableCollection<DiagramConnection>
{
new("start", "paid"),
new("paid", "queue", "Yes"),
new("paid", "forum", "No")
};
MAUI
<shiny:DiagramView Nodes="{Binding Nodes}"
Connections="{Binding Connections}"
LayoutKind="Layered"
AllowNodeDrag="True"
AllowConnectionEdit="True" />
Blazor
<div style="height:520px">
<DiagramView Nodes="nodes"
Connections="connections"
LayoutKind="DiagramLayoutKind.Layered"
AllowNodeDrag="true"
AllowConnectionEdit="true" />
</div>

The Blazor component fills its parent and needs a bounded height, the same as GanttView.

Nodes are DiagramNode, connections are DiagramConnection — concrete types rather than a generic TItem, because the engine writes layout results back onto them. Put your own object in DiagramNode.Item and bind a template to it.

Both collections are watched. An ObservableCollection redraws on add and remove, and every node and connection is watched for property changes, so editing the source redraws without calling anything.

Nest nodes in Children and the parent link maintains itself; or hand over a flat list where each node carries a ParentId and the model reassembles the tree. Mixing the two is fine. Charts arrive from a database as rows and from a view model as a tree, and forcing a conversion on the consumer is how a control ends up with two half-supported paths.

A hierarchy with no connections of its own still draws its links. An org chart handed over as nested nodes has parents and children and no edges; without this it would draw as a tidy grid of boxes joined by nothing, which looks like the control failed rather than like a deliberate absence. Declaring a connection between the same two nodes replaces the implicit one — that is how you add a label or a different arrowhead.

DiagramNodeShape is the flowchart vocabulary rather than a general shape library: Rectangle, RoundedRectangle, Stadium, Circle, Ellipse, Diamond, Parallelogram, Hexagon, Cylinder, Document, Triangle. Each one means something to a reader of a flowchart.

Anchoring and hit testing both work against the real outline, not the bounding box. That single detail is most of what makes a decision tree look drawn rather than approximated: with a box anchor, every arrow into a diamond stops short in mid-air at the corner of an invisible rectangle, and a click in that same empty corner selects the node.

Real data routinely contains a dangling connection, a duplicate id or a parent chain that loops. A control that threw on one would be unusable against it, and one that silently dropped it would be worse — so the diagram still draws and DiagramModel.Issues says what was wrong. Read it from the Built event (MAUI) or OnBuilt (Blazor), because nothing else will tell you.

DiagramJson.Save and DiagramJson.Load round-trip the graph, the shapes and hand-placed positions through a flat pair of lists. Routes, depths and selection are not saved — they are derived on the next rebuild, so saving them would only create the possibility of a file disagreeing with itself. Serialization is source-generated, so it survives a trimmed WebAssembly publish.

Load into LayoutKind = None to keep the saved positions.

  • Layouts — the five arrangements and when each is the right one
  • Editing & Undo — plans, cancellation and the undo stack