In this second post of our .NET MAUI “Stock App” blog series, we'll show you how to leverage the capabilities of the DevExpress Chart View for .NET MAUI and illustrate how to display historical data (daily open-close-high-low stock prices and transaction volume) within your next mobile app. If you are new to this series, please be sure to review our first post for background information on this app. The complete sample is available here: https://github.com/DevExpress-Examples/maui-stocks-mini.
The UI components used in this sample application (alongside other DevExpress .NET MAUI components) are available free of charge. To learn more about our free offer and reserve your free copy, please visit the following webpage: Xamarin.Forms UI Controls – Free Offer from DevExpress.
Prerequisites
- Install Visual Studio 2022 and the latest version of .NET MAUI. Review the following Microsoft help topic for more information: Installation.
- Register the https://nuget.devexpress.com/free/api or your personal NuGet feed within Visual Studio. If you are unfamiliar with NuGet packages, please review the following Microsoft help topic (to register a NuGet source): Install and manage packages in Visual Studio.
How to Reproduce This Application
The following step-by-step tutorial details how to reproduce this application. In this blog post, I will show how to use our Japanese Candlestick Chart.
The Historical Data Page
<ContentPage
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:dxc="clr-namespace:DevExpress.Maui.Charts;assembly=DevExpress.Maui.Charts"
xmlns:local="clr-namespace:Stocks"
x:Class="Stocks.HistoricalDataPage"
BackgroundColor="{DynamicResource BackgroundColor}">
<ContentPage.Content>
</ContentPage.Content>
</ContentPage>
Populate the Chart with Data
- StockPrices - daily open-close-high-low stock prices.
- RangeStart and RangeEnd - specify the visible date range in the chart. The chart displays data for the last 60 days. Users can scroll the chart to explore all historical price data.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Stocks {
public class HistoricalDataViewModel {
public ItemViewModel Item { get; set; }
public IList<StockPrice> StockPrices { get; set; }
public DateTime RangeStart { get; set; }
public DateTime RangeEnd { get; set; }
public HistoricalDataViewModel(ItemViewModel item) {
Item = item;
Symbol symbol = Data.Symbols.Where(s => s.Ticker == this.Item.Ticker).First();
RangeStart = symbol.Prices.First().Date;
RangeEnd = RangeStart.AddDays(-60);
StockPrices = new List<StockPrice>();
foreach(StockPrice price in symbol.Prices) {
StockPrices.Add(price);
}
}
}
}
Update the Historical Data Page Markup
We can now update the historical data page so it displays data from the view model. We set the ContentPage.BindingContext property to a view model object in the page constructor.
using Microsoft.Maui.Controls;
namespace Stocks {
public partial class HistoricalDataPage : ContentPage {
public HistoricalDataPage(HistoricalDataViewModel viewModel) {
InitializeComponent();
BindingContext = viewModel;
Title = viewModel.Item.Ticker;
}
}
}
At the top of the page, the app displays company name and the last price. Below company name, the app displays a chart. We place these elements within a grid layout.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="115"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
</Grid>
The first grid row contains company name and the last price. We use labels and images to display this information.
<StackLayout
Grid.Row="0" Grid.Column="0"
BackgroundColor="Transparent"
Orientation="Vertical"
HorizontalOptions="StartAndExpand"
VerticalOptions="CenterAndExpand"
Spacing="0"
Margin="12">
<Label
Text="{Binding Item.CompanyName}"
TextColor="{DynamicResource TextColor}"
FontSize="Subtitle"
Margin="0,0,12,0"/>
<StackLayout
Orientation="Horizontal"
Spacing="0"
HorizontalOptions="StartAndExpand">
<Label
Text="{Binding Item.ClosePrice, StringFormat='{0:0.00}'}"
TextColor="{DynamicResource TextColor}"
FontSize="Title"
Margin="0,0,12,0"
VerticalOptions="End"
VerticalTextAlignment="End"
LineBreakMode="TailTruncation"/>
<Image
WidthRequest="18"
HeightRequest="18"
HorizontalOptions="End"
Margin="0,0,3,0"
Source="{Binding Item.Change,
Converter={local:DoubleToImageSourceConverter
PositiveValue='quote_arrow_up.svg',
NegativeValue='quote_arrow_down.svg',
ZeroValue='not_changed.svg'}}"
VerticalOptions="End">
<Image.WidthRequest>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="Android" Value="20"/>
<On Platform="iOS" Value="24"/>
</OnPlatform>
</Image.WidthRequest>
<Image.HeightRequest>
<OnPlatform x:TypeArguments="x:Double">
<On Platform="Android" Value="20"/>
<On Platform="iOS" Value="24"/>
</OnPlatform>
</Image.HeightRequest>
</Image>
<Label
Text="{Binding Item.Change, StringFormat='{0:+0.00;-0.00;0.00}'}"
TextColor="{Binding Item.Change,
Converter={local:DoubleToColorConverter
PositiveValue='RisingValueColor',
NegativeValue='FallingValueColor',
ZeroValue='TextColor'}}"
HorizontalOptions="End"
VerticalOptions="End"
FontSize="Caption"
Margin="3,0"/>
<Label
Text="{Binding Item.ChangePercent, StringFormat='{0:(+0.00%);(-0.00%);(0.00%)}'}"
TextColor="{Binding Item.Change,
Converter={local:DoubleToColorConverter
PositiveValue='RisingValueColor',
NegativeValue='FallingValueColor',
ZeroValue='TextColor'}}"
HorizontalOptions="End"
VerticalOptions="End"
Margin="3,0,0,0"
FontSize="Caption"/>
</StackLayout>
<Label
Text="{Binding Item.Date, StringFormat='Date: {0:d}'}"
TextColor="{DynamicResource SecondaryTextColor}"
FontSize="Caption"/>
</StackLayout>
We place the chart in the second row.
<dxc:ChartView
Theme="Dark"
Grid.Row="1"
AxisXNavigationMode="ScrollingAndZooming"
AxisMaxZoomPercent="100000">
<dxc:ChartView.ChartStyle>
<dxc:ChartStyle
BackgroundColor="{StaticResource BackgroundColor}">
<dxc:ChartStyle.Padding>
<dxc:Padding Left="8" Right="8"/>
</dxc:ChartStyle.Padding>
</dxc:ChartStyle>
</dxc:ChartView.ChartStyle>
</dxc:ChartView>
Axes
- DateTimeAxisX.MeasureUnit - specifies detail level for date-time values.
- DateTimeAxisX.Range - specifies date range. We bind these settings to properties in the view model.
- NumericAxisY.AutoRangeMode - specifies whether the value range is calculated based on all or visible values.
- NumericAxisY.Label - specifies label position and string format.
- NumericAxisY.Style - specifies grid line visibility and colors.
<dxc:ChartView.AxisX>
<dxc:DateTimeAxisX
x:Name="axisX"
EmptyRangesVisible="False"
MeasureUnit="Day">
<dxc:DateTimeAxisX.Range>
<dxc:DateTimeRange
SideMargin="3"
VisualMin="{Binding RangeStart}"
VisualMax="{Binding RangeEnd}"/>
</dxc:DateTimeAxisX.Range>
</dxc:DateTimeAxisX>
</dxc:ChartView.AxisX>
<dxc:ChartView.AxisY>
<dxc:NumericAxisY
AlwaysShowZeroLevel="False"
AutoRangeMode="VisibleValues">
<dxc:NumericAxisY.DisplayPosition>
<dxc:AxisDisplayPositionFar/>
</dxc:NumericAxisY.DisplayPosition>
<dxc:NumericAxisY.Layout>
<dxc:AxisLayout Anchor1="0.333" Anchor2="1.0" />
</dxc:NumericAxisY.Layout>
<dxc:NumericAxisY.Label>
<dxc:AxisLabel Position="Inside" TextFormat="$#.#"/>
</dxc:NumericAxisY.Label>
<dxc:NumericAxisY.Style>
<dxc:AxisStyle
LineVisible="False"
MajorGridlinesVisible="True"
MajorGridlinesColor="{StaticResource SeparatorColor}"/>
</dxc:NumericAxisY.Style>
</dxc:NumericAxisY>
</dxc:ChartView.AxisY>
Japanese Candlestick Chart
The CandleStickSeries contains open-close-high-low stock prices. The CandleStickSeries.Data property is set to a SeriesDataAdapter object. This object interprets bound data source fields. To specify data source fields with data, we use ValueDataMember objects. Review the following topic for more information: Data Adapters.
<dxc:ChartView.Series>
<dxc:CandleStickSeries>
<dxc:CandleStickSeries.Data>
<dxc:SeriesDataAdapter
DataSource="{Binding StockPrices}"
ArgumentDataMember="Date">
<dxc:ValueDataMember Type="Open" Member="Open"/>
<dxc:ValueDataMember Type="High" Member="High"/>
<dxc:ValueDataMember Type="Low" Member="Low"/>
<dxc:ValueDataMember Type="Close" Member="Close"/>
</dxc:SeriesDataAdapter>
</dxc:CandleStickSeries.Data>
</dxc:CandleStickSeries>
</dxc:ChartView.Series>
We assign a CandleStickSeriesStyle object to the CandleStickSeries.Style property to specify candlestick-related appearance settings.
<dxc:CandleStickSeries.Style>
<dxc:CandleStickSeriesStyle
RisingFill="{StaticResource RisingValueColor}"
RisingStroke="{StaticResource RisingValueColor}"
FallingFill="{StaticResource FallingValueColor}"
FallingStroke="{StaticResource FallingValueColor}"/>
</dxc:CandleStickSeries.Style>
Bar Chart
The BarSeries display data as bars. We use Bar charts to display daily stock volumes.
<dxc:BarSeries>
<dxc:BarSeries.Data>
<dxc:SeriesDataAdapter DataSource="{Binding StockPrices}"
ArgumentDataMember="Date">
<dxc:ValueDataMember Type="Value"
Member="Volume" />
</dxc:SeriesDataAdapter>
</dxc:BarSeries.Data>
<dxc:BarSeries.Style>
<dxc:BarSeriesStyle
Fill="{StaticResource SymbolDetailPage_VolumeChartColor}"
Stroke="{StaticResource SymbolDetailPage_VolumeChartColor}"/>
</dxc:BarSeries.Style>
</dxc:BarSeries>
- LabelValueNotation - this property is set to an AxisLabelEngineeringNotation object. The chart also supports AxisLabelScientificNotation.
- Layout - this property is set to an AxisLayout object that specifies axis size and position on the chart.
- DisplayPosition - this property allows you to position the axis at the near or far edge, or specify relative or absolute position.
- Style - this property is set to an AxisStyle object that specifies axis and grid line visibility and colors.
<dxc:BarSeries.AxisY>
<dxc:NumericAxisY
AutoRangeMode="VisibleValues">
<dxc:NumericAxisY.LabelValueNotation>
<dxc:AxisLabelEngineeringNotation/>
</dxc:NumericAxisY.LabelValueNotation>
<dxc:NumericAxisY.Layout>
<dxc:AxisLayout Anchor1="0" Anchor2="0.333" />
</dxc:NumericAxisY.Layout>
<dxc:NumericAxisY.DisplayPosition>
<dxc:AxisDisplayPositionFar/>
</dxc:NumericAxisY.DisplayPosition>
<dxc:NumericAxisY.Label>
<dxc:AxisLabel Position="Inside" TextFormat="$#">
<dxc:AxisLabel.Style>
<dxc:AxisLabelStyle>
<dxc:AxisLabelStyle.TextStyle>
<dxc:TextStyle Color="{StaticResource TextColor}"/>
</dxc:AxisLabelStyle.TextStyle>
</dxc:AxisLabelStyle>
</dxc:AxisLabel.Style>
</dxc:AxisLabel>
</dxc:NumericAxisY.Label>
<dxc:NumericAxisY.Style>
<dxc:AxisStyle
LineVisible="False"
MajorGridlinesVisible="True"
MajorGridlinesColor="{StaticResource SeparatorColor}"/>
</dxc:NumericAxisY.Style>
</dxc:NumericAxisY>
</dxc:BarSeries.AxisY>
Navigation Between Two Pages
When a user taps a company in the list on the main page, the application displays historical data for that company on the second page. Let's wrap the main page in a NavigationPage to support navigation from the main page to the second page (and back). Update the App.xaml.cs file as follows:
using Microsoft.Maui.Controls;
using Application = Microsoft.Maui.Controls.Application;
namespace Stocks {
public partial class App : Application {
public App() {
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
}
}
In the MainPage.xaml file and the code-behind, handle the DXCollectionView.Tap event as follows:
using DevExpress.Maui.CollectionView;
private void DXCollectionView_Tap(object sender, CollectionViewGestureEventArgs e) {
var item = (ItemViewModel)e.Item;
var historicalDataViewModel = new HistoricalDataViewModel(item);
Navigation.PushAsync(new HistoricalDataPage(historicalDataViewModel));
}
Run the Application
Let’s execute the application once more. Users can now tap a company name on the main page and analyze the company's historical data on the second page.