TL;DR: Let’s see how to create a Bullet Chart using the .NET MAUI Linear Gauge. This chart is ideal for visualizing product sales performance for clear and intuitive data representation, making it easier to derive actionable insights. Steps include data collection, model and ViewModel class setup, layout organization, and customization.
Welcome to this week’s edition of the Chart of the Week blog series!
Today, we’ll explore a powerful data visualization technique: the Bullet Chart, built using Syncfusion .NET MAUI Linear Gauge. This chart is ideal for businesses and analysts comparing actual vs. target values, monitoring performance ranges, and tracking progress toward goals. Whether assessing sales revenue, efficiency, or other critical KPIs, integrating a Bullet Chart into your app enables clear and intuitive data representation, making it easier to derive actionable insights.
A Bullet Chart is a data visualization tool that can display key performance indicators in a compact format. It consists of a main bar representing actual performance, a marker for the target value, and qualitative background ranges indicating performance levels. Bullet charts are more space-efficient and informative compared to traditional gauges. They are commonly used in business intelligence dashboards to track metrics like revenue, sales, and efficiency. This makes them ideal for quick and clear performance analysis.
The following image shows the bullet chart that we intend to create.
To start our analysis, we’ll gather essential data needed to visualize product sales and performance effectively. We will use assumed sales performance data for this.
Now, define a GaugeModel class, which stores data for a product, including category, observed value, target value, and performance ranges (low, mid, high). It also includes an axis visibility property, defaulting to false. The constructor initializes all these values for each instance.
Refer to the following code example.
public class GaugeModel { public string Category { get; set; } public double ObservedValue { get; set; } public double TargetValue { get; set; } public double LowRange { get; set; } public double MidRange { get; set; } public double HighRange { get; set; } public bool IsAxisVisible { get; set; } = false; public GaugeModel(string text, double actual, double target, double low, double mid, double high) { Category = text; ObservedValue = actual; TargetValue = target; LowRange = low; MidRange = mid; HighRange = high; } }
Next, create a ViewModel class with a property named BulletChartData to store product performance data. Populate the collection with sample data and bind it to the UI, ensuring dynamic updates when the data changes.
Refer to the following code example.
internal class ViewModel { public ObservableCollection<GaugeModel> BulletChartData { get; set; } public ViewModel() { BulletChartData = new ObservableCollection<GaugeModel>() { new Model("Product A", 190, 220, 150, 180, 210, false), new Model("Product B", 175, 180, 130, 160, 190, false), new Model("Product C", 195, 200, 130, 170, 210, false), new Model("Product D", 205, 180, 140, 170, 210, false), new Model("Product E", 215, 220, 150, 190, 220, false), new Model("Product F", 185, 200, 140, 170, 210, false), new Model("Product G", 208, 200, 150, 190, 220, true) }; } }
Next, create a structured layout to organize the UI elements:
This structured approach ensures a clean and visually appealing layout.
<Border StrokeShape="RoundRectangle 10" Stroke="Black" Margin="10"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> </Grid> </Border>
To design the Bullet Chart, we’ll use the Syncfusion .NET MAUI Linear Gauge control by referring to this documentation. Enhance the chart by defining range indicators, target markers, bar pointers, and tick styles to create a visually intuitive performance comparison.
This setup ensures that the charts update in real-time when data changes occur.
<Grid Grid.Row="1"> <VerticalStackLayout BindableLayout.ItemsSource="{Binding BulletChartData}" x:Name="layout" Spacing="{OnPlatform Default=70, WinUI=40, Android=40}"> <BindableLayout.ItemTemplate> <DataTemplate> <gauge:SfLinearGauge> …… </gauge:SfLinearGauge> </DataTemplate> </BindableLayout.ItemTemplate> </VerticalStackLayout> </Grid>
To enhance readability and improve data visualization, we’ll customize the following chart elements:
Providing context to the visualization helps users understand the insights presented in the Bullet Chart by summarizing its purpose clearly.
This section enhances clarity by summarizing what the visualization represents.
<Grid Grid.Row="0" Margin="10"> <Grid.RowDefinitions> <RowDefinition Height="{OnPlatform Android=68,Default=80,iOS=68}"/> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="{OnPlatform Android=20, Default=40, iOS=15}"/> <ColumnDefinition Width="Auto"/> </Grid.ColumnDefinitions> <BoxView Grid.Column="0" Grid.RowSpan="2" BackgroundColor="Blue" Margin="0,-20,0,0" HeightRequest="45" WidthRequest="20" /> <StackLayout Grid.Column="1" Grid.Row="0" Margin="7,7,0,0"> <Label Text="Comparative Analysis of Product Performance" FontSize="{OnPlatform Android=15,Default=18,iOS=15}" FontAttributes="Bold"/> <Label Text="{OnPlatform Default='Evaluation of Key Performance Metrics and Competitive Benchmarking Across Market Competitors.', Android='Evaluating Key Performance Metrics and Competitive Industry Benchmarks.', iOS='Evaluating Performance Metrics and Industry Benchmarks.'}" FontSize="{OnPlatform Android=10,Default=13,iOS=10}" Margin="0,2,0,0"/> </StackLayout> </Grid>
Refer to the following code example to customize the chart axis. Here, we’ll:
<gauge:SfLinearGauge Interval="40" Maximum="240" Orientation="Horizontal" MinorTicksPerInterval="0" ShowLine="False" TickOffset="{OnPlatform Android=30, Default=25, iOS=15}" ShowLabels="{Binding IsAxisVisible}" ShowTicks="{Binding IsAxisVisible}"> …….. </gauge:SfLinearGauge>
Modify the MajorTickStyle to set the stroke color and enhance the appearance of major ticks.
Refer to the code examples below.
<gauge:SfLinearGauge> <gauge:SfLinearGauge.MajorTickStyle> <gauge:LinearTickStyle Stroke="Black"/> </gauge:SfLinearGauge.MajorTickStyle> ...... </gauge:SfLinearGauge>
Let’s define the following three LinearRange elements for different performance levels:
Then, assign distinct colors to each range for better differentiation.
Refer to the following code example.
<gauge:SfLinearGauge> …………. <gauge:SfLinearGauge.Ranges> <gauge:LinearRange EndValue="{Binding LowRange}" StartWidth="{OnPlatform Android=60, Default=50, iOS=30}" EndWidth="{OnPlatform Android=60, Default=50, iOS=30}" Position="Cross" Fill="#668ae6"/> <gauge:LinearRange StartValue="{Binding LowRange}" EndValue="{Binding MidRange}" StartWidth="{OnPlatform Android=60, Default=50, iOS=30}" EndWidth="{OnPlatform Android=60, Default=50, iOS=30}" Fill="#a6bfe6" Position="Cross" /> <gauge:LinearRange StartValue="{Binding MidRange}" EndValue="{Binding HighRange}" StartWidth="{OnPlatform Android=60, Default=50, iOS=30}" EndWidth="{OnPlatform Android=60, Default=50, iOS=30}" Fill="#d9e0ff" Position="Cross" /> </gauge:SfLinearGauge.Ranges> ………….. </gauge:SfLinearGauge>
Now, use a BarPointer to represent ObservedValues with a customized Fill and PointerSize.
<gauge:SfLinearGauge> ……… <gauge:SfLinearGauge.BarPointers> <gauge:BarPointer Value="{Binding ObservedValue}" PointerSize="{OnPlatform Android=8, Default=8, iOS=6}" Fill="#003bcc"/> </gauge:SfLinearGauge.BarPointers> ………… </gauge:SfLinearGauge>
Next, use a LinearShapePointer to indicate TargetValues with a rectangle marker. You can also customize the marker’s shape, width, height, and position for better clarity.
<gauge:SfLinearGauge> <gauge:SfLinearGauge.MarkerPointers> <gauge:LinearShapePointer ShapeType="Rectangle" Value="{Binding TargetValues}" Fill="#003f5c" ShapeWidth="4" ShapeHeight="30" Position="Cross"/> </gauge:SfLinearGauge.MarkerPointers> </gauge:SfLinearGauge>
Now, use a LinearContentPointer to indicate Category in content. Then, adjust the Alignment, OffsetX, and OffsetY properties to enhance the readability and positioning of the chart.
<gauge:SfLinearGauge> …… <gauge:SfLinearGauge.MarkerPointers> …… <gauge:LinearContentPointer Value="{OnPlatform Android=4, iOS=5, Default=0}" OffsetX="{OnPlatform Android=-10, iOS=-10, Default=-15}" OffsetY="{OnPlatform Android=10, iOS=10, Default=10}" Alignment="{OnPlatform Android=Start, iOS=Start, Default=Start}"> <gauge:LinearContentPointer.Content> <Label Text="{Binding BindingContext.Category}"/> </gauge:LinearContentPointer.Content> </gauge:LinearContentPointer> </gauge:SfLinearGauge.MarkerPointers> </gauge:SfLinearGauge>
After executing the previous code examples, the output will look like the following image.
For more details, refer to the visualizing product sales performance with the .NET MAUI Bullet Chart GitHub demo.
Thanks for reading! We’ve explored how to create a Bullet Chart using the Syncfusion .NET MAUI Linear Gauge control. We highly recommend following the steps outlined in this blog. Share your feedback in the comments section below.
If you need any assistance, please do not hesitate to contact us via our support forum, support portal, or feedback portal. We are always eager to help you!