Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions App.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version = "1.0" encoding = "UTF-8" ?>
<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DualBarChartDemo"
x:Class="DualBarChartDemo.App">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Resources/Styles/Colors.xaml" />
<ResourceDictionary Source="Resources/Styles/Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
</Application>
15 changes: 15 additions & 0 deletions App.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace DualBarChartDemo
{
public partial class App : Application
{
public App()
{
InitializeComponent();
}

protected override Window CreateWindow(IActivationState? activationState)
{
return new Window(new AppShell());
}
}
}
12 changes: 12 additions & 0 deletions AppShell.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="DualBarChartDemo.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:DualBarChartDemo"
Title="DualBarChartDemo">

<ShellContent
ContentTemplate="{DataTemplate local:MainPage}"
Route="MainPage" />
</Shell>
10 changes: 10 additions & 0 deletions AppShell.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace DualBarChartDemo
{
public partial class AppShell : Shell
{
public AppShell()
{
InitializeComponent();
}
}
}
69 changes: 69 additions & 0 deletions DualBarChartDemo.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net9.0-android;net9.0-ios;net9.0-maccatalyst</TargetFrameworks>
<TargetFrameworks Condition="$([MSBuild]::IsOSPlatform('windows'))">$(TargetFrameworks);net9.0-windows10.0.19041.0</TargetFrameworks>
<!-- Uncomment to also build the tizen app. You will need to install tizen by following this: https://github.com/Samsung/Tizen.NET -->
<!-- <TargetFrameworks>$(TargetFrameworks);net9.0-tizen</TargetFrameworks> -->

<!-- Note for MacCatalyst:
The default runtime is maccatalyst-x64, except in Release config, in which case the default is maccatalyst-x64;maccatalyst-arm64.
When specifying both architectures, use the plural <RuntimeIdentifiers> instead of the singular <RuntimeIdentifier>.
The Mac App Store will NOT accept apps with ONLY maccatalyst-arm64 indicated;
either BOTH runtimes must be indicated or ONLY macatalyst-x64. -->
<!-- For example: <RuntimeIdentifiers>maccatalyst-x64;maccatalyst-arm64</RuntimeIdentifiers> -->

<OutputType>Exe</OutputType>
<RootNamespace>DualBarChartDemo</RootNamespace>
<UseMaui>true</UseMaui>
<SingleProject>true</SingleProject>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>

<!-- Display name -->
<ApplicationTitle>DualBarChartDemo</ApplicationTitle>

<!-- App Identifier -->
<ApplicationId>com.companyname.dualbarchartdemo</ApplicationId>

<!-- Versions -->
<ApplicationDisplayVersion>1.0</ApplicationDisplayVersion>
<ApplicationVersion>1</ApplicationVersion>

<!-- To develop, package, and publish an app to the Microsoft Store, see: https://aka.ms/MauiTemplateUnpackaged -->
<WindowsPackageType>None</WindowsPackageType>

<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'ios'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'maccatalyst'">15.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'android'">21.0</SupportedOSPlatformVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</SupportedOSPlatformVersion>
<TargetPlatformMinVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'windows'">10.0.17763.0</TargetPlatformMinVersion>
<SupportedOSPlatformVersion Condition="$([MSBuild]::GetTargetPlatformIdentifier('$(TargetFramework)')) == 'tizen'">6.5</SupportedOSPlatformVersion>
</PropertyGroup>

<ItemGroup>
<!-- App Icon -->
<MauiIcon Include="Resources\AppIcon\appicon.svg" ForegroundFile="Resources\AppIcon\appiconfg.svg" Color="#512BD4" />

<!-- Splash Screen -->
<MauiSplashScreen Include="Resources\Splash\splash.svg" Color="#512BD4" BaseSize="128,128" />

<!-- Images -->
<MauiImage Include="Resources\Images\*" />
<MauiImage Update="Resources\Images\dotnet_bot.png" Resize="True" BaseSize="300,185" />

<!-- Custom Fonts -->
<MauiFont Include="Resources\Fonts\*" />

<!-- Raw Assets (also remove the "Resources\Raw" prefix) -->
<MauiAsset Include="Resources\Raw\**" LogicalName="%(RecursiveDir)%(Filename)%(Extension)" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="9.0.0" />
<PackageReference Include="Microsoft.Maui.Graphics.Text.Markdig" Version="9.0.90" />
<PackageReference Include="Syncfusion.Maui.Toolkit" Version="*"/>
</ItemGroup>

</Project>
117 changes: 117 additions & 0 deletions MainPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="DualBarChartDemo.MainPage"
xmlns:syncfusion="clr-namespace:Syncfusion.Maui.Toolkit.Charts;assembly=Syncfusion.Maui.Toolkit"
xmlns:local="clr-namespace:DualBarChartDemo">

<ContentPage.BindingContext>
<local:ChartViewModel />
</ContentPage.BindingContext>

<ContentPage.Resources>
<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>
</ContentPage.Resources>


<Border Margin="{OnPlatform Android=10,Default=25,iOS=10}"
StrokeThickness="1" Stroke="Black"
StrokeShape="RoundRectangle 15">

<syncfusion:SfCartesianChart HorizontalOptions="Fill"
VerticalOptions="Fill"
Margin="{OnPlatform Android=5,Default=15,iOS=5}">
<syncfusion:SfCartesianChart.Resources>
<DataTemplate x:Key="tooltipTemplate" >
<Border BackgroundColor="#7bb4eb"
Padding="8, 3, 8, 3"
Stroke="Transparent">
<StackLayout Orientation="Horizontal">
<Label Text="{Binding Item.Year, StringFormat='{}{0:yyyy}'}"
TextColor="Black"
FontSize="14"
HorizontalOptions="Center"
VerticalOptions="Center"/>
<Label Text=" : "
TextColor="Black"
FontAttributes="Bold"
FontSize="14"
HorizontalOptions="Center"
VerticalOptions="Center"/>
<BoxView WidthRequest="5"
BackgroundColor="Transparent"/>
<Label Text="{Binding Item.YValue}"
TextColor="Black"
FontSize="14"
HorizontalOptions="Center"
VerticalOptions="Center"/>
</StackLayout>
</Border>
</DataTemplate>
</syncfusion:SfCartesianChart.Resources>
<syncfusion:SfCartesianChart.Title>
<Grid Padding="35,0,0,10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Path Grid.Column="0"
Data="{StaticResource PathData}"
Fill="Black"
Stroke="Gray"
StrokeThickness="1"
HeightRequest="40"
WidthRequest="40"
VerticalOptions="Center"/>

<StackLayout Grid.Column="1" Orientation="Vertical" HorizontalOptions="Start">
<Label Text="Building Variance Indicators in .NET MAUI Toolkit Charts to Visualize Natural Gas Price Volatility"
FontAttributes="Bold"
FontSize="{OnPlatform Android=12,Default=16,iOS=12}" />
<Label Text="Natural Gas Price Volatility Analysis 2014-2024"
FontSize="{OnPlatform Android=10,Default=12,iOS=10}" />
</StackLayout>
</Grid>
</syncfusion:SfCartesianChart.Title>

<syncfusion:SfCartesianChart.TooltipBehavior>
<syncfusion:ChartTooltipBehavior Background="#7bb4eb" />
</syncfusion:SfCartesianChart.TooltipBehavior>

<syncfusion:SfCartesianChart.XAxes>
<syncfusion:DateTimeAxis Interval="2" IntervalType="Years" >
<syncfusion:DateTimeAxis.Title>
<syncfusion:ChartAxisTitle Text="Time Period (Years)" />
</syncfusion:DateTimeAxis.Title>
<syncfusion:DateTimeAxis.AxisLineStyle>
<syncfusion:ChartLineStyle Stroke="#888888"/>
</syncfusion:DateTimeAxis.AxisLineStyle>
</syncfusion:DateTimeAxis>
</syncfusion:SfCartesianChart.XAxes>

<syncfusion:SfCartesianChart.YAxes>
<syncfusion:NumericalAxis Maximum="5.5" Interval="1" >
<syncfusion:NumericalAxis.Title>
<syncfusion:ChartAxisTitle Text="Price (USD per Million BTU)" />
</syncfusion:NumericalAxis.Title>
</syncfusion:NumericalAxis>
</syncfusion:SfCartesianChart.YAxes>

<syncfusion:SfCartesianChart.Series>
<syncfusion:ColumnSeries ItemsSource="{Binding Data}"
XBindingPath="Year"
YBindingPath="YValue"
EnableTooltip="True"
Fill="#7bb4eb"
ShowDataLabels="False"
TooltipTemplate="{StaticResource tooltipTemplate}">
</syncfusion:ColumnSeries>
<local:RangeColumnSeriesExt ItemsSource="{Binding Data}"
XBindingPath="Year"
High="High"
Low="Low"
EnableTooltip="False" />
</syncfusion:SfCartesianChart.Series>
</syncfusion:SfCartesianChart>
</Border>
</ContentPage>
125 changes: 125 additions & 0 deletions MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
using Syncfusion.Maui.Toolkit.Charts;
using System.Collections.ObjectModel;
using Microsoft.Maui.Graphics;
using Font = Microsoft.Maui.Graphics.Font;
using Microsoft.Maui.Graphics.Text;

namespace DualBarChartDemo
{
public partial class MainPage : ContentPage
{

public MainPage()
{
InitializeComponent();
}

}

public partial class RangeColumnSeriesExt : RangeColumnSeries
{
protected override ChartSegment CreateSegment()
{
return new RangeColumnSegmentExt();
}
}

public partial class RangeColumnSegmentExt : RangeColumnSegment
{
protected override void Draw(ICanvas canvas)
{
// Call base layout first
base.OnLayout();

// Skip drawing if segment isn't properly laid out or data is missing
if (float.IsNaN(Left) || float.IsNaN(Top) || float.IsNaN(Right) || float.IsNaN(Bottom) ||
Series is not RangeColumnSeriesExt || Item is not ChartDataModel dataPoint)
return;

// Skip last segment or if there's no change
var itemsSource = Series.ItemsSource as ObservableCollection<ChartDataModel>;
if (Index == itemsSource?.Count - 1 || dataPoint.Low == dataPoint.High)
return;

// Calculate change direction and percentage
bool isIncreasing = dataPoint.Low < dataPoint.High;
double changePercent = Math.Abs((dataPoint.High - dataPoint.Low) / dataPoint.Low) * 100;

// Set color based on direction
Color arrowColor = isIncreasing ? Colors.Green : Colors.Red;

// Ensure we have a valid drawing canvas
if (canvas == null) return;

// Save canvas state before drawing
canvas.SaveState();

// Calculate center of column
float centerX = (Left + Right) / 2;

// Draw line first
canvas.StrokeColor = Colors.Black;
canvas.StrokeSize = 1f;
canvas.DrawLine(centerX, Top, centerX, Bottom);

// Draw arrow
float arrowSize = 6;
var arrowPath = new PathF();

// Draw arrow based on direction
if (isIncreasing)
{
arrowPath.MoveTo(centerX, Top);
arrowPath.LineTo(centerX - arrowSize, Top + arrowSize);
arrowPath.LineTo(centerX + arrowSize, Top + arrowSize);
}
else
{
arrowPath.MoveTo(centerX, Bottom);
arrowPath.LineTo(centerX - arrowSize, Bottom - arrowSize);
arrowPath.LineTo(centerX + arrowSize, Bottom - arrowSize);
}

arrowPath.Close();
canvas.FillColor = Colors.Black;
canvas.FillPath(arrowPath);

// Draw percentage text with clear formatting
string percentText = isIncreasing ?
$"+{changePercent:0}%" :
$"-{changePercent:0}%";

// Position text safely within visible area
float textY = isIncreasing ?
Math.Max(Top - 20, 15) :
Math.Min(Bottom + 20, Bottom + 40);

canvas.FontColor = arrowColor;
canvas.FontSize = 12;
canvas.Font = new Font("Arial", (int)FontWeight.Bold);
#if MACCATALYST
String boldPercentText = $"**{percentText}**";

// Use MarkdownAttributedTextReader to create styled text
IAttributedText attributedText = MarkdownAttributedTextReader.Read(boldPercentText);

// Draw text with proper positioning - works on all platforms
float textWidth = percentText.Length * 8; // approximate width
float textHeight = 16; // approximate height

// Setup color before drawing text
canvas.FontColor = arrowColor;

// Draw the text using common API that works on all platforms
canvas.DrawText(attributedText,
centerX - (textWidth / 2),
textY - (textHeight / 2),
textWidth, textHeight);
#else
canvas.DrawString(percentText,centerX,textY,HorizontalAlignment.Center);
#endif
canvas.RestoreState();

}
}
}
27 changes: 27 additions & 0 deletions MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using Microsoft.Extensions.Logging;
using Syncfusion.Maui.Toolkit.Hosting;

namespace DualBarChartDemo
{
public static class MauiProgram
{
public static MauiApp CreateMauiApp()
{
var builder = MauiApp.CreateBuilder();
builder
.UseMauiApp<App>()
.ConfigureSyncfusionToolkit()
.ConfigureFonts(fonts =>
{
fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
});

#if DEBUG
builder.Logging.AddDebug();
#endif

return builder.Build();
}
}
}
Loading