Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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();
}
}
}
68 changes: 68 additions & 0 deletions DualBarChartDemo.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
<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="Syncfusion.Maui.Toolkit" Version="1.0.5" />
</ItemGroup>

</Project>
127 changes: 127 additions & 0 deletions MainPage.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
<?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" >M14.958001,14.880005C11.712001,15.658997 8.1829996,17.106995 7.4849997,19.033997 7.2039995,19.811005 7.4359999,20.733002 8.1600008,21.779007L10.726001,19.212997 12.006,20.494003 9.6400003,22.858994C10.162,23.057999,10.645,23.169006,11.071,23.169006L11.072,23.169006C11.734,23.169006 12.251,22.944 12.748,22.436996 14.151,21.003006 14.794,17.789993 14.958001,14.880005z M17.013,12.417999L17,13.643005C16.977001,15.863998 16.656002,21.304001 14.177001,23.835999 13.31,24.720993 12.266,25.169006 11.072,25.169006 10.174001,25.169006 9.1820002,24.891998 8.1260004,24.373001L5.2629995,27.237 3.9819994,25.957001 6.729001,23.210007C5.4420004,21.492004 5.059001,19.860992 5.604001,18.354004 6.915,14.729996 13.179,13.158005 15.811,12.651001z M2,11L2,30 20,30 20,11z M2,2L2,8 20,8 20,2z M0,0L22,0 22,8.1670074 22,11 22,24.843994 25,21.311996 25,9 29.313,3.6880035 31.710999,6.125 28,10.796997 28,23 22,28.990005 22,32 0,32 0,11 0,8.1670074z</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-2023"
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.MajorTickStyle>
<syncfusion:ChartAxisTickStyle Stroke="Transparent" TickSize="0" />
</syncfusion:DateTimeAxis.MajorTickStyle>
<syncfusion:DateTimeAxis.AxisLineStyle>
<syncfusion:ChartLineStyle Stroke="#888888" StrokeWidth="1"/>
</syncfusion:DateTimeAxis.AxisLineStyle>
</syncfusion:DateTimeAxis>
</syncfusion:SfCartesianChart.XAxes>

<syncfusion:SfCartesianChart.YAxes>
<syncfusion:NumericalAxis Interval="1" >
<syncfusion:NumericalAxis.Title>
<syncfusion:ChartAxisTitle Text="Price (USD per Million BTU)" />
</syncfusion:NumericalAxis.Title>
<syncfusion:NumericalAxis.MajorTickStyle>
<syncfusion:ChartAxisTickStyle Stroke="Transparent" TickSize="0" />
</syncfusion:NumericalAxis.MajorTickStyle>
<syncfusion:NumericalAxis.AxisLineStyle>
<syncfusion:ChartLineStyle StrokeWidth="1" />
</syncfusion:NumericalAxis.AxisLineStyle>

</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}"
Label="Column Value">
</syncfusion:ColumnSeries>
<local:RangeColumnSeriesExt ItemsSource="{Binding Data}"
XBindingPath="year"
High="high"
Low="low"
EnableTooltip="False" />
</syncfusion:SfCartesianChart.Series>
</syncfusion:SfCartesianChart>
</Border>
</ContentPage>
98 changes: 98 additions & 0 deletions MainPage.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@

using Syncfusion.Maui.Toolkit.Charts;
using Syncfusion.Maui.Toolkit.Graphics.Internals;
using System.Collections.ObjectModel;
using Font = Microsoft.Maui.Graphics.Font;

namespace DualBarChartDemo
{
public partial class MainPage : ContentPage
{

public MainPage()
{
InitializeComponent();
}

}

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

public class RangeColumnSegmentExt : RangeColumnSegment
{
protected override void Draw(ICanvas canvas)
{
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
if (Index == (Series.ItemsSource as ObservableCollection<ChartDataModel>)?.Count - 1 ||
dataPoint.high == 0 || dataPoint.low == dataPoint.high)
return;

// Calculate change direction and percentage
bool isIncreasing = dataPoint.low < dataPoint.high;
double changePercent = dataPoint.low != 0 ?
((dataPoint.high - dataPoint.low) / Math.Abs(dataPoint.low)) * 100 : 0;

// Set color based on direction
Color arrowColor = isIncreasing ? Color.FromArgb("#1CD01C") : Colors.Red;

// Save canvas state
canvas.CanvasSaveState();

// Draw line with arrow
float centerX = (Left + Right) / 2;
canvas.StrokeColor = Colors.Black;
canvas.StrokeSize = 1f;
canvas.DrawLine(centerX, Top, centerX, Bottom);

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

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);

string percentText = isIncreasing ?
$"{Math.Abs(changePercent):0}%" :
$"- {Math.Abs(changePercent):0}%";

float textY = isIncreasing ? Top - 20 : Bottom + 20;

canvas.FontColor = arrowColor;
canvas.FontSize = 14;
var fontFamily = "Bold";
canvas.Font = new Font(fontFamily);

canvas.DrawString(percentText, centerX, textY, HorizontalAlignment.Center);

// Restore canvas state
canvas.CanvasRestoreState();
}
}
}
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();
}
}
}
16 changes: 16 additions & 0 deletions Model.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DualBarChartDemo
{
public class ChartDataModel
{
public DateTime year { get; set; }
public double yValue { get; set; }
public double high { get; set; }
public double low { get; set; }
}
}
Loading