Back to Devexpress

SankeyDiagramControl Class

wpf-devexpress-dot-xpf-dot-charts-dot-sankey.md

latest30.7 KB
Original Source

SankeyDiagramControl Class

Displays a multilevel Sankey diagram.

Namespace : DevExpress.Xpf.Charts.Sankey

Assembly : DevExpress.Xpf.Charts.v25.2.dll

NuGet Package : DevExpress.Wpf.Charts

Declaration

csharp
[DXLicenseWpf]
public class SankeyDiagramControl :
    Control,
    IWeakEventListener,
    IPrintableControl,
    IPrintingTarget,
    IChartsPrintableControl,
    ISankeyLayoutOptionsProvider<SankeyNode>
vb
<DXLicenseWpf>
Public Class SankeyDiagramControl
    Inherits Control
    Implements IWeakEventListener,
               IPrintableControl,
               IPrintingTarget,
               IChartsPrintableControl,
               ISankeyLayoutOptionsProvider(Of SankeyNode)

Remarks

A Sankey diagram depicts transfers or flows between entities (also called nodes) in a system. This diagram can help locate the most important contributions to a flow.

The following image displays diagram elements:

Each link connects the source and target nodes and has an assigned value - weight. The width of a link is proportional to its weight.

Demos

Run Demo: Colorizer

Add to the Project

Drag and drop the SankeyDiagramControl component from the Toolbox to the form to add a Sankey diagram to the project.

This adds references to the following assemblies to the project:

  • DevExpress.Charts.v25.2.Core.dll
  • DevExpress.Data.v25.2.dll
  • DevExpress.DataVisualization.v25.2.Core.dll
  • DevExpress.Mvvm.v25.2.dll
  • DevExpress.Printing.v25.2.Core.dll
  • DevExpress.Xpf.Charts.v25.2.dll
  • DevExpress.Xpf.Core.v25.2.dll
  • DevExpress.Xpf.Printing.v25.2.dll

Bind to Data

Use the DataSource property to bind the control to a data source. You can assign this property to an object that implements any of the following interfaces: IList, IListSource, or IBindingList.

Then, specify the names of data members that store data for source nodes, target nodes, and weights:

  • SourceDataMember - Specifies the name of a data member that contains source node labels.

  • TargetDataMember - Specifies the name of a data member that contains target node labels.

  • WeightDataMember (Optional) - Specifies the name of a data member that contains link weights. If the WeightDataMember property is not specified, weights are equal to 1.

  • XAML

xaml
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:SankeySample"
        xmlns:dxsa="http://schemas.devexpress.com/winfx/2008/xaml/sankey" 
        x:Class="SankeySample.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <dxsa:SankeyDiagramControl DataSource="{Binding Data}" 
                                   SourceDataMember="Source" 
                                   TargetDataMember="Target" 
                                   WeightDataMember="Value">
            <dxsa:SankeyDiagramControl.DataContext>
                <local:SankeyViewModel/>
            </dxsa:SankeyDiagramControl.DataContext>
            <dxsa:SankeyDiagramControl.Titles>
                <dxsa:SankeyTitle Content="Export/Import" 
                                  HorizontalAlignment="Center"/>
            </dxsa:SankeyDiagramControl.Titles>
        </dxsa:SankeyDiagramControl>
    </Grid>
</Window>
csharp
using System.Collections.Generic;
using System.Windows;

namespace SankeySample {
//...
    public class SankeyViewModel {
        public List<SankeyItem> Data {
            get { return GetData(); }
        }
        public List<SankeyItem> GetData() {
            List<SankeyItem> data = new List<SankeyItem>{                
                new SankeyItem { Source = "France", Target = "UK", Value = 53 },
                new SankeyItem { Source = "Australia", Target = "UK", Value = 72 },
                new SankeyItem { Source = "France", Target = "Canada", Value = 81 },
                new SankeyItem { Source = "China", Target = "Canada", Value = 96 },
                new SankeyItem { Source = "UK", Target = "France", Value = 61 },
                new SankeyItem { Source = "Canada", Target = "France", Value = 89 } 
            };
            return data;
        }
    }
    public class SankeyItem {
        public string Source { get; set; }
        public string Target { get; set; }
        public double Value { get; set; }
    }
}
vb
Imports System.Collections.Generic

Namespace SankeySample
    '...
    Public Class SankeyViewModel
        Public ReadOnly Property Data As List(Of SankeyItem)
            Get
                Return GetData()
            End Get
        End Property

        Public Function GetData() As List(Of SankeyItem)
            Dim data As List(Of SankeyItem) = New List(Of SankeyItem) From {
                New SankeyItem With {
                    .Source = "France", .Target = "UK", .Value = 53
                },
                New SankeyItem With {
                    .Source = "Australia", .Target = "UK", .Value = 72
                },
                New SankeyItem With {
                    .Source = "France", .Target = "Canada", .Value = 81
                },
                New SankeyItem With {
                    .Source = "China", .Target = "Canada", .Value = 96
                },
                New SankeyItem With {
                    .Source = "UK", .Target = "France", .Value = 61
                },
                New SankeyItem With {
                    .Source = "Canada", .Target = "France", .Value = 89
                }
            }
            Return data
        End Function
    End Class

    Public Class SankeyItem
        Public Property Source As String
        Public Property Target As String
        Public Property Value As Double
    End Class
End Namespace

Result:

Customize Nodes

Assign a SankeyNodeLabel object to the SankeyDiagramControl.NodeLabel property to specify node label settings such as text orientation:

xaml
<dxsa:SankeyDiagramControl>
    <!--...-->
    <dxsa:SankeyDiagramControl.NodeLabel>
        <dxsa:SankeyNodeLabel TextOrientation="BottomToTop"/>
    </dxsa:SankeyDiagramControl.NodeLabel>
</dxsa:SankeyDiagramControl>

Initialize the SankeyDiagramControl.NodeTemplate property with a DataTemplate object to customize node label appearance.

xaml
<dxsa:SankeyDiagramControl.NodeTemplate>
    <DataTemplate>
        <Rectangle Stroke="Black" 
                   StrokeThickness="2" 
                   Fill="Transparent"/>
    </DataTemplate>
</dxsa:SankeyDiagramControl.NodeTemplate>

For more information about template configuration, refer to the following topic: Data Templating Overview.

Sort Nodes

The control automatically arranges nodes based on underlying data. If you wish to rearrange nodes or specify a custom sort order, create a class that implements IComparer<SankeyNode>. Then, assign an object of this class to the NodeComparer property.

The following code arranges nodes in descending order of their TotalWeight values:

xaml
<dxsa:SankeyDiagramControl>
    <!--...-->
    <dxsa:SankeyDiagramControl.NodeComparer>
        <local:MyNodeComparer/>
    </dxsa:SankeyDiagramControl.NodeComparer>
    <!--...-->
</dxsa:SankeyDiagramControl>
csharp
using DevExpress.Xpf.Charts.Sankey;
//...
public class MyNodeComparer : IComparer<SankeyNode> {
    public int Compare(SankeyNode x, SankeyNode y) {
        return y.TotalWeight.CompareTo(x.TotalWeight);
    }
}
vb
Imports DevExpress.Xpf.Charts.Sankey
'...
Public Class MyNodeComparer
    Inherits IComparer(Of SankeyNode)

    Public Function Compare(ByVal x As SankeyNode, ByVal y As SankeyNode) As Integer
        Return y.TotalWeight.CompareTo(x.TotalWeight)
    End Function
End Class

Define Node Layout Algorithm

You can use the predefined SankeyLinearLayoutAlgorithm to change node position. The SankeyLinearLayoutAlgorithm.NodeAlignment property specifies the node alignment.

The following example aligns nodes to the top of the Sankey diagram:

xaml
<dxsa:SankeyDiagramControl x:Name="sankeyDiagramControl1" DataSource="{Binding Data}" 
                           SourceDataMember="Source" 
                           TargetDataMember="Target" 
                           WeightDataMember="Value">
    <!--...-->
    <dxsa:SankeyDiagramControl.LayoutAlgorithm>
        <dxsa:SankeyLinearLayoutAlgorithm NodeAlignment="Far"/>
    </dxsa:SankeyDiagramControl.LayoutAlgorithm>
</dxsa:SankeyDiagramControl>

You can also implement a custom layout. See the following help topic for more information: SankeyLayoutAlgorithmBase.

The Sankey diagram control highlights a node or a link when the mouse cursor hovers over this element (the default behavior). A node is highlighted with the related links. The image below shows the highlighted Canada node:

Use the EnableHighlighting property to disable highlighting:

xaml
<dxsa:SankeyDiagramControl x:Name="sankeyDiagramControl1" 
                           EnableHighlighting="False"  
                           DataSource="{Binding Data}"  
                           SourceDataMember="Source" 
                           TargetDataMember="Target" 
                           WeightDataMember="Value">
    <!--...-->
</dxsa:SankeyDiagramControl>

The SelectionMode property defines whether a user can select links and nodes. Nodes are selected with the related links. The image below shows the selected Canada node:

When SelectionMode is None (default), a user cannot select Sankey elements. Set this property to Single, Multiple, or Extended to allow users to select Sankey nodes and links.

The following markup allows a user to select multiple Sankey elements:

xaml
<dxsa:SankeyDiagramControl x:Name="sankeyDiagramControl1" 
                           SelectionMode="Multiple"...>
    <!--...-->
</dxsa:SankeyDiagramControl>

To select Sankey nodes and links programmatically, add them to the SelectedItems collection. The following example selects the France node and the related links:

xaml
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        xmlns:dxsa="http://schemas.devexpress.com/winfx/2008/xaml/sankey" 
        x:Class="WpfApp1.MainWindow"
        mc:Ignorable="d"
        Title="MainWindow">

    <Grid>
        <dxsa:SankeyDiagramControl x:Name="sankeyDiagramControl1" 
                                   SelectedItems="{Binding SelectedSankeyItems}" ...>
            <!--...-->
            <dxsa:SankeyDiagramControl.DataContext>
                <local:SankeyViewModel/>
            </dxsa:SankeyDiagramControl.DataContext>
        </dxsa:SankeyDiagramControl>
    </Grid>
</Window>
csharp
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows;
using DevExpress.Xpf.Charts.Sankey;

namespace WpfApp1 {

    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
    }

    public class SankeyViewModel : INotifyPropertyChanged {
        IList<object> selectedSankeyItems;
        List<SankeyItem> data;

        public List<SankeyItem> Data {
            get {
                if (data == null)
                    data = GetData();
                return data;
            }
        }
        public IList<object> SelectedSankeyItems {
            get { return selectedSankeyItems; }
            set {
                if (selectedSankeyItems != value) {
                    if (selectedSankeyItems is ObservableCollection<object>)
                        ((ObservableCollection<object>)selectedSankeyItems).CollectionChanged -= SelectedSankeyItems_CollectionChanged;
                    selectedSankeyItems = value;
                    if (selectedSankeyItems is ObservableCollection<object>)
                        ((ObservableCollection<object>)selectedSankeyItems).CollectionChanged += SelectedSankeyItems_CollectionChanged;
                    OnPropertyChanged("SelectedSankeyItems");
                }
            }
        }

        public SankeyViewModel() {
            ObservableCollection<object> collection = new ObservableCollection<object>();
            collection.CollectionChanged += SelectedSankeyItems_CollectionChanged;
            selectedSankeyItems = collection;
            selectedSankeyItems.Add("France");
            selectedSankeyItems.Add(Data[0]);
            selectedSankeyItems.Add(Data[2]);
            selectedSankeyItems.Add(Data[5]);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        void SelectedSankeyItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
        }
        void OnPropertyChanged(string propertyName) {
            PropertyChangedEventHandler propertyChangedEventHandler = PropertyChanged;
            if (propertyChangedEventHandler != null)
                propertyChangedEventHandler(this, new PropertyChangedEventArgs(propertyName));
        }

        public List<SankeyItem> GetData() {
            List<SankeyItem> data = new List<SankeyItem>{
                new SankeyItem { Source = "France", Target = "UK", Value = 53 },
                new SankeyItem { Source = "Australia", Target = "UK", Value = 72 },
                new SankeyItem { Source = "France", Target = "Canada", Value = 81 },
                new SankeyItem { Source = "China", Target = "Canada", Value = 96 },
                new SankeyItem { Source = "UK", Target = "France", Value = 61 },
                new SankeyItem { Source = "Canada", Target = "France", Value = 89 }
            };
            return data;
        }
    }
    public class SankeyItem {
        public string Source { get; set; }
        public string Target { get; set; }
        public double Value { get; set; }
    }
}
vb
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Collections.Specialized
Imports System.ComponentModel
Imports DevExpress.Xpf.Charts.Sankey

Namespace WpfApp1
    Public Partial Class MainWindow
        Inherits Window

        Public Sub New()
            InitializeComponent()
        End Sub
    End Class

    Public Class SankeyViewModel
        Implements INotifyPropertyChanged

        Private selectedSankeyItemsField As IList(Of Object)
        Private dataField As List(Of SankeyItem)

        Public ReadOnly Property Data As List(Of SankeyItem)
            Get
                If dataField Is Nothing Then dataField = GetData()
                Return dataField
            End Get
        End Property

        Public Property SelectedSankeyItems As IList(Of Object)
            Get
                Return selectedSankeyItemsField
            End Get
            Set(ByVal value As IList(Of Object))

                If selectedSankeyItemsField IsNot value Then
                    If TypeOf selectedSankeyItemsField Is ObservableCollection(Of Object) Then RemoveHandler CType(selectedSankeyItemsField, ObservableCollection(Of Object)).CollectionChanged, AddressOf SelectedSankeyItems_CollectionChanged
                    selectedSankeyItemsField = value
                    If TypeOf selectedSankeyItemsField Is ObservableCollection(Of Object) Then AddHandler CType(selectedSankeyItemsField, ObservableCollection(Of Object)).CollectionChanged, AddressOf SelectedSankeyItems_CollectionChanged
                    OnPropertyChanged("SelectedSankeyItems")
                End If
            End Set
        End Property

        Public Sub New()
            Dim collection As ObservableCollection(Of Object) = New ObservableCollection(Of Object)()
            AddHandler collection.CollectionChanged, AddressOf SelectedSankeyItems_CollectionChanged
            selectedSankeyItemsField = collection
            selectedSankeyItemsField.Add("France")
            selectedSankeyItemsField.Add(Data(0))
            selectedSankeyItemsField.Add(Data(2))
            selectedSankeyItemsField.Add(Data(5))
        End Sub

        Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged

        Private Sub SelectedSankeyItems_CollectionChanged(ByVal sender As Object, ByVal e As NotifyCollectionChangedEventArgs)
        End Sub

        Private Sub OnPropertyChanged(ByVal propertyName As String)
            Dim propertyChangedEventHandler As PropertyChangedEventHandler = PropertyChangedEvent
            If propertyChangedEventHandler IsNot Nothing Then propertyChangedEventHandler(Me, New PropertyChangedEventArgs(propertyName))
        End Sub

        Public Function GetData() As List(Of SankeyItem)
            Dim data As List(Of SankeyItem) = New List(Of SankeyItem) From {
                New SankeyItem With {
                    .Source = "France",
                    .Target = "UK",
                    .Value = 53
                },
                New SankeyItem With {
                    .Source = "Australia",
                    .Target = "UK",
                    .Value = 72
                },
                New SankeyItem With {
                    .Source = "France",
                    .Target = "Canada",
                    .Value = 81
                },
                New SankeyItem With {
                    .Source = "China",
                    .Target = "Canada",
                    .Value = 96
                },
                New SankeyItem With {
                    .Source = "UK",
                    .Target = "France",
                    .Value = 61
                },
                New SankeyItem With {
                    .Source = "Canada",
                    .Target = "France",
                    .Value = 89
                }
            }
            Return data
        End Function
    End Class

    Public Class SankeyItem
        Public Property Source As String
        Public Property Target As String
        Public Property Value As Double
    End Class
End Namespace

For more examples on selection, see the demo:

Run Demo: Interaction

The Sankey diagram control uses the SankeyPaletteColorizer to color nodes. A new color from the palette is applied to each node with a unique label. Colors repeat if the number of unique labels exceeds the number of palette colors. To apply a gradient fill to links, the control utilizes the source and target node colors. Specify the SankeyPaletteColorizer.Palette property to change colors that are used to paint a Sankey diagram. You can select one of the predefined palettes.

xaml
xmlns:dxc="http://schemas.devexpress.com/winfx/2008/xaml/charts"
<!--...-->
<dxsa:SankeyDiagramControl.Colorizer>
    <dxsa:SankeyPaletteColorizer LinkBrush="Gray">
        <dxsa:SankeyPaletteColorizer.Palette>
            <dxc:NorthernLightsPalette/>
        </dxsa:SankeyPaletteColorizer.Palette>
    </dxsa:SankeyPaletteColorizer>
</dxsa:SankeyDiagramControl.Colorizer>

You can also create a new palette as follows:

xaml
xmlns:dxc="http://schemas.devexpress.com/winfx/2008/xaml/charts"
<!--...-->
<dxsa:SankeyDiagramControl.Colorizer>
    <dxsa:SankeyPaletteColorizer LinkBrush="Gray">
        <dxsa:SankeyPaletteColorizer.Palette>
            <dxc:CustomPalette>
                <dxc:CustomPalette.Colors>
                    <Color>Red</Color>
                    <Color>Green</Color>
                    <Color>Magenta</Color>
                </dxc:CustomPalette.Colors>
            </dxc:CustomPalette>
        </dxsa:SankeyPaletteColorizer.Palette>
    </dxsa:SankeyPaletteColorizer>
</dxsa:SankeyDiagramControl.Colorizer>

Custom Colorizer

Create a class derived from the SankeyColorizerBase class to paint links and nodes based on a custom algorithm. Then, assign an object of this class to the Colorizer property.

The following example implements a colorizer that applies random colors to nodes and specifies colors used to apply a gradient fill to links.

xaml
<dxsa:SankeyDiagramControl.Colorizer>
    <local:MyColorizer LinkSourceColor="Yellow" LinkTargetColor="Red" />
</dxsa:SankeyDiagramControl.Colorizer>
csharp
public class MyColorizer : SankeyColorizerBase {
    readonly Random rand = new Random();
    readonly Dictionary<string, Color> KeyColorPairs = new Dictionary<string, Color>();
    public Color LinkSourceColor { get; set; }
    public Color LinkTargetColor { get; set; }
    public override Color GetLinkSourceColor(SankeyLink link) {
        return LinkSourceColor;
    }
    public override Color GetLinkTargetColor(SankeyLink link) {
        return LinkTargetColor;
    }
    public override Color GetNodeColor(SankeyNode info) {
        if (!KeyColorPairs.TryGetValue((string)info.Tag, out Color nodeColor)) {
            nodeColor = GenerateColor();
            KeyColorPairs.Add((string)info.Tag, nodeColor);
        }
        return nodeColor;
    }
    private Color GenerateColor() {
        return Color.FromRgb((byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256));
    }
}
vb
Public Class MyColorizer
    Inherits SankeyColorizerBase
    Private ReadOnly rand As Random = New Random()
    Private ReadOnly KeyColorPairs As Dictionary(Of String, Color) = New Dictionary(Of String, Color)()
    Public Property LinkSourceColor As Color
    Public Property LinkTargetColor As Color

    Public Overrides Function GetLinkSourceColor(ByVal link As SankeyLink) As Color
        Return LinkSourceColor
    End Function

    Public Overrides Function GetLinkTargetColor(ByVal link As SankeyLink) As Color
        Return LinkTargetColor
    End Function

    Public Overrides Function GetNodeColor(ByVal info As SankeyNode) As Color
        Dim nodeColor As Color = Nothing

        If Not KeyColorPairs.TryGetValue(CStr(info.Tag), nodeColor) Then
            nodeColor = GenerateColor()
            KeyColorPairs.Add(CStr(info.Tag), nodeColor)
        End If

        Return nodeColor
    End Function

    Private Function GenerateColor() As Color
        Return Color.FromRgb(rand.Next(256), rand.Next(256), rand.Next(256))
    End Function
End Class

Customize Tooltips

Tooltips are shown when the mouse pointer hovers over a node or link. Use the ToolTipOptions, SankeyToolTipOptions.LinkToolTipEnabled and SankeyToolTipOptions.NodeToolTipEnabled properties to access tooltip settings and disable/enable tooltips.

xaml
<dxsa:SankeyDiagramControl>
    <dxsa:SankeyDiagramControl.ToolTipOptions>
        <dxsa:SankeyToolTipOptions OpenMode="OnHover" 
                                   AutoPopDelay="00:00:03" 
                                   InitialDelay="00:00:00.1" 
                                   CloseOnClick="False"
                                   LinkToolTipEnabled="True" 
                                   NodeToolTipEnabled="True"/>
    </dxsa:SankeyDiagramControl.ToolTipOptions>
</dxsa:SankeyDiagramControl>

Initialize the SankeyDiagramControl.ToolTipTemplate property with a DataTemplate to customize tooltip appearance.

xaml
<dxsa:SankeyDiagramControl.ToolTipTemplate>
    <DataTemplate>
        <Border BorderThickness="1" 
        CornerRadius="10" 
        Opacity="1.0" 
        Background="#FF2C2B2B">
            <StackPanel Orientation="Vertical" Margin="8">
                <Label Foreground="White" 
                FontStyle="Italic" 
                FontSize="14" 
                Content="{Binding Path=Info.ToolTipText}" />
            </StackPanel>
        </Border>
    </DataTemplate>
</dxsa:SankeyDiagramControl.ToolTipTemplate>

Use one of the methods below to print the control:

The following methods allow you to export the control to various formats:

The code below specifies the resulting Sankey diagram image’s width to fit the document width and exports a Sankey diagram to a PDF file:

csharp
sankeyDiagram.PrintOptions = new SankeyPrintOptions { 
    SizeMode = DevExpress.Xpf.Charts.PrintSizeMode.ProportionalZoom 
};
sankeyDiagram.ExportToPdf("D://chart.pdf");
vb
sankeyDiagram.PrintOptions = New SankeyPrintOptions With {
    .SizeMode = DevExpress.Xpf.Charts.PrintSizeMode.ProportionalZoom
}
sankeyDiagram.ExportToPdf("D://chart.pdf")

Implements

IPrintableControl

Inheritance

Object DispatcherObject DependencyObject Visual UIElement FrameworkElement Control SankeyDiagramControl

See Also

SankeyDiagramControl Members

Sankey Diagram Control

DevExpress.Xpf.Charts.Sankey Namespace