Back to Devexpress

How to: Implement a Custom Layout Algorithm

wpf-115014-controls-and-libraries-treemap-control-layout-algorithms-examples-how-to-implement-a-custom-layout-algorithm.md

latest10.4 KB
Original Source

How to: Implement a Custom Layout Algorithm

  • Jun 07, 2019
  • 4 minutes to read

To implement a custom layout algorithm, design a class inheriting the abstract TreeMapLayoutAlgorithm class and implement its TreeMapLayoutAlgorithmBase.Calculate method, which will calculate item layouts using a custom algorithm.

In this example, items are arranged depending on the empty space width/height ratio.

vb
Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text
Imports System.Threading.Tasks
Imports System.Windows
Imports System.Windows.Controls
Imports System.Windows.Data
Imports System.Windows.Documents
Imports System.Windows.Input
Imports System.Windows.Media
Imports System.Windows.Media.Imaging
Imports System.Windows.Navigation
Imports System.Windows.Shapes

Namespace CustomLayoutAlgorithmSample
    ''' <summary>
    ''' Interaction logic for MainWindow.xaml
    ''' </summary>
    Partial Public Class MainWindow
        Inherits Window

        Public Sub New()
            InitializeComponent()
        End Sub
    End Class
End Namespace
xaml
<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:CustomLayoutAlgorithmSample"
        xmlns:dxtm="http://schemas.devexpress.com/winfx/2008/xaml/treemap"
        x:Class="CustomLayoutAlgorithmSample.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <!--region #LayoutAlgorithm-->
        <dxtm:TreeMapControl>
            <dxtm:TreeMapControl.LayoutAlgorithm>
                <local:CustomSliceAndDiceLayoutAlgorithm/>
            </dxtm:TreeMapControl.LayoutAlgorithm>
            <!--endregion #LayoutAlgorithm-->
            <dxtm:TreeMapControl.Colorizer>
                <dxtm:TreeMapPaletteColorizer/>
            </dxtm:TreeMapControl.Colorizer>
            <dxtm:TreeMapItemStorage>
                    <dxtm:TreeMapItem Label="United States"
                                      Value="11384763"/>
                    <dxtm:TreeMapItem Label="Brazil"
                                      Value="1799612"/>
                    <dxtm:TreeMapItem Label="Canada"
                                      Value="1572781"/>
                    <dxtm:TreeMapItem Label="Germany"
                                      Value="3371003"/>
                    <dxtm:TreeMapItem Label="United Kingdom"
                                      Value="2582021"/>
                    <dxtm:TreeMapItem Label="France"
                                      Value="2422649"/>
                    <dxtm:TreeMapItem Label="Italy"
                                      Value="1809047"/>
                    <dxtm:TreeMapItem Label="China"
                                      Value="17968195"/>
                    <dxtm:TreeMapItem Label="Japan"
                                      Value="4116242"/>
                    <dxtm:TreeMapItem Label="India"
                                      Value="2864903"/>
            </dxtm:TreeMapItemStorage>
            <!--region #CloseTag-->
            <!-- ... -->
        </dxtm:TreeMapControl>
        <!--endregion #CloseTag-->
    </Grid>
</Window>
vb
#Region "#CustomLayoutAlgorithmImpl"
Imports DevExpress.Xpf.TreeMap
Imports System.Collections.Generic
Imports System.Linq
Imports System.Windows
Imports System

Namespace CustomLayoutAlgorithmSample
    Friend Class CustomSliceAndDiceLayoutAlgorithm
        Inherits TreeMapLayoutAlgorithmBase
        Implements IComparer(Of ITreeMapLayoutItem)

        ' Cut the slice depending on the non-filled space width/height ratio.
        Public Overrides Sub Calculate(ByVal items As IList(Of ITreeMapLayoutItem), ByVal size As Size, ByVal groupLevel As Integer)
            Dim unlayoutedItemsWeight As Double = 0
            For Each item In items
                unlayoutedItemsWeight += item.Weight
            Next item

            Dim sortedItems = items.ToList()
            sortedItems.Sort(Me)

            Dim emptySpace As New Rect(0, 0, size.Width, size.Height)
            For Each item In sortedItems
                Dim itemWidth As Double
                Dim itemHeight As Double

                Dim newEmptySpaceX As Double
                Dim newEmptySpaceY As Double
                Dim newEmptySpaceWidth As Double
                Dim newEmptySpaceHeight As Double

                If emptySpace.Width / emptySpace.Height > 1.0 Then
                    itemWidth = emptySpace.Width * item.Weight / unlayoutedItemsWeight
                    itemHeight = emptySpace.Height

                    newEmptySpaceX = emptySpace.X + itemWidth
                    newEmptySpaceY = emptySpace.Y
                    newEmptySpaceHeight = emptySpace.Height

                    newEmptySpaceWidth = emptySpace.Width - itemWidth
                    newEmptySpaceWidth = If(newEmptySpaceWidth < 0, 0, newEmptySpaceWidth)
                Else
                    itemWidth = emptySpace.Width
                    itemHeight = emptySpace.Height * item.Weight / unlayoutedItemsWeight

                    newEmptySpaceX = emptySpace.X
                    newEmptySpaceY = emptySpace.Y + itemHeight
                    newEmptySpaceWidth = emptySpace.Width

                    newEmptySpaceHeight = emptySpace.Height - itemHeight
                    newEmptySpaceHeight = If(newEmptySpaceHeight < 0, 0, newEmptySpaceHeight)
                End If
                item.Layout = New Rect(emptySpace.X, emptySpace.Y, itemWidth, itemHeight)
                emptySpace = New Rect(newEmptySpaceX, newEmptySpaceY, newEmptySpaceWidth, newEmptySpaceHeight)
                unlayoutedItemsWeight -= item.Weight
            Next item
        End Sub

        Public Function Compare(ByVal x As ITreeMapLayoutItem, ByVal y As ITreeMapLayoutItem) As Integer Implements IComparer(Of ITreeMapLayoutItem).Compare
            If x.Weight > y.Weight Then
                Return -1
            ElseIf x.Weight < y.Weight Then
                Return 1
            Else
                Return 0
            End If
        End Function

        Protected Overrides Function CreateObject() As TreeMapDependencyObject
            Return New CustomSliceAndDiceLayoutAlgorithm()
        End Function
    End Class
End Namespace
#End Region ' #CustomLayoutAlgorithmImpl
csharp
#region #CustomLayoutAlgorithmImpl
using DevExpress.Xpf.TreeMap;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System;

namespace CustomLayoutAlgorithmSample {
    class CustomSliceAndDiceLayoutAlgorithm : TreeMapLayoutAlgorithmBase, IComparer<ITreeMapLayoutItem> {
        // Cut the slice depending on the non-filled space width/height ratio.
        public override void Calculate(IList<ITreeMapLayoutItem> items, Size size, int groupLevel) {
            double unlayoutedItemsWeight = 0;
            foreach (var item in items)
                unlayoutedItemsWeight += item.Weight;

            var sortedItems = items.ToList();
            sortedItems.Sort(this);

            Rect emptySpace = new Rect(0, 0, size.Width, size.Height);
            foreach (var item in sortedItems) {
                double itemWidth;
                double itemHeight;

                double newEmptySpaceX;
                double newEmptySpaceY;
                double newEmptySpaceWidth;
                double newEmptySpaceHeight;

                if (emptySpace.Width / emptySpace.Height > 1) {
                    itemWidth = emptySpace.Width * item.Weight / unlayoutedItemsWeight;
                    itemHeight = emptySpace.Height;

                    newEmptySpaceX = emptySpace.X + itemWidth;
                    newEmptySpaceY = emptySpace.Y;
                    newEmptySpaceHeight = emptySpace.Height;

                    newEmptySpaceWidth = emptySpace.Width - itemWidth;
                    newEmptySpaceWidth = newEmptySpaceWidth < 0 ? 0 : newEmptySpaceWidth;
                }
                else {
                    itemWidth = emptySpace.Width;
                    itemHeight = emptySpace.Height * item.Weight / unlayoutedItemsWeight;

                    newEmptySpaceX = emptySpace.X;
                    newEmptySpaceY = emptySpace.Y + itemHeight;
                    newEmptySpaceWidth = emptySpace.Width;

                    newEmptySpaceHeight = emptySpace.Height - itemHeight;
                    newEmptySpaceHeight = newEmptySpaceHeight < 0 ? 0 : newEmptySpaceHeight;
                }
                item.Layout = new Rect(emptySpace.X, emptySpace.Y, itemWidth, itemHeight);
                emptySpace = new Rect(
                    newEmptySpaceX,
                    newEmptySpaceY,
                    newEmptySpaceWidth,
                    newEmptySpaceHeight);
                unlayoutedItemsWeight -= item.Weight;
            }
        }

        public int Compare(ITreeMapLayoutItem x, ITreeMapLayoutItem y) {
            if (x.Weight > y.Weight) return -1;
            else if (x.Weight < y.Weight) return 1;
            else return 0;
        }

        protected override TreeMapDependencyObject CreateObject() {
            return new CustomSliceAndDiceLayoutAlgorithm();
        }
    }
}
#endregion #CustomLayoutAlgorithmImpl
csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace CustomLayoutAlgorithmSample {
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window {
        public MainWindow() {
            InitializeComponent();
        }
    }
}