TL;DR: The Syncfusion Blazor Diagram component is a powerful tool for creating interactive diagrams. Learn how to easily create a multi-parent hierarchical tree diagram, customize node spacing, adjust layouts, and utilize features like zoom, pan, expand/collapse, and export for a comprehensive visual experience.
In the digital age, effectively visualizing complex relationships is crucial for any domain, from organizational structures to data science projects. Hierarchical trees with multiple parents, also known as directed acyclic graphs (DAGs), offer a powerful way to represent these intricate connections.
In this blog, we’ll see how to create a hierarchical tree with multiple parents using the Syncfusion Blazor Diagram component.
Refer to the following image. Here, the rectangular boxes represent the nodes, and the curved lines represent the connectors.
Whether you’re a developer looking to streamline your workflow or a business analyst aiming to present data more intuitively, this guide will walk you through the step-by-step process to harness the versatile Blazor Diagram component’s full potential.
Let’s get started!
The Syncfusion Blazor Diagram component is designed to straightforwardly create hierarchical diagrams with multiple parents. It supports external data sources and can easily convert and map data into a visual diagram by setting a few properties.
Follow these steps to get started with the Blazor Diagram component:
@using Syncfusion.Blazor.Diagram <SfDiagramComponent @ref="@Diagram" Created="OnCreated" Height="690px" NodeCreating="@OnNodeCreating" ConnectorCreating="@OnConnectorCreating" InteractionController="@DiagramInteractions.ZoomPan"> <DataSourceSettings ID="Id" ParentID="ReportingPerson" DataSource="DataSource"></DataSourceSettings> <Layout @bind-Type="@Type" @bind-VerticalSpacing="@VerticalSpacing" @bind-SamePoint="@samepoint" @bind-HorizontalSpacing="@HorizontalSpacing" @bind-Orientation="@OrientationType" HorizontalAlignment="@HorizontalAlignment" VerticalAlignment="@VerticalAlignment"> <LayoutMargin Top="50" Bottom="0" Right="0" Left="10"></LayoutMargin> </Layout> <SnapSettings Constraints="SnapConstraints.None"></SnapSettings> </SfDiagramComponent> @code { private Orientation subTreeOrientation = Orientation.Vertical; private bool samepoint = false; private SubTreeAlignmentType subTreeAlignment = SubTreeAlignmentType.Right; public SfDiagramComponent Diagram; public LayoutOrientation OrientationType = LayoutOrientation.TopToBottom; public LayoutType Type = LayoutType.ComplexHierarchicalTree; public HorizontalAlignment HorizontalAlignment = HorizontalAlignment.Left; public VerticalAlignment VerticalAlignment = VerticalAlignment.Top; public int HorizontalSpacing = 40; public int VerticalSpacing = 40; // Defines default values for the Node object. private void OnNodeCreating(IDiagramObject obj) { Node node = obj as Node; node.Width = 40; node.Height = 40; //Initialize shape. HierarchicalDetails hierarchicalDetails = node.Data as HierarchicalDetails; node.Shape = new BasicShape() { Type =Syncfusion.Blazor.Diagram.NodeShapes.Basic, Shape = NodeBasicShapes.Rectangle, CornerRadius = 7 }; node.Style = new ShapeStyle() { Fill = hierarchicalDetails.FilllColor, StrokeWidth = 1, StrokeColor = hierarchicalDetails.Border }; } // Defines default values for the Connector object. private void OnConnectorCreating(IDiagramObject connector1) { Connector connector = (connector1 as Connector); connector.Type = ConnectorSegmentType.Orthogonal; connector.CornerRadius = 7; connector.Style = new ShapeStyle() { StrokeColor= "#6d6d6d", }; connector.TargetDecorator.Height = 7; connector.TargetDecorator.Width = 7; } private void OnCreated() { FitOptions options = new FitOptions() { Mode = FitMode.Both, Region = DiagramRegion.Content }; Diagram.FitToPage(options); } public class HierarchicalDetails { public string Id { get; set; } public string FilllColor { get; set; } public string Border { get; set; } public string Label { get; set; } public List<string> ReportingPerson { get; set; } } //DataSource items. public List<HierarchicalDetails> DataSource = new List<HierarchicalDetails>() { new HierarchicalDetails() { Id= "node11", Label = "StackPanel",FilllColor="#e7704c", Border="#c15433" }, new HierarchicalDetails() { Id= "node12", Label= "StackPanel1", ReportingPerson = new List<string>(){ "node114" },FilllColor="#efd46e", Border="#d6b123" }, new HierarchicalDetails() { Id= "node13", Label= "StackPanel2",ReportingPerson = new List<string>(){ "node12" },FilllColor="#58b087", Border="#16955e" }, new HierarchicalDetails() { Id= "node14", Label= "StackPanel3",ReportingPerson = new List<string>(){ "node12"},FilllColor="#58b087", Border="#16955e" }, new HierarchicalDetails() { Id= "node15", Label= "StackPanel4", ReportingPerson = new List<string>(){ "node12" },FilllColor="#58b087", Border="#16955e" }, new HierarchicalDetails() { Id= "node116", Label= "StackPanel16",ReportingPerson = new List<string>(){ "node22", "node12"},FilllColor="#58b087", Border="#16955e" }, new HierarchicalDetails() { Id= "node17", Label= "StackPanel6",ReportingPerson = new List<string>(){ "node13", "node14", "node15"},FilllColor="#659be5", Border="#3a6eb5"}, new HierarchicalDetails() { Id= "node19", Label= "StackPanel8",ReportingPerson = new List<string>(){"node16", "node17", "node18" },FilllColor="#8dbe6c", Border="#489911"}, new HierarchicalDetails() { Id= "node110", Label= "StackPanel9", ReportingPerson = new List<string>(){ "node16", "node17", "node18" },FilllColor="#8dbe6c", Border="#489911" }, new HierarchicalDetails() { Id= "node111", Label= "StackPanel10",ReportingPerson = new List<string>(){"node16", "node17", "node18","node116" },FilllColor="#8dbe6c", Border="#489911"}, new HierarchicalDetails() { Id= "node21", Label= "StackPanel11" ,FilllColor="#e7704c", Border="#c15433"}, new HierarchicalDetails() { Id= "node22", Label= "StackPanel12",ReportingPerson = new List<string>(){"node114" },FilllColor="#efd46e", Border="#d6b123"}, new HierarchicalDetails() { Id= "node23", Label= "StackPanel13", ReportingPerson = new List<string>(){ "node22" } ,FilllColor="#58b087", Border="#16955e"}, new HierarchicalDetails() { Id= "node24", Label= "StackPanel14",ReportingPerson = new List<string>(){ "node22" },FilllColor="#58b087", Border="#16955e" }, new HierarchicalDetails() { Id= "node25", Label= "StackPanel15",ReportingPerson = new List<string>(){"node22" },FilllColor="#58b087", Border="#16955e"}, new HierarchicalDetails() { Id= "node27", Label= "StackPanel17",ReportingPerson = new List<string>(){"node23", "node24", "node25" },FilllColor="#659be5", Border="#3a6eb5"}, new HierarchicalDetails() { Id= "node29", Label= "StackPanel19",ReportingPerson = new List<string>(){ "node26", "node27", "node28", "node116" } ,FilllColor="#8dbe6c", Border="#489911" }, new HierarchicalDetails() { Id= "node210", Label= "StackPanel20",ReportingPerson = new List<string>(){"node26", "node27", "node28"} ,FilllColor="#8dbe6c", Border="#489911"}, new HierarchicalDetails() { Id= "node211", Label= "StackPanel21",ReportingPerson = new List<string>(){ "node26", "node27", "node28" } ,FilllColor="#8dbe6c", Border="#489911" }, new HierarchicalDetails() { Id= "node31", Label= "StackPanel22" ,FilllColor="#e7704c", Border="#c15433"}, new HierarchicalDetails() { Id= "node114", Label= "StackPanel23", ReportingPerson = new List<string>(){ "node11", "node21", "node31" } ,FilllColor="#f3904a", Border="#d3722e" }, }; }
You can also construct a multi-parent hierarchical tree using a collection of nodes and connectors. This method gives you more control over your diagram’s individual elements.
Refer to the following code example.
@using Syncfusion.Blazor.Diagram <SfDiagramComponent Height="600px" Nodes="@nodes" Connectors="@connectors" NodeCreating="@OnNodeCreating" ConnectorCreating="@OnConnectorCreating"> <Layout Type="LayoutType.ComplexHierarchicalTree" @bind-HorizontalSpacing="@HorizontalSpacing" @bind-VerticalSpacing="@VerticalSpacing"> </Layout> <SnapSettings Constraints="SnapConstraints.None"></SnapSettings> </SfDiagramComponent> @code { //Initialize the diagram's nodes collection. DiagramObjectCollection<Node> nodes = new DiagramObjectCollection<Node>(); //Initialize the diagram's connectors collection. DiagramObjectCollection<Connector> connectors = new DiagramObjectCollection<Connector>(); int HorizontalSpacing = 40; int VerticalSpacing = 40; //Method to set node's default properties. private void OnNodeCreating(IDiagramObject obj) { Node node = obj as Node; node.Height = 40; node.Width = 50; //Initializing the default node's shape style. node.Style = new ShapeStyle() { Fill = "#6CA0DC", StrokeColor = "#6CA0DC" }; node.Annotations = new DiagramObjectCollection<ShapeAnnotation>() { new ShapeAnnotation { Content = node.Annotations[0].Content, Style = new TextStyle() { Color = "white", Bold = true }, } }; } //Method to set connector's default properties. private void OnConnectorCreating(IDiagramObject connector) { (connector as Connector).Type = ConnectorSegmentType.Orthogonal; (connector as Connector).CornerRadius = 5; (connector as Connector).TargetDecorator = new DecoratorSettings() { Style = new ShapeStyle() { Fill = "#6CA0DC", StrokeColor = "#6CA0DC" } }; (connector as Connector).Style = new ShapeStyle() { StrokeColor = "#6CA0DC" }; } protected override void OnInitialized() { //Initializing node and connectors. nodes = new DiagramObjectCollection<Node>() { new Node() { ID="node1", Annotations = new DiagramObjectCollection<ShapeAnnotation>() { new ShapeAnnotation{Content="node1"} } }, new Node() { ID="node2", Annotations = new DiagramObjectCollection<ShapeAnnotation>() { new ShapeAnnotation{Content="node2"} } }, new Node() { ID="node3", Annotations = new DiagramObjectCollection<ShapeAnnotation>() { new ShapeAnnotation{Content="node3"} } }, new Node() { ID="node4", Annotations = new DiagramObjectCollection<ShapeAnnotation>() { new ShapeAnnotation{Content="node4"} } }, new Node() { ID="node5", Annotations = new DiagramObjectCollection<ShapeAnnotation>() { new ShapeAnnotation{Content="node5"} } }, }; connectors = new DiagramObjectCollection<Connector>() { new Connector() { ID="connector1", SourceID="node1", TargetID="node4" }, new Connector() { ID="connector2", SourceID="node2", TargetID="node4" }, new Connector() { ID="connector3", SourceID="node3", TargetID="node4" }, new Connector() { ID="connector4", SourceID="node4", TargetID="node5" }, }; } }
Refer to the following image.
For even more flexibility, you can create a multi-parent layout using dynamic and expando objects. This approach lets you handle data that might change or expand over time without a pre-defined structure.
Refer to the following code example.
@using Syncfusion.Blazor.Diagram @using System.Text.Json; @using System.Dynamic; <SfDiagramComponent @ref="@diagram" Width="100%" Height="580px" NodeCreating="NodeCreating" ConnectorCreating="ConnectorCreating"> <DataSourceSettings DataSource="@ExpandoDataSource" ID="Id" ParentID="ReportingPerson"></DataSourceSettings> <Layout Type="LayoutType.ComplexHierarchicalTree" VerticalAlignment="VerticalAlignment.Auto" HorizontalAlignment="HorizontalAlignment.Auto" Orientation="LayoutOrientation.TopToBottom" HorizontalSpacing="40" VerticalSpacing="40"> </Layout> <SnapSettings Constraints="SnapConstraints.None"></SnapSettings> </SfDiagramComponent> @code { public SfDiagramComponent diagram; public List<ExpandoObject> ExpandoDataSource { get; set; } public List<ExpandoObject> ExpandoData = new List<ExpandoObject>(); public class HierarchicalDetails { public string Id { get; set; } public string FilllColor { get; set; } public string Border { get; set; } public string Label { get; set; } public List<string> ReportingPerson { get; set; } } protected override void OnInitialized() { this.ExpandoDataSource = GetData(); } public List<ExpandoObject> GetData() { ExpandoData.Clear(); dynamic Member1 = new ExpandoObject(); Member1.Id = "node11"; Member1.Label = "StackPanel"; Member1.FillColor = "#e7704c"; Member1.Border = "#c15433"; ExpandoData.Add(Member1); dynamic Member2 = new ExpandoObject(); Member2.Id = "node12"; Member2.Label = "StackPanel1"; Member2.ReportingPerson = new List<string>() { "node114" }; Member2.FillColor = "#efd46e"; Member2.Border = "#d6b123"; ExpandoData.Add(Member2); dynamic Member7 = new ExpandoObject(); Member7.Id = "node13"; Member7.Label = "StackPanel2"; Member7.ReportingPerson = new List<string>() { "node12" }; Member7.FillColor = "#58b087"; Member7.Border = "#16955e"; ExpandoData.Add(Member7); dynamic Member3 = new ExpandoObject(); Member3.Id = "node14"; Member3.Label = "StackPanel3"; Member3.ReportingPerson = new List<string>() { "node12" }; Member3.FillColor = "#58b087"; Member3.Border = "#16955e"; ExpandoData.Add(Member3); dynamic Member8 = new ExpandoObject(); Member8.Id = "node15"; Member8.Label = "StackPanel4"; Member8.ReportingPerson = new List<string>() { "node12" }; Member8.FillColor = "#58b087"; Member8.Border = "#16955e"; ExpandoData.Add(Member8); dynamic Member10 = new ExpandoObject(); Member10.Id = "node116"; Member10.Label = "StackPanel16"; Member10.ReportingPerson = new List<string>() { "node22", "node12" }; Member10.FillColor = "#58b087"; Member10.Border = "#16955e"; ExpandoData.Add(Member10); dynamic Member11 = new ExpandoObject(); Member11.Id = "node17"; Member11.Label = "StackPanel6"; Member11.ReportingPerson = new List<string>() { "node13", "node14", "node15" }; Member11.FillColor = "#659be5"; Member11.Border = "#3a6eb5"; ExpandoData.Add(Member11); dynamic Member13 = new ExpandoObject(); Member13.Id = "node19"; Member13.Label = "StackPanel8"; Member13.ReportingPerson = new List<string>() { "node16", "node17", "node18" }; Member13.FillColor = "#8dbe6c"; Member13.Border = "#489911"; ExpandoData.Add(Member13); dynamic Member5 = new ExpandoObject(); Member5.Id = "node110"; Member5.Label = "StackPanel9"; Member5.ReportingPerson = new List<string>() { "node16", "node17", "node18" }; Member5.FillColor = "#8dbe6c"; Member5.Border = "#489911"; ExpandoData.Add(Member5); dynamic Member14 = new ExpandoObject(); Member14.Id = "node111"; Member14.Label = "StackPanel10"; Member14.ReportingPerson = new List<string>() { "node16", "node17", "node18", "node116" }; Member14.FillColor = "#8dbe6c"; Member14.Border = "#489911"; ExpandoData.Add(Member14); dynamic Member15 = new ExpandoObject(); Member15.Id = "node21"; Member15.Label = "StackPanel11"; Member15.FillColor = "#e7704c"; Member15.Border = "#c15433"; ExpandoData.Add(Member15); dynamic Member6 = new ExpandoObject(); Member6.Id = "node22"; Member6.Label = "StackPanel12"; Member6.ReportingPerson = new List<string>() { "node114" }; Member6.FillColor = "#efd46e"; Member6.Border = "#d6b123"; ExpandoData.Add(Member6); dynamic Member16 = new ExpandoObject(); Member16.Id = "node23"; Member16.Label = "StackPanel13"; Member16.ReportingPerson = new List<string>() { "node22" }; Member16.FillColor = "#58b087"; Member16.Border = "#16955e"; ExpandoData.Add(Member16); dynamic Member17 = new ExpandoObject(); Member17.Id = "node24"; Member17.Label = "StackPanel14"; Member17.ReportingPerson = new List<string>() { "node22" }; Member17.FillColor = "#58b087"; Member17.Border = "#16955e"; ExpandoData.Add(Member17); dynamic Member18 = new ExpandoObject(); Member18.Id = "node25"; Member18.Label = "StackPanel15"; Member18.ReportingPerson = new List<string>() { "node22" }; Member18.FillColor = "#58b087"; Member18.Border = "#16955e"; ExpandoData.Add(Member18); dynamic Member20 = new ExpandoObject(); Member20.Id = "node27"; Member20.Label = "StackPanel17"; Member20.ReportingPerson = new List<string>() { "node23", "node24", "node25" }; Member20.FillColor = "#659be5"; Member20.Border = "#3a6eb5"; ExpandoData.Add(Member20); dynamic Member22 = new ExpandoObject(); Member22.Id = "node29"; Member22.Label = "StackPanel19"; Member22.ReportingPerson = new List<string>() { "node26", "node27", "node28", "node116" }; Member22.FillColor = "#8dbe6c"; Member22.Border = "#489911"; ExpandoData.Add(Member22); dynamic Member23 = new ExpandoObject(); Member23.Id = "node210"; Member23.Label = "StackPanel20"; Member23.ReportingPerson = new List<string>() { "node26", "node27", "node28" }; Member23.FillColor = "#8dbe6c"; Member23.Border = "#489911"; ExpandoData.Add(Member23); dynamic Member24 = new ExpandoObject(); Member24.Id = "node211"; Member24.Label = "StackPanel21"; Member24.ReportingPerson = new List<string>() { "node26", "node27", "node28" }; Member24.FillColor = "#8dbe6c"; Member24.Border = "#489911"; ExpandoData.Add(Member24); dynamic Member25 = new ExpandoObject(); Member25.Id = "node31"; Member25.Label = "StackPanel22"; Member25.FillColor = "#e7704c"; Member25.Border = "#c15433"; ExpandoData.Add(Member25); dynamic Member26 = new ExpandoObject(); Member26.Id = "node114"; Member26.Label = "StackPanel23"; Member26.ReportingPerson = new List<string>() { "node11", "node21", "node31" }; Member26.FillColor = "#f3904a"; Member26.Border = "#d3722e"; ExpandoData.Add(Member26); return ExpandoData; } private void NodeCreating(IDiagramObject obj) { Node node = obj as Node; node.Width = 40; node.Height = 40; node.Shape = new BasicShape() { Type = NodeShapes.Basic, Shape = NodeBasicShapes.Rectangle, CornerRadius = 7 }; dynamic hierarchicalData = node.Data as ExpandoObject; if (hierarchicalData == null) hierarchicalData = JsonSerializer.Deserialize<ExpandoObject>(node.Data.ToString()); node.Style.Fill = hierarchicalData.Border; node.Style.StrokeWidth = 1; } private void ConnectorCreating(IDiagramObject connector) { (connector as Connector).Type = ConnectorSegmentType.Orthogonal; (connector as Connector).CornerRadius = 7; (connector as Connector).TargetDecorator.Height = 7; (connector as Connector).TargetDecorator.Width = 7; (connector as Connector).TargetDecorator.Style.StrokeColor = "#6d6d6d"; } }
Refer to the following image.
In the automatic layout, line distribution is crucial for arranging connectors without overlap. Connectors originating from the same parent node can sometimes overlap, making the diagram unclear.
The SamePoint property in the layout settings allows you to control whether the connectors from a single parent node should start from the same or different points. By default, the SamePoint property is set to true, ensuring a clean and organized diagram.
The following code example illustrates creating a complex hierarchical tree with line distribution.
<Layout Type="LayoutType.ComplexHierarchicalTree" SamePoint="false" @bind-HorizontalSpacing="@HorizontalSpacing" @bind-VerticalSpacing="@VerticalSpacing"> </Layout>
Refer to the following image.
You can use the ExpandIcon and CollapseIcon properties of nodes to implement the expand and collapse features in the hierarchical tree’s children nodes.
Refer to the following code example or the Blazor Playground app.
private void OnNodeCreating(IDiagramObject obj) { Node node = obj as Node; node.ExpandIcon = new DiagramExpandIcon() { Shape = DiagramExpandIcons.Minus, Height = 15, Width = 15, Fill = "Gray" }; node.CollapseIcon = new DiagramCollapseIcon() { Shape = DiagramCollapseIcons.Plus, Height = 15, Width = 15, Fill = "Gray" }; }
Refer to the following image.
You can easily change the multi-parent tree layout by dragging a parent or child node and dropping it in the required location. To enable editing of the hierarchical tree structure, add the following code to the Blazor Diagram component’s drop event. The demo is also available on the Blazor Playground app.
private async void Drop(DropEventArgs args) { if (args.Element != null && args.Element is DiagramSelectionSettings diagramSelectionSettings && diagramSelectionSettings.Nodes.Count > 0 && args.Target != null) { Node node = diagramSelectionSettings.Nodes[0]; List<string> edges = node.InEdges; Connector connector = Diagram.GetObject(edges[0]) as Connector; connector.SourceID = (args.Target as Node).ID; await Diagram.DoLayout(); } }
Refer to the following image.
We can adjust the spacing between each level of the multi-parent tree diagram horizontally and vertically. Also, define a margin around the diagram.
Refer to the following code example or the Blazor Playground app.
<Layout Type="LayoutType.ComplexHierarchicalTree" VerticalSpacing="70" HorizontalSpacing="70" Orientation="LayoutOrientation.TopToBottom"> <LayoutMargin Top="50" Bottom="0" Right="0" Left="10"></LayoutMargin> </Layout>
Refer to the following image.
Viewing a large diagram on a small screen can be challenging. In that case, we can use the zooming and panning features to view the multi-parent tree in detail.
You can zoom in and out of the diagram using the Ctrl + mouse wheel. Refer to the following code example to enable zooming and panning actions in the Diagram component. Also, refer to the demo on the Blazor Playground app.
<SfDiagramComponent ID="Diagram" Height="700px" InteractionController="DiagramInteractions.ZoomPan"></SfDiagramComponent>
Refer to the following image.
We can also export the diagram to different image formats, such as PNG, JPEG, and SVG. Refer to the following code example or the Blazor Playground app.
private async Task ExportDiagram() { DiagramExportSettings options = new DiagramExportSettings() { Region = DiagramPrintExportRegion.PageSettings, FitToPage = true, Margin = new DiagramThickness() { Bottom = 0, Left = 0, Right = 0, Top = 0 }, }; DiagramExportFormat format = DiagramExportFormat.JPEG; string fileName = "Diagram"; await Diagram.ExportAsync(fileName, format, options); }
Thanks for reading! In this blog, we have explored the process of creating and customizing a multi-parent hierarchical tree diagram using the Syncfusion Blazor Diagram component. Give it a try, and leave your feedback in the comments section given below!
Existing customers can download the latest version of Essential Studio® on the license and downloads page. If you are not a Syncfusion customer, try our 30-day free trial (No credit card required) to experience our incredible features.
You can also contact us through our support forum, support portal, or feedback portal. As always, we are happy to assist you!