Sankey Chart

Represents a chart which displays data as nodes connected by weighted edges.

Rendered in 0 ms
Basic Usage

To get a Sankey Chart use ChartType="ChartType.Sankey" to render the configured ChartSeries as Nodes and Edges.

Income (3200)Expenses (2800)Savings (400)Housing (1200)Food (500)Insurance (250)Mobility (125)Travel (425)Leisure (300)Interest (10)Stocks (390)Rent (950)Other (250)Home insurance (50)Car insurance (75)Health insurance (125)Car (300)Public transport (125)
@using MudBlazor.Charts


<MudPaper Class="doc-section-component-container">
    <MudChart ChartType="ChartType.Sankey" Width="650px" Height="350px" ChartSeries="@_series"/>
</MudPaper>
@code {

    private List<ChartSeries<int>> _series = new()
    {
        new()
        { 
            Name = "Income Flow", 
            Data = new List<SankeyEdge<int>> 
            {
                //Income
                new("Income", "Expenses", 2800),
                new("Income", "Savings", 400),

                // Expenses
                new("Expenses", "Housing", 1200),
                new("Expenses", "Food", 500),
                new("Expenses", "Insurance", 250),
                new("Expenses", "Mobility", 125),
                new("Expenses", "Travel", 425),
                new("Expenses", "Leisure", 300),
                // Savings
                new("Savings", "Interest", 10),
                new("Savings", "Stocks", 390),

                // Housing
                new("Housing", "Rent", 950),
                new("Housing", "Other", 250),
                // Insurance
                new("Insurance", "Home insurance", 50),
                new("Insurance", "Car insurance", 75),
                new("Insurance", "Health insurance", 125),
                // Travel
                new("Travel", "Car", 300),
                new("Travel", "Public transport", 125),
            },
        }
    };
}
Customization

The Sankey Chart can be further customised using SankeyChartOptions.

Node width

5

Min. vertical spacing

5

Edge opacity

0.5

Chart width

650

Chart height

650
@using MudBlazor.Charts


<MudStack Row="true" Justify="Justify.Center" Class="mud-width-full">
    <MudPaper Class="pa-4">
        <MudStack Row="true">
            <MudNumericField @bind-Value="_nodeCount" Label="Nodes" Min="2" Max="500"/>
            <MudNumericField @bind-Value="_columnCount" Label="Columns" Min="2" Max="5"/>
            <MudButton OnClick="@(() => GenerateData())" StartIcon="@Icons.Material.Outlined.Refresh" Color="Color.Primary" Variant="Variant.Outlined">Generate</MudButton>
        </MudStack>
    </MudPaper>
</MudStack>

<MudPaper Class="doc-section-component-container">
    <MudChart ChartType="ChartType.Sankey" ChartSeries="@_series" Width="@($"{_width}px")" Height="@($"{_height}px")" ChartOptions="@_options" />
</MudPaper>

<MudGrid>
    <MudItem xs="12" Class="d-flex justify-center">
        <MudButtonGroup Color="Color.Primary" Variant="Variant.Outlined" Class="pt-4">
            <MudTooltip Text="Show edge values">
                <MudToggleIconButton Icon="@Icons.Material.Filled.ShortText" Color="@Color.Dark" ToggledColor="@Color.Primary" @bind-Toggled="_options.ShowEdgeLabels" />
            </MudTooltip>
            <MudTooltip Text="Show labels">
                <MudToggleIconButton Icon="@Icons.Material.Filled.Textsms" Color="@Color.Dark" ToggledColor="@Color.Primary" @bind-Toggled="_options.ShowLabels"/>
            </MudTooltip>
            <MudTooltip Text="Show node values">
                <MudToggleIconButton Icon="@Icons.Material.Filled.Money" Color="@Color.Dark" ToggledColor="@Color.Primary" @bind-Toggled="_options.ShowNodeValues" />
            </MudTooltip>
            <MudTooltip Text="Highlight on hover">
                <MudToggleIconButton Icon="@Icons.Material.Filled.Highlight" Color="@Color.Dark" ToggledColor="@Color.Primary" @bind-Toggled="_options.HighlightOnHover" />
            </MudTooltip>
            <MudTooltip Text="Hide nodes with no edges">
                <MudToggleIconButton Icon="@Icons.Material.Filled.DisabledVisible" Color="@Color.Dark" ToggledColor="@Color.Primary" @bind-Toggled="_options.HideNodesWithNoEdges" />
            </MudTooltip>
            <MudTooltip Text="Order nodes by value">
                <MudToggleIconButton Icon="@Icons.Material.Filled.Sort" Color="@Color.Dark" ToggledColor="@Color.Primary" @bind-Toggled="_options.OrderNodesByValue" />
            </MudTooltip>
        </MudButtonGroup>
    </MudItem>
    <MudItem xs="12" md="4">
        <MudSlider @bind-Value="_options.NodeWidth" Max="50" ValueLabel="true">Node width</MudSlider>
    </MudItem>
    <MudItem xs="12" md="4">
        <MudSlider @bind-Value="_options.MinVerticalSpacing" Max="30" ValueLabel="true">Min. vertical spacing</MudSlider>
    </MudItem>
    <MudItem xs="12" md="4">
        <MudSlider @bind-Value="_options.EdgeOpacity" Step="0.01" Max="1" ValueLabel="true">Edge opacity</MudSlider>
    </MudItem>
    <MudItem xs="12" md="6">
        <MudSlider @bind-Value="_width" ValueLabel="true" Max="2000">Chart width</MudSlider>
    </MudItem>
    <MudItem xs="12" md="6">
        <MudSlider @bind-Value="_height" ValueLabel="true" Max="2000">Chart height</MudSlider>
    </MudItem>
    <MudItem xs="12" md="6">
        <MudColorPicker Label="Highlighting color" @bind-Text="@_options.HighlightColor" />
    </MudItem>
    <MudItem xs="12" md="6">
        <MudNumericField @bind-Value="_options.HideNodesSmallerThan" Label="Hide nodes smaller than" />
    </MudItem>
    <MudItem xs="12" md="6">
        <MudSelect @bind-Value="_options.LabelFontSize" Label="Label font size">
            <MudSelectItem Value="@("0.25rem")">0.25rem</MudSelectItem>
            <MudSelectItem Value="@("0.33rem")">0.33rem</MudSelectItem>
            <MudSelectItem Value="@("0.5rem")">0.5rem</MudSelectItem>
            <MudSelectItem Value="@("0.66rem")">0.66rem</MudSelectItem>
            <MudSelectItem Value="@("0.75rem")">0.75rem</MudSelectItem>
            <MudSelectItem Value="@("1rem")">1rem</MudSelectItem>
            <MudSelectItem Value="@("1.25rem")">1.25rem</MudSelectItem>
        </MudSelect>
    </MudItem>
    <MudItem xs="12" md="6">
        <MudNumericField @bind-Value="_options.LabelPadding" Label="Label padding" />
    </MudItem>
</MudGrid>
@code {
    private List<SankeyNode> _nodes = [];
    private List<SankeyEdge<double>> _edges = [];
    private List<ChartSeries<double>> _series = [];
    private readonly SankeyChartOptions _options = new()
    {
        NodeWidth = 5,
        MinVerticalSpacing = 5,
        LabelFontSize = "0.33rem",
        LabelPadding = 2,
        OrderNodesByValue = true,
        HideNodesSmallerThan = 1
    };

    private int _width = 650;
    private int _height = 650;
    private int _nodeCount = 50;
    private int _columnCount = 3;

    protected override void OnAfterRender(bool firstRender)
    {
        base.OnAfterRender(firstRender);
        if (firstRender) GenerateData();
    }

    private void GenerateData(double minEdgePercentage = 0.15, double maxEdgePercentage = 0.7)
    {
        var rnd = new Random();
        _nodes = [];
        _edges = [];

        var nodesPerColumn = (int)Math.Ceiling((double)_nodeCount / _columnCount);
        for (var col = 0; col < _columnCount; col++)
        {
            var nodesInCurrentColumn = Math.Min(nodesPerColumn, _nodeCount - _nodes.Count);
            for (var i = 0; i < nodesInCurrentColumn; i++)
            {
                var nodeName = $"Node_{col}_{i}";
                _nodes.Add(new SankeyNode(nodeName, col));
            }
        }

        var nodesByColumn = _nodes.GroupBy(n => n.Column)
            .OrderBy(g => g.Key)
            .Select(g => g.ToList())
            .ToList();
        var nodeOutputs = new Dictionary<string, double>();
        var nodeInputs = new Dictionary<string, double>();
        foreach (var node in _nodes)
        {
            nodeOutputs[node.Name] = 0;
            nodeInputs[node.Name] = 0;
        }

        for (var colI = 0; colI < nodesByColumn.Count - 1; colI++)
        {
            var sourceNodes = nodesByColumn[colI];
            var targetNodes = nodesByColumn[colI + 1];

            if (colI == 0)
            {
                foreach (var sourceNode in sourceNodes)
                {
                    nodeOutputs[sourceNode.Name] = rnd.Next(1, 1000);
                }
            }

            foreach (var sourceNode in sourceNodes)
            {
                var remainingEdgeValue = nodeOutputs[sourceNode.Name];
                var edgeCount = rnd.Next(1, Math.Min(targetNodes.Count, 3) + 1);
                var selectedTargets = targetNodes.OrderBy(_ => rnd.Next()).Take(edgeCount).ToList();

                for (var i = 0; i < selectedTargets.Count; i++)
                {
                    var targetNode = selectedTargets[i];
                    double edgeValue;

                    if (i == selectedTargets.Count - 1)
                    {
                        edgeValue = remainingEdgeValue;
                    }
                    else
                    {
                        var maxEdgeValue = remainingEdgeValue - (selectedTargets.Count - i - 1) * 10;
                        edgeValue = rnd.NextDouble() * maxEdgeValue * maxEdgePercentage + maxEdgeValue * minEdgePercentage;
                        remainingEdgeValue -= edgeValue;
                    }

                    _edges.Add(new SankeyEdge<double>(sourceNode.Name, targetNode.Name, Math.Round(edgeValue)));
                    nodeInputs[targetNode.Name] += edgeValue;
                }
            }

            foreach (var targetNode in targetNodes)
            {
                nodeOutputs[targetNode.Name] = nodeInputs[targetNode.Name];
            }
        }

        _edges.RemoveAll(e => e.Weight <= 0);

        _series = new List<ChartSeries<double>>()
        {
            new()
            {
                Name = "Generated Data",
                Data = _edges
            }
        };

        _options.NodeOverrides = _nodes;

        StateHasChanged();
    }
}
Events

The only event the Sankey Chart currently provides is the selected index one for nodes.

Balls (60)Soccer ball (20)Football (10)Tennis ball (30)Good condition (32)Bad condition (28)

Node selected:

@using MudBlazor.Charts


<MudPaper Class="doc-section-component-container">
    <MudChart @ref="_mudChart" ChartType="ChartType.Sankey" ChartSeries="@Series.AsList()" Width="@_width" Height="@_height" @bind-SelectedIndex="_selectedIndex"/>
</MudPaper>

<MudGrid>
    <MudItem xs="12">
        <MudText>Node selected: @(_selectedIndex >= 0 ? Chart.Nodes.ElementAt(_selectedIndex).Name : "")</MudText>
    </MudItem>
</MudGrid>
@code {
    private MudChart<int> _mudChart = null!;
    private string _width = "650px";
    private string _height = "350px";
    private int _selectedIndex = -1;

    public Sankey<int> Chart => _mudChart.ChartReference as Sankey<int>;

    private readonly List<SankeyEdge<int>> _edges =
    [
        new("Balls", "Soccer ball", 20),
        new("Balls", "Football", 10),
        new("Balls", "Tennis ball", 30),
        
        new("Soccer ball", "Good condition", 17),
        new("Soccer ball", "Bad condition", 3),
        new("Football", "Good condition", 5),
        new("Football", "Bad condition", 5),
        new("Tennis ball", "Good condition", 10),
        new("Tennis ball", "Bad condition", 20),
    ];

    private ChartSeries<int> Series => new() { Name = "Ball Catalog", Data = _edges };
}
An unhandled error has occurred. Reload 🗙