How to: Implement a Custom Layout Algorithm
- 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.
- MainWindow.xaml.vb
- MainWindow.xaml
- SliceAndDiceLayoutAlgorithm.vb
- SliceAndDiceLayoutAlgorithm.cs
- MainWindow.xaml.cs
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
<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>
#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
#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
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();
}
}
}