Hierarchical charts
editBuild a tree
The hierarchical marks draw a tree of HierarchyNode<T>. Hierarchy builds one from nested objects, from rows that give the identifier of their parent, or from paths. Pass a value to compute the value of every node: its own value plus the values of its descendants. NaN, infinite and negative values count as zero, and a sum larger than double.MaxValue is double.MaxValue. SortByValue sorts the children by decreasing value, which gives better treemaps and packings.
public sealed record Department(string Name, double People, IReadOnlyList<Department> Teams);var tree = new Department("Company", 0,
[
new("Engineering", 0, [new("Platform", 12, []), new("Product", 18, []), new("Data", 7, [])]),
new("Sales", 0, [new("Europe", 9, []), new("Americas", 11, [])]),
new("Support", 6, []),
]);
var root = Hierarchy.FromTree(tree, children: department => department.Teams, name: department => department.Name, value: department => department.People);
Assert.Equal(63, root.Value);
var plot = new Plot { Title = "People by team", Width = 400, Height = 440 };
plot.Add(new PackMark<Department>(root) { Padding = 4 });var rows = new[]
{
(Id: "root", Parent: (string?)null, Size: 0.0),
(Id: "a", Parent: "root", Size: 0.0),
(Id: "a1", Parent: "a", Size: 3.0),
(Id: "a2", Parent: "a", Size: 5.0),
(Id: "b", Parent: "root", Size: 4.0),
};
var root = Hierarchy.Stratify(rows, row => row.Id, row => row.Parent, row => row.Size);The examples below build the tree from file paths:
public sealed record FileEntry(string Path, double Size);The nodes are colored by their top-level group, unless the Fill channel of the mark says otherwise.
Packed circles
A PackMark<T> packs the leaves as circles whose areas are proportional to their values. The layout is deterministic.
FileEntry[] files =
[
new("src/app/main.cs", 120), new("src/app/view.cs", 80), new("src/app/model.cs", 60),
new("src/core/parser.cs", 140), new("src/core/lexer.cs", 90), new("src/core/ast.cs", 45),
new("tests/app.tests.cs", 70), new("tests/core.tests.cs", 85),
new("docs/readme.md", 40), new("docs/guide.md", 55),
];
var root = Hierarchy.FromPaths(files, file => file.Path, value: file => file.Size).SortByValue();
var plot = new Plot { Title = "Size of the files", Width = 480, Height = 520 };
plot.Add(new PackMark<FileEntry?>(root));Treemap
A TreemapMark<T> divides the area into rectangles. PaddingTop leaves room for the names of the groups. The default tiling is squarified; TreemapTiling has the others.
var root = Hierarchy.FromPaths(SampleData.Files, file => file.Path, value: file => file.Size).SortByValue();
var plot = new Plot { Title = "Size of the files" };
plot.Add(new TreemapMark<FileEntry?>(root) { PaddingTop = 18, PaddingOuter = 2, PaddingInner = 2 });
plot.Legend.Visible = false;Sunburst
A PartitionMark<T> in polar coordinates draws a ring per depth.
var root = Hierarchy.FromPaths(SampleData.Files, file => file.Path, value: file => file.Size).SortByValue();
var plot = new Plot
{
Title = "Size of the files",
Width = 480,
Height = 480,
Coordinates = new PolarCoordinates { InnerRadius = 0.2 },
};
plot.Add(new PartitionMark<FileEntry?>(root));Icicle
The same mark in Cartesian coordinates draws a layer per depth: from the top down, or from the left when transposed.
var root = Hierarchy.FromPaths(SampleData.Files, file => file.Path, value: file => file.Size).SortByValue();
var plot = new Plot { Title = "Size of the files", Coordinates = new CartesianCoordinates { Transposed = true } };
plot.Add(new PartitionMark<FileEntry?>(root) { ShowRoot = true, Label = node => node.Depth is 0 ? "all" : node.Name });
plot.Legend.Visible = false;Tree diagram
A TreeMark<T> draws a node-link diagram with the tidy tree layout. The root is at the top in Cartesian coordinates, and on the left when they are transposed.
var root = Hierarchy.FromPaths(SampleData.Files, file => file.Path);
var plot = new Plot { Title = "Files of the repository", Height = 420, Coordinates = new CartesianCoordinates { Transposed = true } };
plot.Add(new TreeMark<FileEntry?>(root) { Label = node => node.Depth is 0 ? "repository" : node.Name });
plot.Legend.Visible = false;Radial dendrogram
With TreeLayoutKind.Cluster, the leaves are all at the deepest level: a dendrogram. In polar coordinates, the tree is radial.
var root = Hierarchy.FromPaths(SampleData.Files, file => file.Path);
var plot = new Plot { Title = "Files of the repository", Width = 520, Height = 520, Coordinates = new PolarCoordinates() };
plot.Add(new TreeMark<FileEntry?>(root) { Layout = TreeLayoutKind.Cluster });
plot.Legend.Visible = false;Layouts
The layouts are public, to draw trees yourself: PackLayout, TreemapLayout and PartitionLayout return the circle or the rectangle of every node, TreeLayout and ClusterLayout its point.
var root = Hierarchy.FromPaths(["a/x", "a/y", "b"], path => path, value: path => path.Length).SortByValue();
var layout = new TreemapLayout { Width = 300, Height = 200, Tiling = tiling };
var rectangles = layout.Layout(root);var root = Hierarchy.FromPaths(["a/1", "a/2", "a/3", "b", "c/1/x", "c/1/y", "c/2"], path => path);
var points = new TreeLayout { Width = 600, Height = 300 }.Layout(root);