SciChart WPF 2D Charts > 2D Chart Types > The Bubble Charts Type > Bubble Series Color Maps
Bubble Series Color Maps

Introduction

A Color Map colors each bubble individually by a data value, adding a visual dimension on top of position (X/Y) and size (Z). Assign an IColorMap to a bubble series and every bubble's fill is sampled from the palette's gradient instead of a single flat color.

By default the colorized value is the point's Z value - the same value that drives bubble size - so color and size move together. Assigning a ColorMapValueProvider lets color track a different quantity instead, typically one carried in point metadata, so size and color can encode two independent dimensions of the data.

FillBrush independently customizes the bubble's radial fill, and composes with whichever color was resolved for the point.

Bubble Series Color Map support has been introduced in SciChart WPF v9.1.

Enabling ColorMap Coloring

Assign ColorMap - an IColorMap - to switch a bubble series from its flat BubbleColor to per-point coloring. The shipping implementation is HeatmapColorPalette that accepts a GradientStops collection as the content property.

When ColorMap is null (the default) the series falls back to BubbleColor; setting it back to null at runtime disables the feature immediately.

Enabling ColorMap Coloring
Copy Code
<s:FastBubbleRenderableSeries x:Name="BubbleSeries" BubbleColor="White"
                              AutoZRange="True" MaxBubbleSizeInPixels="60" Opacity="0.85">
    <s:FastBubbleRenderableSeries.ColorMap>
        <s:HeatmapColorPalette>
            <GradientStop Color="#4682B4" Offset="0" />
            <GradientStop Color="#FF4500" Offset="1" />
        </s:HeatmapColorPalette>
    </s:FastBubbleRenderableSeries.ColorMap>
</s:FastBubbleRenderableSeries>

The HeatmapColorPalette

HeatmapColorPalette maps a numeric value to a color by interpolating across GradientStops, the same GradientStop type used by WPF's own gradient brushes, with offsets relative from 0 to 1.

Minimum (default 0) and Maximum (default 100) define the value range mapped across those stops - subject to the ColorMapRangeMode described next. Precision (default 1000) sets the size of the cached color lookup table; AllowsHighPrecision (default false) turns on interpolation between adjacent table entries for smoother gradients, at the cost of a little extra work per point.

A NaN colorized value always renders transparent - this is fixed behavior, not configurable - which is a convenient way to hide points that have no meaningful value for the mapped quantity.

ColorMap is typed to the IColorMap interface, so any implementation satisfying it is accepted; HeatmapColorPalette is the fast, built-in path used throughout this article:

Setting HeatmapColorPalette
Copy Code
public interface IColorMap
{
    double Minimum { get; }
    double Maximum { get; }
    Color GetColor(double value);
    void SetDataRange(double minimum, double maximum);
}

Color Range Modes (ColorMapRangeMode)

ColorMapRangeMode decides how the value range that maps onto the palette gradient is resolved on each render pass:

Color Range Modes
Copy Code
public enum ColorMapRangeMode
{
    Manual,
    Auto,
    AutoMin,
    AutoMax,
}

Auto (default)

Both ends of the color range fit the data range on every render pass. Colors stay spread across the full gradient regardless of what the data currently contains, but the same value can map to a different color after a zoom or a data update.

Manual

The palette's own Minimum and Maximum are used verbatim, ignoring the data. Colors stay stable across updates and zoom - the right choice whenever a given value must always map to the same color.

AutoMin

The low end tracks the data; the high end stays pinned to the palette's configured Maximum.

AutoMax

The high end tracks the data; the low end stays pinned to the palette's configured Minimum - for example, pinning the floor to zero while the top of the gradient stretches to whatever the data reaches.

"The data range" is the whole series' Z range by default, or whatever the value provider's GetValueRange returns when a ColorMapValueProvider is assigned (see the next section). Whichever mode is active, the palette's own configured Minimum and Maximum are never mutated - the resolved range is pushed into the color map separately, through IColorMap.SetDataRange.

BubbleSeries.ColorMapRangeMode = ColorMapRangeMode.AutoMax;

Coloring by a Custom Value (ColorMapValueProvider)

When ColorMapValueProvider is null - the default - bubbles color by their Z value, the same value that drives bubble size. Assign an IPointColorMapValueProvider to color by anything else instead, typically a value carried in the point's IPointMetadata.

Coloring by a Custom Value
Copy Code
public interface IPointColorMapValueProvider : IColorMapValueProvider
{
    double GetValue(IRenderableSeries rSeries, int index, IPointMetadata metadata);
    IRange GetValueRange(IRenderableSeries rSeries, IndexRange pointRange);
}

GetValue returns the value to colorize for the point at index - or double.NaN to render that point transparent.

GetValueRange returns the range the Auto/AutoMin/AutoMax modes should span; return a cached range here rather than scanning the data, since it runs on every render pass.

OnBeginSeriesDraw (inherited from IColorMapValueProvider) is called once before each draw pass, a convenient place to cache anything expensive.

NOTE: ColorMapValueProvider on FastBubbleRenderableSeries is typed to IPointColorMapValueProvider directly - the point-indexed flavor a bubble series requires.

Customizing the Bubble Look (FillBrush)

FillBrush is a RadialGradientBrush that defines each bubble's radial appearance. When null (the default) bubbles use the built-in look: a radial gradient of the resolved color, fading to transparent at both the center and the edge.

When a ColorMap (or a PaletteProvider) supplies a per-point color, FillBrush's gradient stop colors are multiplied channel-wise by that color before the bubble is drawn. Author white- or gray-based brushes so per-point colors pass through faithfully - multiplication can only darken a color, never brighten it, so a hue baked into a brush stop (orange, red, ...) distorts the mapped color rather than tinting it cleanly.

Using FillBrush
Copy Code
<!-- Solid disc: two opaque white stops pass the resolved color through unchanged -->
<RadialGradientBrush>
    <GradientStop Color="White" Offset="0" />
    <GradientStop Color="White" Offset="1" />
</RadialGradientBrush>
<!-- Soft ball: opaque white centre fading to transparent at the edge -->
<RadialGradientBrush>
    <GradientStop Color="White" Offset="0" />
    <GradientStop Color="White" Offset="0.7" />
    <GradientStop Color="Transparent" Offset="1" />
</RadialGradientBrush>

To change the brush, assign a new instance - edits to the gradient stops of an already-assigned brush are not tracked and will not be picked up.

A fixed-pixel stroke ring can be layered on top of the fill by setting StrokeThickness above its bubble default of 0; the ring is drawn in the Stroke color at a constant pixel width regardless of bubble size or zoom.

How the Bubble Color Is Resolved

Each bubble's fill color is resolved in a fixed order:

  1. PaletteProvider (as IPointMarkerPaletteProvider) - a per-point override, if it returns one for this point.
  2. Otherwise, the ColorMap color for the point's value (the provider's value, or Z when no ColorMapValueProvider is set).
  3. Otherwise, the series' own BubbleColor.

The resolved color is then shaped by FillBrush (channel-wise multiplication, as above), and the optional stroke ring (Stroke, StrokeThickness) is drawn on top of the fill.

MVVM Support

BubbleRenderableSeriesViewModel exposes ColorMap, ColorMapValueProvider (typed IPointColorMapValueProvider) and FillBrush directly, alongside BubbleColor and the existing sizing properties:

Applying colors in MVVM
Copy Code
var vm = new BubbleRenderableSeriesViewModel
{
    DataSeries = dataSeries,
    BubbleColor = Colors.White,
    ColorMap = new HeatmapColorPalette { Minimum = 0, Maximum = 100 },
    ColorMapRangeMode = ColorMapRangeMode.Auto,
    ColorMapValueProvider = new DepthColorMapValueProvider(depthRange),
    FillBrush = softBallBrush,
};
SeriesViewModels.Add(vm);
Applying colors in MVVM
Copy Code
<s:SciChartSurface RenderableSeries="{s:SeriesBinding SeriesViewModels}" />

Tips and Best Practices

  • Use Manual mode with an explicitly-ranged palette when colors must stay comparable across updates and zoom; use Auto for an exploratory look of the entire data range
  • Return a cached range from GetValueRange rather than scanning the data - it runs on every render pass:
Returning a cached range
Copy Code
private static readonly IRange CachedDepthRange = new DoubleRange(0, 700);
public IRange GetValueRange(IRenderableSeries rSeries, IndexRange pointRange) => CachedDepthRange;
  • Author white- or gray-based FillBrush instances when ColorMap or PaletteProvider coloring is used
  • Return double.NaN from a ColorMapValueProvider to hide points that have no meaningful value for the mapped quantity

See Also