Skip to content

ClassBreakStyle Guide

Namespace: ThinkGeo.Core
Applies to: All ThinkGeo products (WPF, WinForms, Blazor, MAUI, GIS Server, etc.)


Overview

ClassBreakStyle renders features using different styles depending on where a feature's numeric attribute value falls within a set of defined ranges. Each range — called a ClassBreak — specifies a lower threshold value and the style to apply when a feature's attribute is greater than or equal to that threshold. The style of the last matching break (the highest break that is still ≤ the feature's value) wins.

ClassBreakStyle is the standard approach for choropleth maps (polygon fill intensity driven by census or statistical data), graduated point symbol maps (marker size or color driven by a measured value at each point), and any other layer where you want continuous numeric data to drive discrete visual categories.


Constructors

There are two forms in the samples:

Constructor with column name

Passes the column name directly at construction time — the most concise form:

var classBreakStyle = new ClassBreakStyle("H_UNITS");
var classBreakStyle = new ClassBreakStyle("PH");
var classBreakStyle = new ClassBreakStyle("CellValue");

The string argument is the exact name of the feature attribute column whose value will be evaluated at render time.

Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnClassBreaks.xaml.cs, wpfHowDoI:Samples/MapOfflineData/Vector/GenerateESRIGridFile.xaml.cs, blazorHowDoI:Shared/ThematicDemographicStyleBuilder.cs.

Default constructor + ColumnName property

Constructs an empty style and sets the column name as a separate step. Used when the column name is determined dynamically or when constructing inside a loop:

var classBreakStyle = new ClassBreakStyle();
classBreakStyle.ColumnName = "H_UNITS";

Sources: wpfHowDoI:Samples/ThinkGeoCloudIntegration/ColorUtilities.xaml.cs, winformHowDoI:Samples/ThinkGeoCloudIntegration/ColorUtilities.cs, mauiHowDoI:Samples/ThinkGeoCloudIntegration/ColorUtilitiesCloudServices.xaml.cs.


Properties

ColumnName

string — The feature attribute column whose value determines which ClassBreak is applied. Must match the exact column name from the layer's data source (case-sensitive). Set in the constructor or directly on the style object.

ClassBreaks

Collection<ClassBreak> — The ordered list of break definitions. Each ClassBreak holds a threshold Value and a style to apply when the feature's column value is ≥ that threshold. Add breaks in ascending order by Value. The collection is mutable and populated via .Add().

BreakValueInclusion

BreakValueInclusion enum — Controls whether the break value itself is included in the range above or below the break. The only observed value in the samples is BreakValueInclusion.IncludeValue, set in the Blazor thematic demographic builder when computing cluster breaks from actual data:

ClassBreakStyle classBreakStyle = new ClassBreakStyle(SelectedColumns[0])
{
    BreakValueInclusion = BreakValueInclusion.IncludeValue
};

Source: blazorHowDoI:Shared/ThematicDemographicStyleBuilder.cs.


The ClassBreak Object

Each entry in ClassBreaks is a ClassBreak instance. Its two-argument constructor takes a double threshold value and a Style:

new ClassBreak(thresholdValue, style)

The style argument can be any concrete style type — AreaStyle, PointStyle, LineStyle, or any other style that inherits from Style.

After construction, the two most-used read-back properties on ClassBreak are:

  • Value — the double threshold used for legend label formatting (e.g., $">{classBreak.Value} units")
  • DefaultAreaStyle — the style cast back as an AreaStyle, used when wiring up LegendItem.ImageStyle

Geometry Type Patterns

Polygon layers — AreaStyle

The dominant use case. Each break assigns a different fill color to polygon features:

var housingUnitsStyle = new ClassBreakStyle("H_UNITS");

var classBreakIntervals = new double[] { 0, 1000, 2000, 3000, 4000, 5000 };
var colors = GeoColor.GetColorsInHueFamily(GeoColors.Red, classBreakIntervals.Length).Reverse().ToList();

for (var i = 0; i < classBreakIntervals.Length; i++)
{
    var classBreak = new ClassBreak(
        classBreakIntervals[i],
        AreaStyle.CreateSimpleAreaStyle(new GeoColor(192, colors[i]), GeoColors.White));

    housingUnitsStyle.ClassBreaks.Add(classBreak);
}

layer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(housingUnitsStyle);
layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnClassBreaks.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnClassBreaks.cs, mauiHowDoI:Samples/VectorDataStyling/CreateClassBreakStyle.xaml.cs.

Point layers — PointStyle

Each break assigns a different marker color to point features. The first break uses double.MinValue as the sentinel, which matches every feature not covered by any subsequent break — used here to assign a transparent style to values below the lowest explicit threshold:

var classBreakStyle = new ClassBreakStyle("PH");
const byte alpha = 180;
const int symbolSize = 10;

// Sentinel break — catches anything below 6.2 and makes it transparent
classBreakStyle.ClassBreaks.Add(new ClassBreak(double.MinValue,
    new PointStyle(PointSymbolType.Circle, symbolSize, new GeoSolidBrush(GeoColors.Transparent))));

// Color-coded breaks by pH value
classBreakStyle.ClassBreaks.Add(new ClassBreak(6.2,
    new PointStyle(PointSymbolType.Circle, symbolSize, new GeoSolidBrush(GeoColor.FromArgb(alpha, 255, 0, 0)))));
classBreakStyle.ClassBreaks.Add(new ClassBreak(6.83,
    new PointStyle(PointSymbolType.Circle, symbolSize, new GeoSolidBrush(GeoColor.FromArgb(alpha, 255, 128, 0)))));
classBreakStyle.ClassBreaks.Add(new ClassBreak(7.0,
    new PointStyle(PointSymbolType.Circle, symbolSize, new GeoSolidBrush(GeoColor.FromArgb(alpha, 245, 210, 10)))));
classBreakStyle.ClassBreaks.Add(new ClassBreak(7.08,
    new PointStyle(PointSymbolType.Circle, symbolSize, new GeoSolidBrush(GeoColor.FromArgb(alpha, 225, 255, 0)))));
classBreakStyle.ClassBreaks.Add(new ClassBreak(7.15,
    new PointStyle(PointSymbolType.Circle, symbolSize, new GeoSolidBrush(GeoColor.FromArgb(alpha, 224, 251, 132)))));
classBreakStyle.ClassBreaks.Add(new ClassBreak(7.21,
    new PointStyle(PointSymbolType.Circle, symbolSize, new GeoSolidBrush(GeoColor.FromArgb(alpha, 128, 255, 128)))));
classBreakStyle.ClassBreaks.Add(new ClassBreak(7.54,
    new PointStyle(PointSymbolType.Circle, symbolSize, new GeoSolidBrush(GeoColor.FromArgb(alpha, 0, 255, 0)))));

samplesLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(classBreakStyle);
samplesLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Sources: wpfHowDoI:Samples/MapOfflineData/Vector/GenerateESRIGridFile.xaml.cs, winformHowDoI:Samples/MapOfflineData/Vector/GenerateESRIGridFile.cs.

Grid / raster cell layers — AreaStyle on GridFeatureLayer

ClassBreakStyle is also used on GridFeatureLayer (ESRI grid raster data rendered as cells). The column name is "CellValue" — the grid cell's interpolated value — and AreaStyle fills each cell:

var gridClassBreakStyle = new ClassBreakStyle("CellValue");
const byte alpha = 150;

gridClassBreakStyle.ClassBreaks.Add(new ClassBreak(double.MinValue,
    new AreaStyle(new GeoSolidBrush(GeoColors.Transparent))));
gridClassBreakStyle.ClassBreaks.Add(new ClassBreak(6.2,
    new AreaStyle(new GeoSolidBrush(GeoColor.FromArgb(alpha, 255, 0, 0)))));
gridClassBreakStyle.ClassBreaks.Add(new ClassBreak(6.83,
    new AreaStyle(new GeoSolidBrush(GeoColor.FromArgb(alpha, 255, 128, 0)))));
// ... additional breaks ...

gridFeatureLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(gridClassBreakStyle);
gridFeatureLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

Source: wpfHowDoI:Samples/MapOfflineData/Vector/GenerateESRIGridFile.xaml.cs.


Dynamically Computed Breaks

When break thresholds are calculated from the actual feature data rather than specified as constants, the pattern is to read all feature values first, compute break points, then build the ClassBreakStyle:

// 1. Read all feature values from the source
featureSource.Open();
var featureCount = featureSource.GetCount();
double[] values = new double[featureCount];
for (int i = 0; i < featureCount; i++)
{
    Feature feature = featureSource.GetFeatureById(
        (i + 1).ToString(CultureInfo.InvariantCulture), SelectedColumns);
    double.TryParse(feature.ColumnValues[SelectedColumns[0]], out values[i]);
}
featureSource.Close();

// 2. Build the ClassBreakStyle using computed thresholds
ClassBreakStyle classBreakStyle = new ClassBreakStyle(SelectedColumns[0])
{
    BreakValueInclusion = BreakValueInclusion.IncludeValue
};

double[] classBreakValues = ComputeClusterBreaks(values, classBreaksCount - 1);
for (int i = 0; i < classBreakValues.Length; i++)
{
    ClassBreak classBreak = new ClassBreak(
        classBreakValues[i],
        new AreaStyle(
            new GeoPen(GeoColor.FromHtml("#f05133"), 1),
            new GeoSolidBrush(new GeoColor(opacity, familyColors[i]))));
    classBreakStyle.ClassBreaks.Add(classBreak);
}

Source: blazorHowDoI:Shared/ThematicDemographicStyleBuilder.cs.


Updating a Style at Runtime

When regenerating a ClassBreakStyle in response to user input (e.g., a new color family chosen from a cloud color API), clear the layer's existing CustomStyles before adding the new one:

// Get the layer from the overlay already on the map
var housingUnitsOverlay = (LayerOverlay)MapView.Overlays["Frisco Housing Units Overlay"];
var housingUnitsLayer = (ShapeFileFeatureLayer)housingUnitsOverlay.Layers["Frisco Housing Units"];

// Remove previous style
housingUnitsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Clear();

// Build and add the new style
var classBreakStyle = new ClassBreakStyle();
classBreakStyle.ColumnName = "H_UNITS";
double[] intervals = { 0, 1000, 2000, 3000, 4000, 5000 };
for (var i = 0; i < colors.Count; i++)
{
    var areaStyle = new AreaStyle(new GeoSolidBrush(colors[colors.Count - i - 1]));
    classBreakStyle.ClassBreaks.Add(new ClassBreak(intervals[i], areaStyle));
}

housingUnitsLayer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(classBreakStyle);
housingUnitsLayer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

// Redraw — refresh the overlay, not the whole map, for efficiency
await housingUnitsOverlay.RefreshAsync();

Sources: wpfHowDoI:Samples/ThinkGeoCloudIntegration/ColorUtilities.xaml.cs, winformHowDoI:Samples/ThinkGeoCloudIntegration/ColorUtilities.cs, mauiHowDoI:Samples/ThinkGeoCloudIntegration/ColorUtilitiesCloudServices.xaml.cs.


Wiring Breaks to a LegendAdornmentLayer

The standard pattern for creating a synchronized legend is to build both the ClassBreak and its LegendItem in the same loop. classBreak.DefaultAreaStyle gives back the area style that was supplied to the constructor, and classBreak.Value gives the threshold for the label text:

var housingUnitsStyle = new ClassBreakStyle("H_UNITS");
var classBreakIntervals = new double[] { 0, 1000, 2000, 3000, 4000, 5000 };
var colors = GeoColor.GetColorsInHueFamily(GeoColors.Red, classBreakIntervals.Length).Reverse().ToList();

for (var i = 0; i < classBreakIntervals.Length; i++)
{
    // Create break and style together
    var classBreak = new ClassBreak(
        classBreakIntervals[i],
        AreaStyle.CreateSimpleAreaStyle(new GeoColor(192, colors[i]), GeoColors.White));

    housingUnitsStyle.ClassBreaks.Add(classBreak);

    // Mirror it in the legend — use classBreak.DefaultAreaStyle and classBreak.Value
    var legendItem = new LegendItem()
    {
        ImageStyle = classBreak.DefaultAreaStyle,
        TextStyle = new TextStyle(
            $">{classBreak.Value} units",
            new GeoFont("Verdana", 10),
            GeoBrushes.Black)
    };
    legend.LegendItems.Add(legendItem);
}

layer.ZoomLevelSet.ZoomLevel01.CustomStyles.Add(housingUnitsStyle);
layer.ZoomLevelSet.ZoomLevel01.ApplyUntilZoomLevel = ApplyUntilZoomLevel.Level20;

When updating the style at runtime, clear the legend's existing items before adding the new ones:

legend.LegendItems.Clear();
foreach (var classBreak in classBreaks)
{
    var legendItem = new LegendItem
    {
        ImageStyle = classBreak.DefaultAreaStyle,
        TextStyle = new TextStyle(
            $">{classBreak.Value} units",
            new GeoFont("Verdana", 10),
            GeoBrushes.Black)
    };
    legend.LegendItems.Add(legendItem);
}
await MapView.AdornmentOverlay.RefreshAsync();

Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnClassBreaks.xaml.cs, winformHowDoI:Samples/VectorDataStyling/RenderBasedOnClassBreaks.cs, mauiHowDoI:Samples/VectorDataStyling/CreateClassBreakStyle.xaml.cs, mauiHowDoI:Samples/ThinkGeoCloudIntegration/ColorUtilitiesCloudServices.xaml.cs.


Generating a Color Ramp with GeoColor.GetColorsInHueFamily

All polygon class-break samples use GeoColor.GetColorsInHueFamily to produce a graduated color sequence, then .Reverse() so that higher values get deeper colors:

var colors = GeoColor.GetColorsInHueFamily(GeoColors.Red, classBreakIntervals.Length)
                     .Reverse()
                     .ToList();

The Blazor thematic builder uses GeoColor.GetColorsInQualityFamily instead, which interpolates between a start and end color across a wheel direction:

Collection<GeoColor> familyColors = GeoColor.GetColorsInQualityFamily(
    StartColor, EndColor, classBreakCount, ColorWheelDirection);

Sources: wpfHowDoI:Samples/VectorDataStyling/RenderBasedOnClassBreaks.xaml.cs, blazorHowDoI:Shared/ThematicDemographicStyleBuilder.cs.


Common Patterns and Pitfalls

Breaks must be added in ascending order. The engine evaluates breaks from lowest to highest and applies the style of the last break whose Value is ≤ the feature's attribute. If breaks are added out of order, features may render with the wrong style.

Use double.MinValue as the sentinel for an explicit "no match" style. If a feature's column value is below the first explicit threshold, no break technically applies. Adding a ClassBreak(double.MinValue, transparentStyle) as the first entry ensures those features are explicitly rendered as transparent (or any other "unclassified" style) rather than falling back to a default.

ClassBreakStyle reads column values as double. The column's raw string value is parsed to double at render time. Non-numeric or empty values will silently fail to match any break, and the feature will render without style. Ensure the column contains valid numeric data before applying ClassBreakStyle.

Always clear CustomStyles before replacing a style at runtime. If you add a second ClassBreakStyle without clearing, both styles render simultaneously, which usually produces unexpected visual layering. Call layer.ZoomLevelSet.ZoomLevel01.CustomStyles.Clear() before adding the new style.

classBreak.DefaultAreaStyle only works when the underlying style is an AreaStyle. When using PointStyle or LineStyle breaks, access the style through classBreak.DefaultPointStyle or classBreak.DefaultLineStyle respectively. Using DefaultAreaStyle on a PointStyle break returns null.


Source References

Sample Namespace ClassBreakStyle usage
VectorDataStyling/RenderBasedOnClassBreaks WPF, WinForms Polygon choropleth with H_UNITS; GeoColor.GetColorsInHueFamily; legend wiring via classBreak.DefaultAreaStyle and classBreak.Value
VectorDataStyling/CreateClassBreakStyle MAUI Same pattern as above; MapView.MapScale + MapView.CenterPoint for initial extent
MapOfflineData/Vector/GenerateESRIGridFile WPF, WinForms Point layer with PointStyle breaks on "PH" column; grid layer with AreaStyle breaks on "CellValue" column; double.MinValue sentinel
ThinkGeoCloudIntegration/ColorUtilities WPF, WinForms, MAUI Runtime replacement of ClassBreakStyle in response to Cloud Color API; CustomStyles.Clear() + overlay-level RefreshAsync()
Shared/ThematicDemographicStyleBuilder.cs Blazor Dynamically computed cluster breaks from feature data; BreakValueInclusion.IncludeValue; GeoColor.GetColorsInQualityFamily with ColorWheelDirection