Skip to content

Commit 044feaa

Browse files
Merge pull request #1 from SyncfusionExamples/Update-the-sample
Blog - Updated the sample.
2 parents e60b7f6 + 2d0b0b7 commit 044feaa

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+1422
-2
lines changed

README.md

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,55 @@
1-
# Building-Variance-Indicators-in-.NET-MAUI-Toolkit-Charts-to-Visualize-Natural-Gas-Price-Volatility
2-
Discover techniques to visualize natural gas price volatility with dynamic variance indicators using .NET MAUI Toolkit Charts from Syncfusion. Learn how to implement custom arrow indicators with percentage labels by extending range column series to deliver comprehensive energy market trend visualization for better insights
1+
# Visualizing Natural Gas Price Volatility with Custom Variance Indicators in .NET MAUI
2+
3+
Discover how to build sophisticated price volatility visualizations using variance indicators with [Syncfusion's .NET MAUI Toolkit Charts](https://www.syncfusion.com/maui-controls/maui-cartesian-charts).
4+
5+
## Overview
6+
7+
Energy market analysis demands clear visualization of both absolute price levels and price volatility. This project demonstrates how to create custom variance indicators that show percentage changes between time periods with directional arrows and color-coding, enhancing standard column charts for more insightful analysis.
8+
9+
The implementation leverages custom renderers in .NET MAUI Toolkit to create specialized variance indicators that visually represent price movement direction and magnitude. This approach is valuable for energy traders, market analysts, and financial professionals tracking commodity volatility.
10+
11+
## Key Features
12+
13+
### Advanced Variance Visualization
14+
15+
- Display both price levels and percentage changes in a single view
16+
- Highlight price increases and decreases with color-coded indicators
17+
- Show precise percentage changes between consecutive time periods
18+
- Indicate direction with custom arrow elements
19+
20+
### Custom Chart Extensions
21+
22+
- Extend [RangeColumnSeries](https://help.syncfusion.com/cr/maui-toolkit/Syncfusion.Maui.Toolkit.Charts.RangeColumnSeries.html) for specialized variance indicators
23+
- Implement custom `RangeColumnSegmentExt` for dynamic rendering
24+
- Calculate volatility metrics on the fly
25+
- Position visual elements based on data characteristics
26+
27+
### Interactive Data Analysis
28+
29+
- Access detailed data through custom tooltips
30+
- Compare price levels across multiple time periods
31+
- Identify volatility patterns with intuitive visual cues
32+
- Assess market stability through visual trend analysis
33+
34+
## Technologies Used
35+
36+
- **.NET MAUI:** Cross-platform UI framework
37+
- **Syncfusion .NET MAUI Toolkit:** Advanced charting capabilities
38+
- **Custom Renderers:** Enhanced visualization components
39+
- **C# & XAML:** Implementation languages
40+
41+
## Syncfusion Controls Used
42+
43+
This project showcases the powerful customization of Syncfusion control:
44+
45+
- **[SfCartesianChart](https://help.syncfusion.com/cr/maui-toolkit/Syncfusion.Maui.Toolkit.Charts.SfCartesianChart.html):** Foundation for price visualization
46+
47+
![VarianceIndicators](https://github.com/user-attachments/assets/4930c677-6b33-4830-bc96-555def1157db)
48+
49+
## Troubleshooting
50+
51+
### Path Too Long Exception
52+
53+
If you are facing a "Path too long" exception when building this example project, close Visual Studio and rename the repository to a shorter name before building the project.
54+
55+
For a step-by-step procedure, refer to the blog:

VarianceIndicators/App.xaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<?xml version = "1.0" encoding = "UTF-8" ?>
2+
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:local="clr-namespace:VarianceIndicators"
5+
x:Class="VarianceIndicators.App">
6+
<Application.Resources>
7+
<ResourceDictionary>
8+
<ResourceDictionary.MergedDictionaries>
9+
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
10+
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
11+
</ResourceDictionary.MergedDictionaries>
12+
</ResourceDictionary>
13+
</Application.Resources>
14+
</Application>

VarianceIndicators/App.xaml.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace VarianceIndicators
2+
{
3+
public partial class App : Application
4+
{
5+
public App()
6+
{
7+
InitializeComponent();
8+
}
9+
10+
protected override Window CreateWindow(IActivationState? activationState)
11+
{
12+
return new Window(new AppShell());
13+
}
14+
}
15+
}

VarianceIndicators/AppShell.xaml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<Shell
3+
x:Class="VarianceIndicators.AppShell"
4+
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
5+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
6+
xmlns:local="clr-namespace:VarianceIndicators"
7+
Title="VarianceIndicators">
8+
9+
<ShellContent
10+
ContentTemplate="{DataTemplate local:MainPage}"
11+
Route="MainPage" />
12+
13+
</Shell>
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace VarianceIndicators
2+
{
3+
public partial class AppShell : Shell
4+
{
5+
public AppShell()
6+
{
7+
InitializeComponent();
8+
}
9+
}
10+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
using Syncfusion.Maui.Toolkit.Charts;
2+
using Syncfusion.Maui.Toolkit.Graphics.Internals;
3+
using System.Collections.ObjectModel;
4+
using Font = Microsoft.Maui.Graphics.Font;
5+
6+
namespace VarianceIndicators
7+
{
8+
public partial class RangeColumnSeriesExt : RangeColumnSeries
9+
{
10+
protected override ChartSegment CreateSegment()
11+
{
12+
return new RangeColumnSegmentExt();
13+
}
14+
}
15+
16+
public partial class RangeColumnSegmentExt : RangeColumnSegment
17+
{
18+
float offsetPercentage = 0.2f;
19+
float topArrowTextGap = 1.0f;
20+
float bottomArrowTextGap = 1.0f;
21+
22+
protected override void Draw(ICanvas canvas)
23+
{
24+
// Call base layout first
25+
base.OnLayout();
26+
27+
// Skip drawing if segment isn't properly laid out or data is missing
28+
if (float.IsNaN(Left) || float.IsNaN(Top) || float.IsNaN(Right) || float.IsNaN(Bottom) ||
29+
Series is not RangeColumnSeriesExt || Item is not ChartDataModel dataPoint)
30+
return;
31+
32+
// Skip last segment or if there's no change
33+
var itemsSource = Series.ItemsSource as ObservableCollection<ChartDataModel>;
34+
35+
if (Index == itemsSource?.Count - 1 || dataPoint.Low == dataPoint.High)
36+
return;
37+
38+
// Calculate change direction and percentage
39+
bool isIncreasing = dataPoint.Low < dataPoint.High;
40+
double changePercent = Math.Abs((dataPoint.High - dataPoint.Low) / dataPoint.Low) * 100;
41+
42+
// Set color based on direction
43+
Color arrowColor = isIncreasing ? Colors.Green : Colors.Red;
44+
45+
// Ensure we have a valid drawing canvas
46+
if (canvas == null) return;
47+
48+
// Save canvas state before drawing
49+
canvas.SaveState();
50+
51+
float arrowSize = 6;
52+
53+
#if IOS
54+
offsetPercentage = 0.1f;
55+
topArrowTextGap = 2.0f;
56+
#elif MACCATALYST
57+
topArrowTextGap = 2.0f;
58+
#elif WINDOWS
59+
topArrowTextGap = 3.0f;
60+
#elif ANDROID
61+
bottomArrowTextGap = 1.5f;
62+
#endif
63+
64+
float columnWidth = Right - Left;
65+
float offsetX = columnWidth * offsetPercentage;
66+
float centerX = (Left + Right) / 2 + offsetX;
67+
68+
// Draw line first
69+
canvas.StrokeColor = Colors.Black;
70+
canvas.StrokeSize = 1f;
71+
canvas.DrawLine(centerX, Top, centerX, Bottom);
72+
73+
// Draw arrow
74+
var arrowPath = new PathF();
75+
76+
// Draw arrow based on direction
77+
if (isIncreasing)
78+
{
79+
arrowPath.MoveTo(centerX, Top);
80+
arrowPath.LineTo(centerX - arrowSize, Top + arrowSize);
81+
arrowPath.LineTo(centerX + arrowSize, Top + arrowSize);
82+
}
83+
else
84+
{
85+
arrowPath.MoveTo(centerX, Bottom);
86+
arrowPath.LineTo(centerX - arrowSize, Bottom - arrowSize);
87+
arrowPath.LineTo(centerX + arrowSize, Bottom - arrowSize);
88+
}
89+
90+
arrowPath.Close();
91+
canvas.FillColor = Colors.Black;
92+
canvas.FillPath(arrowPath);
93+
94+
// Draw percentage text with clear formatting
95+
string percentText = isIncreasing ?
96+
$"{changePercent:0}%" :
97+
$"- {changePercent:0}%";
98+
99+
var labelStyle = new ChartAxisLabelStyle() { FontSize = 12, TextColor = arrowColor };
100+
var font = Font.Default;
101+
102+
// Measure text size using canvas directly
103+
SizeF textSize = canvas.GetStringSize(percentText, font, (float)labelStyle.FontSize);
104+
float textY = (float)(isIncreasing ? Top - (textSize.Height * topArrowTextGap) : Bottom + (textSize.Height * bottomArrowTextGap));
105+
106+
canvas.DrawText(percentText, centerX - (textSize.Width / 2), textY, labelStyle);
107+
canvas.RestoreState();
108+
}
109+
}
110+
}

VarianceIndicators/MainPage.xaml

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
x:Class="VarianceIndicators.MainPage"
5+
xmlns:syncfusion="clr-namespace:Syncfusion.Maui.Toolkit.Charts;assembly=Syncfusion.Maui.Toolkit"
6+
xmlns:local="clr-namespace:VarianceIndicators">
7+
8+
<ContentPage.BindingContext>
9+
<local:ChartViewModel />
10+
</ContentPage.BindingContext>
11+
12+
<ContentPage.Resources>
13+
<x:String x:Key="PathData" >M24,17.000001L24,26.000002 27,26.000002 27,17.000001z M22,15.000001L29,15.000001 29,28.000002 22,28.000002z M6,14.000001L6,26.000002 9,26.000002 9,14.000001z M4,12.000001L11,12.000001 11,28.000002 4,28.000002z M15,4.0000005L15,26 18,26 18,4.0000005z M13,2.0000005L20,2.0000005 20,28 13,28z M0,0L2,0 2,30 32,30 32,32 0,32z</x:String>
14+
</ContentPage.Resources>
15+
16+
17+
<Border Margin="{OnPlatform Android=10,Default=25,iOS=10}"
18+
StrokeThickness="1" Stroke="Black"
19+
StrokeShape="RoundRectangle 15">
20+
21+
<syncfusion:SfCartesianChart HorizontalOptions="Fill"
22+
VerticalOptions="Fill"
23+
Margin="{OnPlatform Android=5,Default=15,iOS=5}">
24+
<syncfusion:SfCartesianChart.Resources>
25+
<DataTemplate x:Key="tooltipTemplate" >
26+
<Border BackgroundColor="#7bb4eb"
27+
Padding="8, 3, 8, 3"
28+
Stroke="Transparent">
29+
<StackLayout Orientation="Horizontal">
30+
<Label Text="{Binding Item.Year, StringFormat='{}{0:yyyy}'}"
31+
TextColor="Black"
32+
FontSize="14"
33+
HorizontalOptions="Center"
34+
VerticalOptions="Center"/>
35+
<Label Text=" : "
36+
TextColor="Black"
37+
FontAttributes="Bold"
38+
FontSize="14"
39+
HorizontalOptions="Center"
40+
VerticalOptions="Center"/>
41+
<BoxView WidthRequest="5"
42+
BackgroundColor="Transparent"/>
43+
<Label Text="{Binding Item.YValue}"
44+
TextColor="Black"
45+
FontSize="14"
46+
HorizontalOptions="Center"
47+
VerticalOptions="Center"/>
48+
</StackLayout>
49+
</Border>
50+
</DataTemplate>
51+
</syncfusion:SfCartesianChart.Resources>
52+
<syncfusion:SfCartesianChart.Title>
53+
<Grid Padding="35,0,0,10">
54+
<Grid.ColumnDefinitions>
55+
<ColumnDefinition Width="Auto"/>
56+
<ColumnDefinition Width="*" />
57+
</Grid.ColumnDefinitions>
58+
<Path Grid.Column="0"
59+
Data="{StaticResource PathData}"
60+
Fill="Black"
61+
Stroke="Gray"
62+
StrokeThickness="1"
63+
HeightRequest="40"
64+
WidthRequest="40"
65+
VerticalOptions="Center"/>
66+
67+
<StackLayout Grid.Column="1" Orientation="Vertical" HorizontalOptions="Start">
68+
<Label Text="Building Variance Indicators in .NET MAUI Toolkit Charts to Visualize Natural Gas Price Volatility"
69+
FontAttributes="Bold"
70+
FontSize="{OnPlatform Android=12,Default=16,iOS=12}" />
71+
<Label Text="Natural Gas Price Volatility Analysis 2014-2024"
72+
FontSize="{OnPlatform Android=10,Default=12,iOS=10}" />
73+
</StackLayout>
74+
</Grid>
75+
</syncfusion:SfCartesianChart.Title>
76+
77+
<syncfusion:SfCartesianChart.TooltipBehavior>
78+
<syncfusion:ChartTooltipBehavior Background="#7bb4eb" />
79+
</syncfusion:SfCartesianChart.TooltipBehavior>
80+
81+
<syncfusion:SfCartesianChart.XAxes>
82+
<syncfusion:DateTimeAxis Interval="2" IntervalType="Years" >
83+
<syncfusion:DateTimeAxis.Title>
84+
<syncfusion:ChartAxisTitle Text="Time Period (Years)" />
85+
</syncfusion:DateTimeAxis.Title>
86+
<syncfusion:DateTimeAxis.AxisLineStyle>
87+
<syncfusion:ChartLineStyle Stroke="#888888"/>
88+
</syncfusion:DateTimeAxis.AxisLineStyle>
89+
</syncfusion:DateTimeAxis>
90+
</syncfusion:SfCartesianChart.XAxes>
91+
92+
<syncfusion:SfCartesianChart.YAxes>
93+
<syncfusion:NumericalAxis Maximum="5.5" Interval="1" >
94+
<syncfusion:NumericalAxis.Title>
95+
<syncfusion:ChartAxisTitle Text="Price (USD per Million BTU)" />
96+
</syncfusion:NumericalAxis.Title>
97+
<syncfusion:NumericalAxis.MajorGridLineStyle>
98+
<syncfusion:ChartLineStyle StrokeWidth="{OnPlatform Android=0.0,iOS=0.0}" />
99+
</syncfusion:NumericalAxis.MajorGridLineStyle>
100+
</syncfusion:NumericalAxis>
101+
</syncfusion:SfCartesianChart.YAxes>
102+
103+
<syncfusion:SfCartesianChart.Series>
104+
<syncfusion:ColumnSeries ItemsSource="{Binding Data}"
105+
Spacing="{OnPlatform Android=0.5,iOS=0.5}"
106+
XBindingPath="Year"
107+
YBindingPath="YValue"
108+
EnableTooltip="True"
109+
Fill="#7bb4eb"
110+
ShowDataLabels="False"
111+
TooltipTemplate="{StaticResource tooltipTemplate}">
112+
</syncfusion:ColumnSeries>
113+
<local:RangeColumnSeriesExt ItemsSource="{Binding Data}"
114+
XBindingPath="Year"
115+
High="High"
116+
Low="Low" />
117+
</syncfusion:SfCartesianChart.Series>
118+
</syncfusion:SfCartesianChart>
119+
</Border>
120+
</ContentPage>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+

2+
namespace VarianceIndicators
3+
{
4+
public partial class MainPage : ContentPage
5+
{
6+
public MainPage()
7+
{
8+
InitializeComponent();
9+
}
10+
}
11+
}

VarianceIndicators/MauiProgram.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
using Microsoft.Extensions.Logging;
2+
using Syncfusion.Maui.Toolkit.Hosting;
3+
4+
namespace VarianceIndicators
5+
{
6+
public static class MauiProgram
7+
{
8+
public static MauiApp CreateMauiApp()
9+
{
10+
var builder = MauiApp.CreateBuilder();
11+
builder
12+
.UseMauiApp<App>()
13+
.ConfigureSyncfusionToolkit()
14+
.ConfigureFonts(fonts =>
15+
{
16+
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
17+
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
18+
});
19+
20+
#if DEBUG
21+
builder.Logging.AddDebug();
22+
#endif
23+
24+
return builder.Build();
25+
}
26+
}
27+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace VarianceIndicators
2+
{
3+
public class ChartDataModel
4+
{
5+
public DateTime Year { get; set; }
6+
public double YValue { get; set; }
7+
public double High { get; set; }
8+
public double Low { get; set; }
9+
}
10+
}

0 commit comments

Comments
 (0)