Back to Devexpress

Provide Data for the Drill Mode

wpf-400717-controls-and-libraries-charts-suite-chart-control-provide-data-drill-down.md

latest13.8 KB
Original Source

Provide Data for the Drill Mode

  • May 28, 2021
  • 7 minutes to read

The Chart control provides the Drill Mode in which the control visualizes data hierarchies and allows end users to navigate between detail levels:

Data Source Requirements

The Chart control requires object aggregation and composition to be able to use these relationships to navigate between detail levels. For example, this topic uses the following object hierarchy to explain how the drill mode works:

csharp
public interface INameable {
    string Name { get; }
}

public sealed class DevAVBranch: INameable {
    public string Name { get; set; }
    public List<DevAVProductCategory> Categories { get; set; }
    public double TotalIncome { get => Categories.Sum(c => c.TotalIncome); }
}

public sealed class DevAVProductCategory: INameable {
    public string Name { get; set; }
    public List<DevAVProduct> Products { get; set; }
    public double TotalIncome { get => Products.Sum(p => p.TotalIncome); }
}

public sealed class DevAVProduct: INameable {
    public string Name { get; set; }
    public List<DevAVMonthlyIncome> Sales { get; set; }
    public double TotalIncome { get => Sales.Sum(s => s.Income); }
}

public class DevAVMonthlyIncome {
    public DateTime Month { get; set; }
    public double Income { get; set; }
}
vb
Public Interface INameable
    ReadOnly Property Name As String
End Interface

Public Class DevAVBranch
    Implements INameable

    Public Property Name As String Implements INameable.Name
    Public Property Categories As List(Of DevAVProductCategory)
    Public ReadOnly Property TotalIncome As Double
        Get
            Return Categories.Sum(Function(c) c.TotalIncome)
        End Get
    End Property
End Class

Public Class DevAVProductCategory
    Implements INameable

    Public Property Name As String Implements INameable.Name
    Public Property Products As List(Of DevAVProduct)
    Public ReadOnly Property TotalIncome As Double
        Get
            Return Products.Sum(Function(p) p.TotalIncome)
        End Get
    End Property
End Class

Public Class DevAVProduct
    Implements INameable

    Public Property Name As String Implements INameable.Name
    Public Property Sales As List(Of DevAVMonthlyIncome)
    Public ReadOnly Property TotalIncome As Double
        Get
            Return Sales.Sum(Function(s) s.Income)
        End Get
    End Property
End Class

Public Class DevAVMonthlyIncome
    Public Property Month As DateTime
    Public Property Income As Double
End Class
csharp
class MainViewModel {
    public IReadOnlyList<DevAVBranch> Branches { get; } 

    public MainViewModel() {
        Branches = new BranchDAO().GetBranches();
    }
}
vb
Class MainViewModel
    Private _branches As IReadOnlyList(Of DevAVBranch)
    Public ReadOnly Property Branches As IReadOnlyList(Of DevAVBranch)
        Get
            Return _branches
        End Get
    End Property

    Public Sub New()
        _branches = new BranchDAO().GetBranches();
    End Sub
End Class

Configure the Data Source

The Chart Control provides the Data Adapter to manage collections of objects that data provides on different drill down levels. The Adapter uses the Children Selector to get the next detail level collection:

xml
<Window.DataContext>
    <local:MainViewModel/>
</Window.DataContext>
<Window.Resources>
    <local:DevAVSeriesChildrenSelector x:Key="childrenSelector"/>
<Window.Resources>
<dxc:ChartControl>
    <dxc:XYDiagram2D>
        <dxc:XYDiagram.SeriesItemsSource>
            <dxc:HierarchicalDataAdapter 
                DataSource="{Binding Branches}"
                ChildrenSelector="{StaticResource childrenSelector}"/>
        </dxc:XYDiagram.SeriesItemsSource>
    </dxc:XYDiagram2D>
<!-- Other Chart Settings.-->
</dxc:ChartControl>
csharp
class DevAVSeriesChildrenSelector : IChildrenSelector {
    public IEnumerable SelectChildren(object item) {
        if (item as DevAVBranch branch) return new List<DevAVBranch> { branch };
        if (item as DevAVProductCategory category) return category.Products;
        if (item as DevAVProduct product) return new List<DevAVProduct> { product };
        return null;
    }
}
vb
Public Class DevAVSeriesChildrenSelector
    Implements IChildrenSelector

    Public Function SelectChildren(item As Object) As IEnumerable Implements IChildrenSelector.SelectChildren
        Dim branch = TryCast(item, DevAVBranch)
        If (branch IsNot Nothing) Then Return New List(Of DevAVBranch) From { branch }

        Dim category = TryCast(item, DevAVProductCategory)
        If (category IsNot Nothing) Then Return category.Products

        Dim product = TryCast(item, DevAVProduct)
        If (product IsNot Nothing) Then Return New List(Of DevAVProduct) From { product }

        Return Nothing
    End Function
End Class

The table below lists classes and properties included in the above-mentioned code:

|

Symbol

|

Description

| | --- | --- | |

Diagram.SeriesItemsSource

|

Gets or sets the collection of objects used to generate series.

| |

HierarchicalDataAdapter

|

The data adapter that uses data objects with aggregation and composition relationships as data sources for different detail levels when the Chart control is in the Drill Mode.

| |

HierarchicalDataAdapterBase.DataSource

|

Gets or set the collection of data source objects for the Chart in the Drill Mode.

| |

HierarchicalDataAdapter.ChildrenSelector

|

Gets or sets an object that returns a child object collection from a data object on whose representation the end user clicked.

|

Manage Data Appearance on Different Detail Levels

Use the SeriesItemTemplateSelector property to configure how the chart control shows data on different detail levels:

The following code snippet shows how to use this property.

xml
<Window.Resources>
    <!-- Other resources are here. -->
    <local:DevAVSeriesTemplateSelector
        x:Key="seriesTemplateSelector"
        AllCategoriesTemplate="{StaticResource stackedBarTemplate}"
        BranchCategoriesTemplate="{StaticResource barTemplate}"
        CategoryProductsTemplate="{StaticResource stackedSplineAreaTemplate}"
        ProductTemplate="{StaticResource splineTemplate}">
    </local:DevAVSeriesTemplateSelector>
<Window.Resources>
<dxc:ChartControl>
    <dxc:XYDiagram2D SeriesItemTemplateSelector="{StaticResource seriesTemplateSelector}">
        <!-- Other diagram settings. -->
    </dxc:XYDiagram2D>
<!-- Other Chart Settings. -->
</dxc:ChartControl>
csharp
class DevAVSeriesTemplateSelector : DataTemplateSelector {
    public DataTemplate AllCategoriesTemplate { get; set; }
    public DataTemplate BranchCategoriesTemplate { get; set; }
    public DataTemplate CategoryProductsTemplate { get; set; }
    public DataTemplate ProductTemplate { get; set; }

    public override DataTemplate SelectTemplate(object item, DependencyObject container) {
        Diagram diagram = (Diagram)container;
        if (item is DevAVBranch && diagram.BreadcrumbItems.Count == 1)
            return AllCategoriesTemplate;
        if (item is DevAVBranch)
            return BranchCategoriesTemplate;
        if (item is DevAVProduct && diagram.BreadcrumbItems[diagram.BreadcrumbItems.Count - 1].SourceObject is DevAVProduct)
            return ProductTemplate;
        if (item is DevAVProduct) 
            return CategoryProductsTemplate;
        return null;
    }
}
vb
Class DevAVSeriesTemplateSelector
    Inherits DataTemplateSelector

    Public Property AllCategoriesTemplate As DataTemplate
    Public Property BranchCategoriesTemplate As DataTemplate
    Public Property CategoryProductsTemplate As DataTemplate
    Public Property ProductTemplate As DataTemplate

    Public Overrides Function SelectTemplate(item As Object, container As DependencyObject) As DataTemplate
        Dim diagram = CType(container, Diagram)

        Dim branch = TryCast(item, DevAVBranch)
        If (branch IsNot Nothing) And (diagram.BreadcrumbItems.Count = 1) Then Return AllCategoriesTemplate
        If (branch IsNot Nothing) Then Return BranchCategoriesTemplate

        Dim product = TryCast(item, DevAVProduct)
        Dim lastLevelSourceProduct = TryCast(diagram.BreadcrumbItems(diagram.BreadcrumbItems.Count - 1).SourceObject, DevAVProduct)
        If (product IsNot Nothing) And (lastLevelSourceProduct IsNot Nothing) Then Return ProductTemplate
        If (product IsNot Nothing) Then Return CategoryProductsTemplate

        Return Nothing
    End Function
End Class

Customize Text the Chart Displays in the Breadcrumb Panel

The Data Adapter provides the BreadcrumbTextProvider property that manages text the chart shows for detail levels in the breadcrumb panel:

The following code snippet shows how to use this property.

xml
<Window.Resources>
    <!-- Other resources. -->
    <local:DevAVBreadcrumbTextProvider x:Key="textProvider"/>
</Window.Resources>
<!-- Several tags are skipped. -->

<dxc:XYDiagram2D.SeriesItemsSource>
    <dxc:HierarchicalDataAdapter DataSource="{Binding Branches}"
                                 ChildrenSelector="{StaticResource childrenSelector}"
                                 BreadcrumbTextProvider="{StaticResource textProvider}"/>
    </dxc:XYDiagram2D.SeriesItemsSource>
<dxc:XYDiagram2D.SeriesItemsSource>
csharp
class DevAVBreadcrumbTextProvider : IBreadcrumbTextProvider {
    public string GetText(object seriesSourceObj, object pointSourceObj) {
        StringBuilder sb = new StringBuilder("(");
        if (seriesSourceObj != null) sb.Append(GetName(seriesSourceObj));
        if (seriesSourceObj != null && pointSourceObj != null) sb.Append(", ");
        if (pointSourceObj != null) sb.Append(GetName(pointSourceObj));
        sb.Append(")");
        return sb.ToString();
    }

    private string GetName(object obj) {
        if (obj is INameable nameable) return nameable.Name;
        return String.Empty;
    }
}
vb
Public Class DevAVBreadcrumbTextProvider
    Implements IBreadcrumbTextProvider
    Public Function GetText(seriesSourceObj As Object, pointSourceObj As Object) As String Implements IBreadcrumbTextProvider.GetText
        Dim sb = New StringBuilder("(")
        If (seriesSourceObj IsNot Nothing) Then sb.Append(GetName(seriesSourceObj))
        If (seriesSourceObj IsNot Nothing And pointSourceObj IsNot Nothing) Then sb.Append(", ")
        If (pointSourceObj IsNot Nothing) Then sb.Append(GetName(pointSourceObj))
        sb.Append(")")
        Return sb.ToString()
    End Function

    Private Function GetName(obj As Object) As String
        Dim nameable = TryCast(obj, INameable)
        If (nameable IsNot Nothing) Then Return nameable.Name
        Return String.Empty
    End Function
End Class

The code above uses the following classes and properties:

|

Symbol

|

Description

| | --- | --- | |

HierarchicalDataAdapterBase.BreadcrumbTextProvider

|

Gets or sets the object that provides text for breadcrumb items when the Chart is in the Drill Mode.

| |

IBreadcrumbTextProvider

|

An interface of a text provider for the HierarchicalDataAdapter.

|

Handle Changes of Detail Levels

Chart control diagrams provide the DrillDownStateChanged event whose handler can customize the Chart when the current detail level changes:

xml
<dxc:ChartControl DataContext="{Binding Branches}">
    <dxc:XYDiagram2D 
        x:Name="diagram" 
        SeriesItemTemplateSelector="{StaticResource seriesTemplateSelector}" 
        DrillDownStateChanged="XYDiagram2D_DrillDownStateChanged">
        <!-- Other Diagram settings. -->
    </dxc:XYDiagram>
    <!-- Other Chart settings. -->
</dxc:ChartControl>
csharp
private void OnDrillDownStateChanged(object sender, DrillDownStateChangedEventArgs e) {
    diagram.Rotated = e.BreadcrumbItems.Last().IsHome;
}
vb
Private Sub Diagram_DrillDownStateChanged(sender As Object, e As DrillDownStateChangedEventArgs)
    diagram.Rotated = e.BreadcrumbItems.Last().IsHome
End Sub