Good Day Everyone. I'm creating a Xamarin.Forms Portable Application. And I'm currently coding a PieChart (OxyPlot) that contains static data.
What I want to do is to have a Dynamic Data in every Pie Slice I have. Meaning, the data should come from my database.
I'm already able to retrieved data from my database and display it as a List in the Mobile Application that I'm creating using Web Api like this one :
ClientListPage.xaml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="XamarinFormsDemo.Views.ClientListPage"
xmlns:ViewModels="clr-namespace:XamarinFormsDemo.ViewModels;assembly=XamarinFormsDemo"
xmlns:controls="clr-namespace:ImageCircle.Forms.Plugin.Abstractions;assembly=ImageCircle.Forms.Plugin.Abstractions"
BackgroundImage="bg3.jpg"
Title="Client List1">
<StackLayout>
<SearchBar Placeholder="Search" Text="{Binding Keyword}" SearchCommand="{Binding SearchCommand}" x:Name="txtSearch" />
<ListView ItemsSource="{Binding CustomerList, Mode=TwoWay}"
HasUnevenRows="True"
IsPullToRefreshEnabled="True"
x:Name="listView">
<ListView.ItemTemplate>
<DataTemplate>
<ViewCell>
<Grid Padding="10" RowSpacing="10" ColumnSpacing="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<controls:CircleImage Source="icon.png"
HeightRequest="66"
HorizontalOptions="CenterAndExpand"
Aspect="AspectFill"
WidthRequest="66"
Grid.RowSpan="2"
/>
<Label Grid.Column="1"
Text="{Binding CUSTOMER_NAME}"
TextColor="#24e97d"
FontSize="24"/>
<Label Grid.Column="1"
Grid.Row="1"
Text="{Binding CUSTOMER_CODE}"
TextColor="White"
FontSize="18"
Opacity="0.6"/>
<Label Grid.Column="1"
Grid.Row="2"
Text="{Binding CUSTOMER_MOBILE_NUMBER}"
TextColor="White"
FontSize="18"
Opacity="0.6"/>
<Label Grid.Column="1"
Grid.Row="3"
Text="{Binding CUSTOMER_EMAIL_ADDRESS}"
TextColor="White"
FontSize="18"
Opacity="0.6"/>
</Grid>
</ViewCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackLayout>
</ContentPage>
ClientListPage.xaml.cs
using Newtonsoft.Json;
using OxyPlot;
using OxyPlot.Series;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using XamarinFormsDemo.Models;
using XamarinFormsDemo.ViewModels;
namespace XamarinFormsDemo.Views
{
public partial class ClientListPage : ContentPage
{
CustomerVM viewModel;
public ClientListPage()
{
NavigationPage.SetHasNavigationBar(this, true);
InitializeComponent();
viewModel = new CustomerVM();
BindingContext = viewModel;
}
async override protected void OnAppearing()
{
base.OnAppearing();
var json = await GetCustomerAsync();
var customers = JsonConvert.DeserializeObject<Customer[]>(json);
foreach (Customer c in customers)
viewModel.CustomerList.Add(c);
}
async Task<string> GetCustomerAsync()
{
var client = new HttpClient();
client.BaseAddress = new Uri("http://192.168.1.11:50857/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync("api/Customer");
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else return response.ReasonPhrase;
}
}
}
CustomerVM.cs
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Xamarin.Forms;
using XamarinFormsDemo.Models;
using XamarinFormsDemo.Services;
using XamarinFormsDemo.Views;
namespace XamarinFormsDemo.ViewModels
{
public class CustomerVM : INotifyPropertyChanged
{
private ObservableCollection<Customer> _customerList; // keep all customers
private ObservableCollection<Customer> _searchedCustomerList; // keep a copy for searching
private Customer _selectedCustomer = new Customer();
private string _keyword = "";
public string Keyword
{
get
{
return _keyword;
}
set
{
this._keyword = value;
// while keyword changed we filter Employees
//Filter();
}
}
public ObservableCollection<Customer> CustomerList
{
get
{
return _customerList;
}
set
{
_customerList = value;
OnPropertyChanged();
}
}
public CustomerVM()
{
CustomerList = new ObservableCollection<Customer>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
But this time, I need to do it in a PieChart.
I really don't have an idea how to do this. But I think the way I retrieved the data from my WEB API above has similarity on how am I going to do this in my chart. Hope you can help me.
Thanks a lot. These are some of my codes :
SalesPerProductViewModel.cs
using OxyPlot;
using OxyPlot.Annotations;
using OxyPlot.Axes;
using OxyPlot.Series;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using XamarinFormsDemo.Models;
namespace XamarinFormsDemo.ViewModels
{
public class SalesPerProductViewModel : INotifyPropertyChanged
{
private ObservableCollection<Sales> _salesList; // keep all customers
public ObservableCollection<Sales> SalesPerProductModel
{
get
{
return _salesList;
}
set
{
_salesList = value;
OnPropertyChanged();
}
}
public SalesPerProductViewModel()
{
SalesPerProductModel = new ObservableCollection<Sales>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
SalesPerProductPage.xaml (This is where the dynamic chart should be displayed)
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:oxy="clr-namespace:OxyPlot.Xamarin.Forms;assembly=OxyPlot.Xamarin.Forms"
xmlns:ViewModels="clr-namespace:XamarinFormsDemo.ViewModels;assembly=XamarinFormsDemo"
x:Class="XamarinFormsDemo.Views.SalesPerProductPage"
BackgroundImage="bg3.jpg"
Title="Sales Per Product">
<ContentPage.BindingContext>
<ViewModels:SalesPerProductViewModel/>
</ContentPage.BindingContext>
<Label Text="TOTAL SALES LABEL HERE!"
TextColor="#24e97d"/>
<oxy:PlotView Model="{Binding SalesPerProductModel}" />
</ContentPage>
SalesPerProductPage.xaml.cs
using OxyPlot;
using OxyPlot.Xamarin.Forms;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using XamarinFormsDemo.ViewModels;
using Xamarin.Forms;
using XamarinFormsDemo.Models;
using Newtonsoft.Json;
using System.Net.Http;
using System.Net.Http.Headers;
using OxyPlot.Series;
namespace XamarinFormsDemo.Views
{
public partial class SalesPerProductPage : ContentPage
{
private PlotModel modelForSales;
SalesPerProductViewModel viewModelforSales;
public SalesPerProductPage()
{
InitializeComponent();
viewModelforSales = new SalesPerProductViewModel();
BindingContext = viewModelforSales;
} //end of SalesPerProductPage()
async override protected void OnAppearing()
{
base.OnAppearing();
var json = await GetSalesPerProductAsync();
var salesPerProduct = JsonConvert.DeserializeObject<Sales[]>(json);
modelForSales = new PlotModel
{
Title = "Sales Per Product",
TitleColor = OxyColors.Teal,
TitleFontSize = 30,
TextColor = OxyColors.White,
DefaultFont = "Arial Black",
DefaultFontSize = 20
};
dynamic seriesP2 = new PieSeries { StrokeThickness = 2.0, InsideLabelPosition = 0.8, AngleSpan = 360, StartAngle = 0 };
foreach (Sales c in salesPerProduct)
{
seriesP2.Slices.Add(new PieSlice(c.PRODUCT_CODE, c.PRODUCT_ID) { IsExploded = false, Fill = OxyColors.Teal });
}
modelForSales.Series.Add(seriesP2);
this.SalesPerProductModel = modelForSales;
}
public PlotModel SalesPerProductModel { get; private set; }
async Task<string> GetSalesPerProductAsync()
{
var client = new HttpClient();
client.BaseAddress = new Uri("http://192.168.1.11:50857/");
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
HttpResponseMessage response = await client.GetAsync("api/Sales");
if (response.IsSuccessStatusCode)
{
return await response.Content.ReadAsStringAsync();
}
else return response.ReasonPhrase;
}
}
}
Sales.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace XamarinFormsDemo.Models
{
public class Sales
{
public int Id { get; set; }
public int ORDER_ID { get; set; }
public int ORDER_DETAILS_ID { get; set; }
public int PRODUCT_ID { get; set; }
public string PRODUCT_CODE { get; set; }
public string NET_AMOUNT { get; set; }
}
}
When you parsed your json and populated the list to feed into the listview, you can do the same for populating the series in your viewmodel. Once you have a list of Customer objects, go through them and use their data to create the pie slices as necesarry.
Something among the lines of:
I do not know what your Customer class looks like, what values you want to show, etc. So keep that in mind. This is just an example of what I mean. Also, structure your code so you only make the Http call once, then use that list of customers for both the list and the chart, don't download the data twice just to show it in two different ways.
You're assigning your
PlotModel
to a local variable. You must assign it to yourViewModel
. Here's your refactored working code:SalesPerProductPage.xaml:
SalesPerProductPage.xaml.cs:
SalesPerProductViewModel: