Introduction
Learn how to use Spectre.Console Tree widget.
The Tree widget visualizes hierarchical data structures with parent-child relationships using Unicode tree characters.
💡 Tree API
Code samples
Are fully documented.
Base example
public static void Example1()
{
// Create a tree with a root node
var tree = new Tree("[deeppink3 bold]root node[/]")
.Guide(TreeGuide.Line);
// Add child node to the root node
TreeNode types = tree.AddNode("[yellow]Types[/] [dim]Child 1[/]");
// Add grandchild nodes to the child node
types.AddNode("[green]String[/] [dim]Grandchild 1.1[/]");
types.AddNode("[green]Int32[/] [dim]Grandchild 1.2[/]");
types.AddNode("[green]Boolean[/] [dim]Grandchild 1.3[/]");
// Add child node to the root node
TreeNode collection = tree.AddNode("[blue]Collections[/] [dim]Child 1[/]");
// Add grandchild nodes to the child node
collection.AddNode("[green]List<String>[/] [dim]Grandchild 1.1[/]");
collection.AddNode("[green]Dictionary<String,Int32>[/] [dim]Grandchild 1.2[/]");
AnsiConsole.Write(tree);
}
- First create a new Tree with a colorized label (Initializes a new instance of the Tree class.) and specify line type via Guide (Gets or sets the tree guide lines).
- Create child nodes off the tree
- Create child nodes (Grandchild)
- Use AnsiConsole.Write(tree) to display the tree
Using a Dictionary
This example uses a Dictionary<string, string[]>
public static void Example2()
{
var fileSystem = new Dictionary<string, string[]>
{
["Configuration"] = ["appsettings.json", "secrets.json"],
["Source Code"] = ["Main.cs", "Helper.cs", "Utilities.cs"],
["Documentation"] = ["README.md", "CHANGELOG.md"],
};
var tree = new Tree("[green bold]Application[/]");
foreach (var (folder, files) in fileSystem)
{
var folderNode = tree.AddNode($"[yellow]{folder}[/]");
foreach (var file in files)
{
folderNode.AddNode($"[grey]{file}[/]");
}
}
AnsiConsole.Write(tree);
}
- Dictionary keys represent child nodes of the tree node
- Dictionary values represent Grandchild nodes
Using JSON and Grouping
The basics apply as per above, a tree is created followed by child nodes by IEnumerable<IGrouping<string, Book>>
Model
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public decimal? Price { get; set; }
}
internal class BookOperations
{
public static void BooksGroupings()
{
SpectreConsoleHelpers.PrintPink();
List<Book> books = JsonSerializer.Deserialize<List<Book>>(Json())!;
IEnumerable<IGrouping<string, Book>> grouped = GroupBooksByPriceRange(books);
var tree = new Tree("[bold yellow]Books by Price Range[/]");
foreach (var (price, booksGroup) in grouped.Select(group
=> (group.Key, group)))
{
var priceNode = tree.AddNode($"[bold cyan]{Markup.Escape(price)}[/]");
foreach (var book in booksGroup)
{
priceNode.AddNode(
$"[green]Id:[/] {book.Id}, " +
$"[green]Title:[/] {Markup.Escape(book.Title)}, " +
$"[green]Price:[/] {book.Price:C}");
}
}
AnsiConsole.Write(tree);
}
private static string Json()
{
var json =
/* lang=json*/
"""
[
{
"Id": 1,
"Title": "Learn EF Core",
"Price": 19.0
},
{
"Id": 2,
"Title": "C# Basics",
"Price": 18.0
},
{
"Id": 3,
"Title": "ASP.NET Core advance",
"Price": 30.0
},
{
"Id": 4,
"Title": "VB.NET To C#",
"Price": 9.0
},
{
"Id": 5,
"Title": "Basic Azure",
"Price": 59.0
}
]
""";
return json;
}
private static IEnumerable<IGrouping<string, Book>> GroupBooksByPriceRange(List<Book> books)
=> books.GroupBy(b => b switch
{
{ Price: < 10 } => "Under $10",
{ Price: >= 10 and < 20 } => "$10 to $19",
{ Price: >= 20 and < 30 } => "$20 to $29",
{ Price: >= 30 } => "$30 and above",
_ => "Unknown"
});
}
EF Core example
This example uses one table to show relations for Employees and Managers.
internal class EmployeeOperations
{
public static void ReportsToManager()
{
PrintPink();
using var context = new Context();
var table = CreateViewTable();
List<Employees> employees = [.. context.Employees];
List<IGrouping<int?, Employees>> groupedData = employees
.Where(employee => employee.ReportsTo.HasValue)
.OrderBy(employee => employee.LastName)
.GroupBy(employee => employee.ReportsTo)
.ToList();
List<Manager> managers = [];
foreach (var group in groupedData)
{
Manager manager = new()
{
Employee = employees.Find(employee => employee.EmployeeID == group.Key.Value)
};
foreach (Employees groupedItem in group)
{
manager.Workers.Add(groupedItem);
}
managers.Add(manager);
}
managers = managers
.OrderBy(employee => employee.Employee.LastName)
.ToList();
var root = new Tree("[white][B]Employees[/][/]");
foreach (Manager manager in managers)
{
var currentNode = root.AddNode($"[Chartreuse1]{manager.Employee.FullName}[/] [Gray37](Manager)[/]");
foreach (var worker in manager.Workers)
{
/*
* Here a worker can also be a manager, so we check if the worker is also listed as a manager
* in our managers list. If they are, we format their name differently to indicate that
* they are both a worker and a manager.
*/
if (managers.Any(m => m.Employee.EmployeeID == worker.EmployeeID))
{
currentNode.AddNode($"{worker.FullName} <a href="Manager">Gray37</a>[/]");
}
else
{
currentNode.AddNode(worker.FullName);
}
}
}
AnsiConsole.Write(root);
Console.WriteLine();
}
private static Table CreateViewTable() =>
new Table()
.Border(TableBorder.Square)
.BorderColor(Color.Grey100)
.Alignment(Justify.Center)
.Title("~[white on blue][B]Employees[/][/]~")
.AddColumn(new TableColumn("[u]Manager[/]"))
.AddColumn(new TableColumn("[u]Workers[/]"));
}
Recursive directory tree with Collapse
Iterates a directory and collapses specific subfolders (obj and bin).
public static class DirectoryTree
{
public static void Display(string path)
{
if (!Directory.Exists(path))
{
AnsiConsole.MarkupLine("[red]Directory does not exist.[/]");
return;
}
DirectoryInfo root = new(path);
Tree tree = new($"[bold yellow]{Markup.Escape(root.FullName)}[/]");
AddDirectory(root, tree);
AnsiConsole.Write(tree);
}
private static void AddDirectory(DirectoryInfo directory, IHasTreeNodes parent)
{
bool isBinObj = directory.Name.Equals("obj", StringComparison.OrdinalIgnoreCase) ||
directory.Name.Equals("bin", StringComparison.OrdinalIgnoreCase);
try
{
foreach (DirectoryInfo subdirectory in directory.GetDirectories().OrderBy(d => d.Name))
{
TreeNode node = parent.AddNode($"[blue]📁 {Markup.Escape(subdirectory.Name)}[/]");
AddDirectory(subdirectory, node);
if (isBinObj)
{
node.Collapse();
}
}
if (!isBinObj)
{
foreach (FileInfo file in directory.GetFiles().OrderBy(f => f.Name))
{
parent.AddNode($"[green]📄 {Markup.Escape(file.Name)}[/]");
}
}
}
catch (UnauthorizedAccessException uae)
{
parent.AddNode("[red]Access denied[/]");
Log.Error(uae, "Access denied to directory: {Directory}", directory.FullName);
}
catch (IOException ex)
{
parent.AddNode($"[red]{Markup.Escape(ex.Message)}[/]");
Log.Error(ex, "IO exception occurred while accessing directory: {Directory}", directory.FullName);
}
}
}
Main Source code EF Core Source code
Summary
The provided code samples make it easy to create trees.





Top comments (0)