# Clustering

Many applications operate with collections of items which are placed close to others and it's uncomfortable to use them as separate objects for low zoom levels. The **VisualizationLayer** supports the **Clustering** feature. It allows grouping of items according to condition.

## Using of the clustering

The **VisualizationLayer** exposes the following properties which are related to the **Clustering** feature:

* **ClusterGenerator**: Gets or sets an instance of the class which implements the **IClusterGenerator** interface. It can be used when you want to define your own grouping algorithm. As default it uses the instance of the built-in **DefaultClusterGenerator** class.

* **ClusteringEnabled**: Gets or sets a **boolean** value which indicates whether the clustering is enabled.

* **ClusteringEnabledThreshold**: A property of type **int** that gets or sets clustering enabled threshold. This value sets the max zoom level for clustering. If current zoom level of the control is greater than this value then clustering will be disabled.

> tip You should set the **AutoCalculateClusteringThreshold** to **False** to allow the RadMap to respect the **ClusteringEnabledThreshold**. The default value of this property is **True**. This means that you need to explicitly set it to false in all cases when you need to predefine the clustering threshold.

* **ClusteringEnabledThresholdMinItems**: A property of type **int** that gets or sets the minimum number of items in the cluster for auto calculated clustering threshold. The clustering threshold will be set if number of the items in every cluster is less than this value.

* **ClusterItemTemplate**: Gets or sets the data template which represents the cluster item.

* **ClusterTemplateSelector**: Gets or sets the custom logic for choosing a template used to display each cluster.

* **DefaultClusterTemplate**: Gets or sets the default cluster data template. It’s used when the **ClusterTemplateSelector** and **ClusterItemTemplate** properties are not specified.

* **GenerateClustersOnZoom**: A property of type **bool** that gets or sets value which indicates whether clusters should be re-generated when the zoom level is changed. The default value of this property is **true**.

In order to enable the **Clustering** feature you can specify just the **ClusteringEnabled** property to **true**. Also you can configure the **ItemTemplate** and **ClusterItemTemplate** properties for customizing the appearance of the item and of the cluster.

More information about using of the **ItemTemplate** property you can find in the [Data Binding](https://www.telerik.com/products/wpf/documentation/controls/radmap/features/visualization-layer/data-binding) article.

## ClustersInvalidated Event

To get notified when the cluster elements are created, the **ClustersInvalidated** event of the VisualizationLayer can be utilized.

**Example 1: Subscribe to ClustersInvalidated event**

```XAML
<telerik:VisualizationLayer x:Name="VisualizationLayer1" ClustersInvalidated="VisualizationLayer_ClustersInvalidated"/>
```

**Example 2: ClustersInvalidated event handler**

```C#
private void VisualizationLayer_ClustersInvalidated(object sender, System.EventArgs e)
{
    // custom logic		
}
```

## Items Grouping Example

The following example demonstrates how to use the **Clustering** feature to display the airports of USA. The example displays the point type data which can be found in the “airports” shape-file of our [Virtualization and Grouping](https://demos.telerik.com/wpf/#Map/ClusterVirtualization) demo. The file contains 940 airports.

If you try to load this file using the shape-file reader directly, like the sample XAML code below, then the performance of zooming and panning will be slow. Also the data items will be overlapped for low zoom levels (1-7).

**Example 3: Displaying Airports of USA**

```XAML
<UserControl 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:telerik="http://schemas.telerik.com/2008/xaml/presentation"
         d:DesignHeight="300"
         d:DesignWidth="400"
         mc:Ignorable="d">
    <Grid x:Name="LayoutRoot">
        <telerik:RadMap x:Name="RadMap1"
                    Center="40, -100"
                    ZoomLevel="3">
            <telerik:RadMap.Provider>
                <telerik:OpenStreetMapProvider />
            </telerik:RadMap.Provider>
            <telerik:VisualizationLayer>
                <telerik:VisualizationLayer.Reader>
                    <telerik:AsyncShapeFileReader Source="/ItemsGroupingExample;component/Resources/airports.shp" ToolTipFormat="AIRPT_NAME" />
                </telerik:VisualizationLayer.Reader>
            </telerik:VisualizationLayer>
        </telerik:RadMap>
    </Grid>
</UserControl>
```

Here is a snapshot of the displayed items for zoom level 3:

![Rad Map Features Virtualization Layer Clustering](https://www.telerik.com/products/wpf/documentation/assets/0b252fe51805dd9db3388bd79152d607/radmap_features_virtualizationlayer_clustering.png)

Such performance is a reason to use the **Clustering** feature to improve the performance of rendering. Also, it allows to avoid displaying the overlapped objects which are placed close to others.

You can set the **ClusteringEnabled** property to **true** for enabling the feature.

**Example 4: Enable clustering functionality**

```XAML
<telerik:VisualizationLayer x:Name="VisualizationLayer2" ClusteringEnabled="True">
    <telerik:VisualizationLayer.Reader>
        <telerik:AsyncShapeFileReader Source="/ItemsGroupingExample;component/Resources/airports.shp" ToolTipFormat="AIRPT_NAME" />
    </telerik:VisualizationLayer.Reader>
</telerik:VisualizationLayer>
```

Here is a snapshot of the displayed items for zoom level 3 when the grouping is used:

![Rad Map Features Virtualization Layer Clustering 1](https://www.telerik.com/products/wpf/documentation/assets/cc84800251c1194db7b74062102007df/radmap_features_virtualizationlayer_clustering_1.png)

By default the cluster looks like above and it shows how many items it contains.

You can configure the **ItemTemplate** and **ClusterItemTemplate** properties to customize the appearance of the item and of the cluster.

**Example 5: Customizing the appearance of the cluster**

```XAML
<UserControl 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:telerik="http://schemas.telerik.com/2008/xaml/presentation"
         d:DesignHeight="300"
         d:DesignWidth="400"
         mc:Ignorable="d">
    <UserControl.Resources>
        <DataTemplate x:Key="ItemTemplate">
            <Border>
                <telerik:MapLayer.HotSpot>
                    <telerik:HotSpot ElementName="Pin"
                                 X="0.5"
                                 Y="1.0" />
                </telerik:MapLayer.HotSpot>
                <Canvas x:Name="Pin"
                    Width="20"
                    Height="32">
                    <Path Width="20.01"
                      Height="32.005"
                      Data="M1054.5088,458.105L1065.5188,458.105C1067.7278,458.105,1069.5188,459.896,1069.5188,462.105L1069.5188,473.084C1069.5188,475.293,1067.7278,477.084,1065.5188,477.084C1065.5188,477.084,1062.6868,476.831,1062.2128,479.103C1061.6608,481.751,1060.2208,489.11,1060.2208,489.11L1059.3548,489.11C1059.3548,489.11,1058.0138,482.546,1057.2888,479.106C1056.8538,477.041,1054.5088,477.084,1054.5088,477.084C1052.2998,477.084,1050.5088,475.293,1050.5088,473.084L1050.5088,462.105C1050.5088,459.896,1052.2998,458.105,1054.5088,458.105z"
                      Fill="Yellow"
                      Stretch="Fill"
                      Stroke="Black"
                      StrokeEndLineCap="Flat"
                      StrokeLineJoin="Miter"
                      StrokeMiterLimit="10"
                      StrokeStartLineCap="Flat"
                      StrokeThickness="1" />
                    <Path Canvas.Left="3.188"
                      Canvas.Top="2.989"
                      Width="13.42"
                      Height="14.219"
                      Data="M1066.6162,472.8125C1066.6212,473.9125,1065.7252,474.8125,1064.6252,474.8125L1055.2502,474.8125C1054.1502,474.8125,1053.2462,473.9125,1053.2412,472.8125L1053.1962,462.5935C1053.1912,461.4935,1054.0872,460.5935,1055.1872,460.5935L1064.5622,460.5935C1065.6622,460.5935,1066.5662,461.4935,1066.5712,462.5935z"
                      Fill="Black"
                      Stretch="Fill" />
                </Canvas>
            </Border>
        </DataTemplate>

        <DataTemplate x:Key="ClusterTemplate">
            <Border Background="#af3f3f3f"
                CornerRadius="3"
                telerik:MapLayer.HotSpot="0.5,0.5">
                <StackPanel x:Name="Text" Margin="3">
                    <TextBlock HorizontalAlignment="Center"
                           Foreground="Yellow"
                           Text="{Binding Path=Count}" />
                </StackPanel>
            </Border>
        </DataTemplate>
    </UserControl.Resources>

    <Grid x:Name="LayoutRoot1">
        <telerik:RadMap x:Name="RadMap3"
                    Center="40, -100"
                    ZoomLevel="3">
            <telerik:RadMap.Provider>
                <telerik:OpenStreetMapProvider />
            </telerik:RadMap.Provider>
            <telerik:VisualizationLayer x:Name="VisualizationLayer3"
                                    ClusterItemTemplate="{StaticResource ClusterTemplate}"
                                    ClusteringEnabled="True"
                                    ItemTemplate="{StaticResource ItemTemplate}">
                <telerik:VisualizationLayer.Reader>
                    <telerik:AsyncShapeFileReader Source="/ItemsGroupingExample;component/Resources/airports.shp" ToolTipFormat="AIRPT_NAME" />
                </telerik:VisualizationLayer.Reader>
            </telerik:VisualizationLayer>
        </telerik:RadMap>
    </Grid>
</UserControl>
```

There are the results of using custom templates for clusters and items:
![Rad Map Features Virtualization Layer Clustering 2](https://www.telerik.com/products/wpf/documentation/assets/aeb1216e0525a333d15f630e9376521a/radmap_features_virtualizationlayer_clustering_2.png)
![Rad Map Features Virtualization Layer Clustering 3](https://www.telerik.com/products/wpf/documentation/assets/7457b202565b8c507650e2b7469f9dc6/radmap_features_virtualizationlayer_clustering_3.png)

## DefaultClusterGenerator class

The VisualizationLayer has an **IClusterGenerator** instance member which is responsible for the clustering behavior. By default it uses an instance of the built-in **DefaultClusterGenerator class**. This class can be used to customize the default grouping algorithm. The **DefaultClusterGenerator class** exposes the following properties:

* **AutoExpandWhenSingle:** A property of type **Boolean** that gets or sets the value which indicates whether cluster should be automatically expanded when it contains one data item only.
* **CloseDistance:** A property of type **double\[]** that gets or sets an array of the doubles which every item define the max distance between the cluster center and the item for the corresponding ZoomLevel.
* **DeleteWhenEmpty:** A property of type **Boolean** that gets or sets the value which indicates whether cluster should be deleted if it is empty.
* **ExpandRadius:** A property of type **double** that gets or sets the radius (in pixels) which will be used to calculate coordinates of the items when a cluster is expanded to the polygon vertices.
* **HideExpanded:** A property of type **Boolean** that gets or sets whether expanded cluster should be hidden or not.
* **Layer:** A property of type **VisualizationLayer** that gets or sets layer which the current map belongs to.

The **DefaultClusterGenerator** class also provides several methods which can be overridden in order to further customize the clustering algorithm.

* **CreateCluster(Location center, object item):** This method creates a cluster at the specified location.
* **IsItemInClusterRegion(ClusterData cluster, MapObjectInfo info, int zoomLevel):** This method checks if a data item belongs to a given cluster.
* **RegenerationNeeded(ClusterData cluster, int zoomLevel)**: This method can be overridden so you can customize if given cluster should be regenerated at a given zoom level. The default value is **true**.

## The ClusterData object

The **VisualizationLayer** uses the **ClusterData** object to visualize the cluster. The **ClusterData** exposes the following properties which can be used in applications:

* **AutoCalculateBounds**: Gets or sets value which indicates whether bounding box of the cluster should be calculated automatically.

* **AutoExpandWhenSingle**: Gets or sets value which indicates whether cluster should be automatically expanded when it contains 1 data item only.

* **Bounds**: Gets or sets bounding box of the cluster.

* **Children**: Gets data items which belong to this cluster.

* **ClusterState**: A property of type **ClusterState** that gets or sets state of the cluster. It can be **Collapsed**, **Expanded** and **ExpandedToPolygon**. The difference between expanded cluster and cluster expanded to polygon is as following: the expanded cluster uses original locations of the items to show them, the cluster which is expanded to polygon uses vertices of the regular polygon with center at the cluster’s center to show items.

* **Count**: Gets number of the items in the cluster.

* **Data**: Gets or sets additional cluster's data. This data can be used to pass extra information to the cluster's data template or by cluster generator when detecting belonging of the item to the particular cluster.

* **DeleteWhenEmpty**: Gets or sets value which indicates whether cluster should be deleted if it is empty.

* **ExpandRadius**: Gets or sets radius (in pixels) which will be used to calculate coordinates of the items when cluster is expanded to the polygon vertices.

* **HideExpanded**: Gets or sets value which indicates whether the expanded cluster should be hidden.

* **Hotspot**: Gets or sets hotspot of the cluster.

* **Location**: Gets or sets center of the cluster.

* **ZIndex**: Gets or sets the Z-Index of the cluster.

Many of these properties are used by the cluster generator, only. It calculates them automatically during the process of generating clusters. Usually, applications use the **ClusterState** property to expand the cluster’s items. The following example demonstrates how this can be achieved.

## Expand Cluster Example

The **ClusterState** property allows displaying the items of cluster without increasing of the zoom level. You can attach the mouse events in the cluster template like the XAML code below.

**Example 6: Subscribing to MouseLeftButtonDown and MouseRightButtonDown events**

```XAML
<DataTemplate x:Key="ClusterTemplate">
    <Border Background="#af3f3f3f"
        CornerRadius="3"
        MouseLeftButtonDown="ExpandClusterToPolygon"
        MouseRightButtonDown="ExpandCluster"
        telerik:MapLayer.HotSpot="0.5,0.5">
        <StackPanel x:Name="Text" Margin="3">
            <TextBlock HorizontalAlignment="Center"
                   Foreground="Yellow"
                   Text="{Binding Path=Count}" />
        </StackPanel>
    </Border>
</DataTemplate>
```

The following *MouseLeftButtonDown* event handler uses to expand the items of cluster to the polygon vertices:

**Example 8: MouseLeftButtonDown event handler**

```C#
private void ExpandClusterToPolygon(object sender, MouseButtonEventArgs e)
{
    FrameworkElement element = sender as FrameworkElement;
    if (element != null)
    {
        ClusterData data = element.DataContext as ClusterData;
        if (data != null)
        {
            if (data.ClusterState != ClusterState.ExpandedToPolygon)
            {
                data.HideExpanded = false;
                data.ClusterState = ClusterState.ExpandedToPolygon;
            }
            else
            {
                data.ClusterState = ClusterState.Collapsed;
            }
        }
    }

    e.Handled = true;
}
```

```VB.NET
Private Sub ExpandClusterToPolygon(sender As Object, e As MouseButtonEventArgs)
    Dim element As FrameworkElement = TryCast(sender, FrameworkElement)
    If element IsNot Nothing Then
        Dim data As ClusterData = TryCast(element.DataContext, ClusterData)
        If data IsNot Nothing Then
            If data.ClusterState <> ClusterState.ExpandedToPolygon Then
                data.HideExpanded = False
                data.ClusterState = ClusterState.ExpandedToPolygon
            Else
                data.ClusterState = ClusterState.Collapsed
            End If
        End If
    End If

    e.Handled = True
End Sub
```

The following *MouseRightButtonDown* event handler uses to expand the items of cluster to their original locations:

**Example 9: MouseRightButtonDown event handler**

```C#
private void ExpandCluster(object sender, MouseButtonEventArgs e)
{
    FrameworkElement element = sender as FrameworkElement;
    if (element != null)
    {
        ClusterData data = element.DataContext as ClusterData;
        if (data != null)
        {
            if (data.ClusterState != ClusterState.Expanded)
            {
                data.HideExpanded = false;
                data.ClusterState = ClusterState.Expanded;
            }
            else
            {
                data.ClusterState = ClusterState.Collapsed;
            }
        }
    }

    e.Handled = true;
}
```

```VB.NET
Private Sub ExpandCluster(sender As Object, e As MouseButtonEventArgs)
    Dim element As FrameworkElement = TryCast(sender, FrameworkElement)
    If element IsNot Nothing Then
        Dim data As ClusterData = TryCast(element.DataContext, ClusterData)
        If data IsNot Nothing Then
            If data.ClusterState <> ClusterState.Expanded Then
                data.HideExpanded = False
                data.ClusterState = ClusterState.Expanded
            Else
                data.ClusterState = ClusterState.Collapsed
            End If
        End If
    End If

    e.Handled = True
End Sub
```

In the snapshots below you can see how the above example will be displayed using different **ClusterState** values. The **ClusterState** of cluster which contains six items is **Collapsed**.
![Rad Map Features Virtualization Layer Clustering 4](https://www.telerik.com/products/wpf/documentation/assets/7db4b7c6a5871fcbe7b0cb6ed2c2b52f/radmap_features_virtualizationlayer_clustering_4.png)

The **ClusterState** value is **ExpandedToPolygon**.
![Rad Map Features Virtualization Layer Clustering 5](https://www.telerik.com/products/wpf/documentation/assets/fcfbccb7d233d5784e4a6b3842a94a02/radmap_features_virtualizationlayer_clustering_5.png)

The **ClusterState** value is **Expanded**.
![Rad Map Features Virtualization Layer Clustering 6](https://www.telerik.com/products/wpf/documentation/assets/09b2a5945c238174af2c2bd971663942/radmap_features_virtualizationlayer_clustering_6.png)

## See Also

* [Reading Map Shapes](https://www.telerik.com/products/wpf/documentation/controls/radmap/features/visualization-layer/reading-map-shapes)
