In this article:
Introduction
A Stacked Box Plot chart displays multiple box plot series side-by-side at the same X positions, making it easy to compare statistical distributions across categories. Each box shows the median, lower quartile, upper quartile, minimum, and maximum of a dataset.
SciChart WPF provides the StackedBoxPlotRenderableSeries for rendering grouped box plots. It shares the visual appearance of FastBoxPlotRenderableSeries (whiskers, box, median line) but adds side-by-side grouping via the StackedGroupId property — the same grouping concept used by StackedColumnRenderableSeries. Multiple series with the same StackedGroupId are automatically laid out at equal widths within the available DataPointWidth, with configurable pixel spacing between them.
Data Model
StackedBoxPlotRenderableSeries uses the same data series type as FastBoxPlotRenderableSeries: BoxPlotDataSeries<TX, TY>;. Each data point consists of six values:
- X — the category or position on the X axis
- Median — the middle value, drawn as a horizontal line through the box
- Minimum — the lower whisker endpoint
- LowerQuartile — the bottom edge of the box
- UpperQuartile — the top edge of the box
- Maximum — the upper whisker endpoint
| Creating a BoxPlotDataSeries and appending data |
Copy Code |
|---|---|
var series = new BoxPlotDataSeries<double, double>(); // Append individual points series.Append(x: 0, median: 5.0, minimum: 1.0, lowerQuartile: 3.0, upperQuartile: 7.0, maximum: 10.0); // Append multiple points at once series.Append( x: new double[] { 0, 1, 2, 3, 4 }, median: new double[] { 5.0, 6.2, 4.8, 7.1, 5.5 }, minimum: new double[] { 1.0, 2.1, 1.5, 3.0, 2.0 }, lowerQuartile: new double[] { 3.0, 4.0, 3.2, 5.0, 3.8 }, upperQuartile: new double[] { 7.0, 8.0, 6.5, 9.0, 7.2 }, maximum: new double[] { 10.0, 11.0, 9.0, 12.0, 10.5 }); | |
Creating a Stacked Box Plot Chart
To create a Stacked Box Plot chart, add multiple StackedBoxPlotRenderableSeries to a SciChartSurface and assign each one a BoxPlotDataSeries. Series with the same StackedGroupId are automatically arranged side-by-side.
Declaring in XAML / Code-Behind
| Declaring StackedBoxPlotRenderableSeries |
Copy Code |
|---|---|
<!-- Create the chart surface --> <s:SciChartSurface> <!-- Declare RenderableSeries --> <s:SciChartSurface.RenderableSeries> <s:StackedBoxPlotRenderableSeries x:Name="boxSeries1" Fill="#882196F3" Stroke="#2196F3" StrokeThickness="1" StackedGroupId="Group1" /> <s:StackedBoxPlotRenderableSeries x:Name="boxSeries2" Fill="#884CAF50" Stroke="#4CAF50" StrokeThickness="1" StackedGroupId="Group1" /> <s:StackedBoxPlotRenderableSeries x:Name="boxSeries3" Fill="#88FF9800" Stroke="#FF9800" StrokeThickness="1" StackedGroupId="Group1" /> </s:SciChartSurface.RenderableSeries> <!-- X & Y Axis --> <s:SciChartSurface.XAxis> <s:NumericAxis GrowBy="0.1, 0.1"/> </s:SciChartSurface.XAxis> <s:SciChartSurface.YAxis> <s:NumericAxis GrowBy="0.1, 0.1"/> </s:SciChartSurface.YAxis> </s:SciChartSurface> | |
Then in code-behind, assign data to each series:
| Appending data |
Copy Code |
|---|---|
private BoxPlotDataSeries<double, double> CreateBoxData(int seed, int count) { var medians = new RandomWalkGenerator(0).GetRandomWalkSeries(count).YData; var rng = new Random(seed); var series = new BoxPlotDataSeries<double, double>(); for (int i = 0; i < count; i++) { double med = medians[i]; double min = med - rng.NextDouble(); double max = med + rng.NextDouble(); double lower = (med - min) * rng.NextDouble() + min; double upper = (max - med) * rng.NextDouble() + med; series.Append(i, med, min, lower, upper, max); } return series; } // In OnLoaded: boxSeries1.DataSeries = CreateBoxData(seed: 1, count: 5); boxSeries2.DataSeries = CreateBoxData(seed: 2, count: 5); boxSeries3.DataSeries = CreateBoxData(seed: 3, count: 5); | |
How Stacking and Grouping Works
StackedBoxPlotRenderableSeries has a StackedGroupId property which defines how box plots are grouped and laid out side-by-side. This works differently from StackedColumnRenderableSeries: box plots are always placed next to each other (never vertically stacked), because stacking statistical distributions vertically would be meaningless.
Multiple Series, Same StackedGroupId (the default)
By default, StackedGroupId is "DefaultStackedGroupId". When multiple StackedBoxPlotRenderableSeries share the same group, their box plots are placed side-by-side at each X position. The available width (DataPointWidth) is divided equally among all series in the group.
| Single Stacked Group |
Copy Code |
|---|---|
// Three series, same group → three boxes side-by-side at each X <s:StackedBoxPlotRenderableSeries StackedGroupId="Group1" Fill="#882196F3" ... /> <s:StackedBoxPlotRenderableSeries StackedGroupId="Group1" Fill="#884CAF50" ... /> <s:StackedBoxPlotRenderableSeries StackedGroupId="Group1" Fill="#88FF9800" ... /> | |
Multiple Series, Different StackedGroupIds
When series have different StackedGroupIds, each group occupies its own horizontal slot. This allows you to create multiple independent clusters of box plots at the same X position. For example, you could have one group for "Before Treatment" and another for "After Treatment", each containing multiple measurement series.
| Multiple Stacked Groups |
Copy Code |
|---|---|
// Two groups, each with 2 series → two clusters of two boxes each <s:StackedBoxPlotRenderableSeries StackedGroupId="Before" Fill="#882196F3" ... /> <s:StackedBoxPlotRenderableSeries StackedGroupId="Before" Fill="#884CAF50" ... /> <s:StackedBoxPlotRenderableSeries StackedGroupId="After" Fill="#88FF9800" ... /> <s:StackedBoxPlotRenderableSeries StackedGroupId="After" Fill="#88E91E63" ... /> | |
Spacing and Width
Two properties control the horizontal layout of stacked box plots:
DataPointWidth
Inherited from the base box plot series, DataPointWidth is a fractional value between 0.0 and 1.0 that determines what fraction of the available space each cluster of boxes occupies. The default is 0.4. A value of 1.0 means the boxes fill the entire space between adjacent X values.
| Setting DataPointWidth |
Copy Code |
|---|---|
<!-- Wider boxes --> <s:StackedBoxPlotRenderableSeries DataPointWidth="0.8" ... /> <!-- Narrower boxes --> <s:StackedBoxPlotRenderableSeries DataPointWidth="0.3" ... /> | |
Spacing
The Spacing property is an absolute pixel gap inserted between adjacent box plots within the same group. The default is 1.0 pixel. Increase this to add more visual separation between the boxes in a cluster.
| Setting Spacing |
Copy Code |
|---|---|
<!-- More space between boxes in the group --> <s:StackedBoxPlotRenderableSeries Spacing="3" ... /> | |
Appearance Properties
StackedBoxPlotRenderableSeries inherits its visual properties from BaseBoxPlotRenderableSeries and BaseRenderableSeries. The key appearance properties are:
- Stroke (Color) — the outline color of the box, whiskers, and median line. Default: Colors.White.
- StrokeThickness (int) — the width of the outline in pixels. The median line is drawn 1 pixel thicker than this value for emphasis. Default: 1.
- Fill (Brush) — the brush used to fill the box body (the rectangle between LowerQuartile and UpperQuartile). Default: transparent.
- Opacity (double) — overall transparency, from 0.0 (invisible) to 1.0 (opaque). Applied to both stroke and fill.
- AntiAliasing (bool) — enables smoother rendering of box edges. Default: true.
- IsVisible (bool) — controls whether the series is rendered. Default: true.
- PaletteProvider (IPaletteProvider) — optional provider to override stroke or fill colors per data point. Supports IStrokePaletteProvider and IFillPaletteProvider.
Each box plot is drawn with three visual components:
- Whiskers — vertical lines from Minimum to LowerQuartile and from UpperQuartile to Maximum, drawn with Stroke and StrokeThickness.
- Box body — a filled rectangle from LowerQuartile to UpperQuartile, drawn with Fill (interior) and Stroke (border).
- Median line — a horizontal line at the Median value, drawn with Stroke at StrokeThickness + 1 for visual emphasis.
Creating a Stacked Box Plot in Pure Code
| Creating Stacked Box Plot in code |
Copy Code |
|---|---|
var sciChartSurface = new SciChartSurface(); var boxSeries1 = new StackedBoxPlotRenderableSeries { Fill = new SolidColorBrush(Color.FromArgb(0x88, 0x21, 0x96, 0xF3)), Stroke = Color.FromRgb(0x21, 0x96, 0xF3), StrokeThickness = 1, StackedGroupId = "Group1", }; var boxSeries2 = new StackedBoxPlotRenderableSeries { Fill = new SolidColorBrush(Color.FromArgb(0x88, 0x4C, 0xAF, 0x50)), Stroke = Color.FromRgb(0x4C, 0xAF, 0x50), StrokeThickness = 1, StackedGroupId = "Group1", }; sciChartSurface.RenderableSeries.Add(boxSeries1); sciChartSurface.RenderableSeries.Add(boxSeries2); // Create and assign data var data1 = new BoxPlotDataSeries<double, double>(); var data2 = new BoxPlotDataSeries<double, double>(); var rng = new Random(42); for (int i = 0; i < 5; i++) { double med1 = 10 + rng.NextDouble() * 5; data1.Append(i, med1, med1 - 3, med1 - 1.5, med1 + 1.5, med1 + 3); double med2 = 8 + rng.NextDouble() * 5; data2.Append(i, med2, med2 - 2, med2 - 1, med2 + 1, med2 + 2); } boxSeries1.DataSeries = data1; boxSeries2.DataSeries = data2; | |
MVVM Support
StackedBoxPlotRenderableSeries supports the SciChart MVVM API via StackedBoxPlotRenderableSeriesViewModel. All key properties are exposed on the view model: Fill, Stroke, StrokeThickness, DataPointWidth, StackedGroupId, and Spacing.
| Creating Stacked Box Plot with MVVM API |
Copy Code |
|---|---|
// ViewModel public ObservableCollection<IRenderableSeriesViewModel> RenderableSeriesViewModels { get; } = new ObservableCollection<IRenderableSeriesViewModel> { new StackedBoxPlotRenderableSeriesViewModel { DataSeries = CreateBoxData(seed: 1), Fill = new SolidColorBrush(Color.FromArgb(0x88, 0x21, 0x96, 0xF3)), Stroke = Color.FromArgb(0xFF, 0x21, 0x96, 0xF3), StrokeThickness = 1, StackedGroupId = "Group1", }, new StackedBoxPlotRenderableSeriesViewModel { DataSeries = CreateBoxData(seed: 2), Fill = new SolidColorBrush(Color.FromArgb(0x88, 0x4C, 0xAF, 0x50)), Stroke = Color.FromArgb(0xFF, 0x4C, 0xAF, 0x50), StrokeThickness = 1, StackedGroupId = "Group1", }, }; | |
Bind in XAML using SeriesBinding:
| Binding Stacked Box Plot in XAML |
Copy Code |
|---|---|
<!-- XAML --> <s:SciChartSurface RenderableSeries="{s:SeriesBinding RenderableSeriesViewModels}"> <s:SciChartSurface.XAxis> <s:NumericAxis GrowBy="0.1, 0.1"/> </s:SciChartSurface.XAxis> <s:SciChartSurface.YAxis> <s:NumericAxis GrowBy="0.1, 0.1"/> </s:SciChartSurface.YAxis> <s:SciChartSurface.ChartModifier> <s:ModifierGroup> <s:ZoomPanModifier/> <s:ZoomExtentsModifier/> <s:MouseWheelZoomModifier/> </s:ModifierGroup> </s:SciChartSurface.ChartModifier> </s:SciChartSurface> | |
Limitations of Stacked Box Plots
When using StackedBoxPlotRenderableSeries, there are several important differences from StackedColumnRenderableSeries to keep in mind:
No Vertical Stacking
Box plots are always placed side-by-side, never stacked vertically. The IsOneHundredPercent (percentage mode) property is not supported and will throw NotSupportedException if accessed.
Consistent X-Values Across Series
All series within a stacking group should share the same X-values — the same count and the same values in the same order. This ensures that boxes are aligned correctly at each X position. If one series has fewer data points, the side-by-side layout at those positions may appear uneven.
X-Values Must Be Valid Numeric Values
The X-values must be valid, finite numeric values. Special values such as Double.NaN, Double.PositiveInfinity, or Double.NegativeInfinity are not supported and will lead to undefined rendering.
Representing Missing Data
To represent missing data points at a particular X position, set the Y values (Median, Min, LowerQuartile, UpperQuartile, Max) to Double.NaN. The box at that position will not be drawn, while other series in the group will still render normally.
Use Cases
Stacked Box Plot charts are useful in scenarios where you need to compare statistical distributions across multiple categories or conditions:
- Clinical trials — comparing distributions of a biomarker across multiple treatment arms (placebo, low dose, high dose) at each time point
- A/B testing — comparing response time distributions across multiple server configurations or algorithm variants
- Manufacturing quality control — comparing measurement distributions across production lines, shifts, or batches
- Financial analysis — comparing daily return distributions across asset classes, sectors, or time periods
- Academic research — comparing test score distributions across student groups, teaching methods, or experimental conditions
- Sensor monitoring — comparing temperature, pressure, or vibration distributions across sensors or locations