跳至正文
首页 » 博客 » Animating Multiple Data Sources in Xamarin.Forms Data Chart

Animating Multiple Data Sources in Xamarin.Forms Data Chart

导言

您是否曾经想要创建仪表板应用程序或学习如何可视化大型数据集?如果是这样,这篇博客文章将向您展示如何创建一个应用程序,通过在金字塔图表中按年龄分组的人口变化以及在径向图表中按世代分组的人口变化来可视化美国人口。

请注意,上述动画并不表示实际的图表动画,其中数据动画更快,并且在数据更新之间具有平滑的帧插值。

仪表板应用程序是一个伟大的数据可视化工具,用于呈现大型和复杂的数据集。您可以使用使用数据的UI控件构建这些类型的应用程序,并以易于理解的视图显示这些应用程序。XamDataChart是高度灵活的跨平台控件,您可以配置它并将其与60个内置视图的任意组合一起使用,以可视化Xamarin.Forms应用程序中的各种数据类型。此控件是Ultimate UI for Xamarin的一部分,你可以免费使用。

正在运行的应用程序

您可以在此GitHub存储库中获取我们将在本博客文章中构建的应用程序的完整源代码。打开应用程序后,您需要确保在本地存储库中具有我们的试用版或RTM Nuget软件包,并为Infragistics组件还原Nuget软件包

使用说明

以下各节将提供有关使用XamDataChart控件创建仪表板应用程序的说明。

1.创建数据模型

首先,我们需要实现一个数据模型,该模型将存储有关人口的人口统计信息,例如年龄,性别和世代范围。PopulationData类是利用JSON属性从JSON字符串反序列化数据的数据模型的一个示例。

[JsonObject(MemberSerialization.OptIn)]
公共 PopulationData:INotifyPropertyChanged
{
#区域构造函数
公共 PopulationData()
{
}
公共 PopulationData(智力年龄,智力年,男性,女性)
{
。Age = 年龄;
。Year = 年;
。男性 = 男性;
。女性 = 女性;
更新 ();
}

公共 PopulationData(智力开始,智力结束)
{
。GenerationStart = 开始;
。GenerationEnd = 结束;
}
#端区

#区域Json属性
[JsonProperty(PropertyName =“女性”)]
公共 女性 {获取;设置;}
[JsonProperty(PropertyName =“男性”)]
公共 男性 {获取;设置;}
[JsonProperty(PropertyName ="年龄")]
公共 智力年龄 {获取;设置;}
[JsonProperty(PropertyName ="年")]
公共 智力年 {获取;设置;}
#端区

公共 事件PropertyChangedEventHandler PropertyChanged;
公共 无效 OnPropertyChanged(<span style="color:#c678dd;"> 字符串名称)
{
PropertyChanged?。invoke (thisnew PropertyChangedEventArgs(name));
}


公共字符串GenerationRange
{
get {return GenerationStart "-" GenerationEnd; }
}
public double Total {get; private set; }

///
/// 通过PropertyChanged通知
获取或设置MLN中的女性人口
/// 公共FemalesInMillions
{
get {return_ FemalesInMillions; }
{if (_femalesinmillions = = value) return; _Femalesinmillions = value; OnPropertyChanged (" FemalesInMillions "); }
}
private double _FemalesInMillions;

///
/// 通过PropertyChanged通知
获取或设置MLN中的男性人口 ///
公共MalesInMillions
{
get {return _MalesInMillions; }
{if (_malesinmillions = = value) 返回; _Malesinmillions = value; OnPropertyChanged(" MalesInMillions "); }
}
private double _MalesInMillions;
<br # endregion>
public int BirthYear {get {return Year - Age; } }
public int GenerationStart {get; set; }
public int GenerationEnd {get; set; }

公共无效更新 ()
{
if (double .IsNaN (男性) | | double .IsNaN (女性)) 返回;

总计 = 男性女性;

// 将男性转换为负值以将其绘制为与女性相反的值MalesInMillions = -Math.Round (男性/ 10000001 );
FemalesInMillions = Math.Round (女性/ 10000001 );
OnPropertyChanged( "Total" );
}
公共PopulationData克隆 ()
{
返回PopulationData (年龄、年份、男性、女性);
}
}

请注意,此数据模型实现INotifyPropertyChanged接口,该接口可确保对属性值的更改将引发PropertyChanged事件,从而通知数据图表控件对这些更改进行动画处理。

2.创建视图模型

接下来,我们需要一个视图模型来连接到www.api.population.io服务,检索人口数据集,最后将JSON字符串反序列化为PopulationData对象列表:

公共  PopulationViewModel:INotifyPropertyChanged
{
公共 事件PropertyChangedEventHandler PropertyChanged;
公共 无效 OnPropertyChanged(字符串姓名)
{
PropertyChanged?.Invoke (,新建PropertyChangedEventArgs(name));

如果(名称 = ="IsUpdatingData"& &。IsUpdatingData)
{
Device.StartTimer(TimeSpan.FromMilliseconds (。UpdateInterval), TimerCallback);
}
}
公共 PopulationViewModel()
{
PopulationLookup =新建字典 <智力,列表> ();

// 使用每个人口年龄的默认值初始化人口
PopulationByAge =新建清单 ();
用于(var年龄 =0;年龄 <= span = "" class = "hljs-number" style = '颜色: # d19a66;' 数据-mce-style = '颜色: # d19a66;'>100; 年龄)
{
PopulationByAge.Add (新建人口数据 (年龄、历史记录、0,0));
}

// 用每一代人口的默认值初始化人口
PopulationByGen =新建清单 ();
PopulationByGen.Add (新建PopulationData (1820,1900));
PopulationByGen.Add (新建PopulationData (1900,1930));
PopulationByGen.Add (新建PopulationData (1930,1945));
PopulationByGen.Add (新建PopulationData (1945,1965));
& nbsp; PopulationByGen.Add( PopulationData( 19651980 ));
PopulationByGen.Add( PopulationData( 19802000 ));
PopulationByGen.Add( PopulationData( 20002025 ));
PopulationByGen.Add( new PopulationData( 20252050 ));
PopulationByGen.Add( new PopulationData( 20502075 ));
PopulationByGen.Add( new PopulationData( 20752100 ));

GetHistory();
}
受保护的Dictionary< int ,List> PopulationLookup;

private List _populationbyage;
public List PopulationByAge
{
get {return_populationbyage; }
{if (_populationbyage = = value) 返回; _Populationbyage = value; OnPropertyChanged (" PopulationByAge" ); }
}
私有列表 _populationbygen;
公共列表PopulationByGen
{
get {return_populationbygen; }
set {if (_populationbygen = = value) return; _Populationbygen = value; "PopulationByGen"; }
}
private int_updateinterval = 750;
public int UpdateInterval
{
get {return_updateinterval; }
set {if (_updateinterval = = value) 返回 ;_Updateinterval = ; OnPropertyChanged(<span style = "color:#98c379;">" UpdateInterval "); }
}
private bool _IsUpdatingData = true;
public bool IsUpdatingData
{
get {return _IsUpdatingData; }
{if (_isupdatingdata = = value) 返回; _Isupdatingdata = value; OnPropertyChanged (" IsUpdatingData " ); OnPropertyChanged( " IsEditableYear " ); }
}

公共bool IsEditableYear
{
get {return!this .IsUpdatingData; }
}
private int _CurrentYear;
public int CurrentYear
{
get {return _CurrentYear; }

{
if (value = = _currentyear) return;
if (value > HistoryStop) value = HistoryStart;
if (value < HistoryStart) value = HistoryStart;
if (PopulationLookup.ContainsKey( value ))
{
_CurrentYear = value;
OnPropertyChanged( " CurrentYear " );
UpdateData();
}
}
}

protected int HistoryStop = 2100;
// 控制从人口服务检索的数据范围的值;受保护 智力HistoryStart =1950;
受保护 智力HistoryInterval =10;
受保护HttpClient客户端 =新建HttpClient();
受保护 字符串来源 ="http:// api.population.io/1.0/population/{0}/United States/?format = json";

公共 异步任务 <列表> GetData (智力年)
{
varurl =字符串。格式 (来源,年份);
varstr =等待Client.GetStringAsync(url);
var人口 =等待任务。运行 (
() => JsonConvert.DeserializeObject<List>(str));

foreach(var项目人口)
{
item.Update();
}
返回人口;
}
私人 异步 无效 GetHistory()
{
用于(智力year = HistoryStart; year <= historystop = "" year = "" historyinterval = "" br = ""> {
var数据 =等待GetData (年);

PopulationLookup.Add (年份,数据);
}
当前年份 =1950;
设备.StartTimer (新建时间跨度 (0,0,0,0,200), TimerCallback);
}
私人 bool TimerCallback()
{
CurrentYear = HistoryInterval;
<span style="color:# c678dd;">return IsUpdatingData;
}
private void UpdateData ()
{
for (int i = 0; i < PopulationLookup[CurrentYear].Count;i )
{
PopulationByAge[i].Age = PopulationLookup[CurrentYear][i].Age;
PopulationByAge[i].Year = PopulationLookup[CurrentYear][i].Year;
PopulationByAge[i].Males = PopulationLookup[CurrentYear][i].Males;
PopulationByAge[i].Fomeroes = PopulationLookup[CurrentYear][i].Fomeroes;
PopulationByAge[i].Update();
}
foreach (PopulationByGen中的var生成)
{
var maemes = 0.0
var雌性 = 0.0
foreach (PopulationLookup[CurrentYear] 中的var人口)
{
if (人口.出生年> generation.GenerationStart & &
人口.出生年 <= generation =" "generationend =" "br =" "> {
女性 = 人口.女性;
男性 = 人口。男性;
}
}
世代。男性百万 = 男性/ 1000000;
世代。女性百万 = 女性/ 1000000;
}
}
}

3.创建金字塔图

随着后端的实现,我们可以转移到可视化和动画数据的有趣部分。XamDataChart控件支持60多种类型的系列。您可以使用两个彼此相邻堆叠的BarSeries视图创建金字塔图,以显示0到100岁之间的美国人口。

<ig:XamDataChart 标题=“按年龄划分的美国人口”
TitleFontSize="12" TitleTopMargin="10"
TitleTextColor="灰色"
GridMode="BeforeSeries" PlotAreaBackground="#4DE3E3E3"
图例="{x: 参考图例}">

<ig:XamDataChart.Axes>
<ig:CategoryYAxis x: 名称="重心"
间隙="0.5"
重叠="1"
大中风="# BB808080" 行程="# BB808080"
MajorStrokeThickness="0" StrokeThickness="0"
滴答长度="5"
滴答声="# BB808080"
TickStrokeThickness="1"
& nbsp;ItemsSource="{绑定PopulationByAge}"
标签="年龄"/>

<ig:NumericXAxis x: 名称="BarXAxis"
大中风="# BB808080" 行程="# BB808080"
MajorStrokeThickness="1" StrokeThickness="1"
滴答长度="5" 滴答声="# BB808080"
TickStrokeThickness="1"
最小值="-5"
最大值="5"
时间间隔="1"/>

ig:XamDataChart.Axes>
<ig:XamDataChart.Series>
<ig:BarSeries ItemsSource="{绑定PopulationByAge}"
XAxis=<span style="color:#98c379;">"{x: 参考BarXAxis}"

YAxis="{x:Reference BarYAxis}"
TransitionDuration="200"
刷子="# 86009DFF" 大纲="# 86009DFF"
厚度="0" 标题=“男性”
ValueMemberPath="MalesInMillions"/>
<ig:BarSeries ItemsSource="{绑定PopulationByAge}"
XAxis="{x:Reference BarXAxis}"
YAxis="{x:Reference BarYAxis}"
TransitionDuration="200"
刷子="# A1C159F7" 大纲="# A1C159F7"
厚度="0" 标题=“女性”
ValueMemberPath=“百万女性”/>

ig:XamDataChart.Series>
ig:XamDataChart>

实施后,金字塔图将显示按年龄分组的男性和女性人群之间的差异,并动态显示数据随时间的变化。

请注意,可以通过在单个BarSeries上设置TransitionDuration属性来控制数据动画的持续时间。但是,此持续时间应始终小于或等于视图模型更新数据的时间间隔,否则,数据动画将不会平滑。

4.创建径向图

XamDataChart控件还可以使用RadialSeries类型之一显示绘图区域中心点周围的分组数据点,如下面的代码段所示:

<ig:XamDataChart Grid.Row="1"
GridMode="BehindSeries"
标题=“美国世代人口”
TitleFontSize="12" TitleTopMargin="10"
TitleTextColor="灰色">

<ig:XamDataChart.Axes>
<ig:CategoryAngleAxis x: 名称="AngleAxis"
大中风="# BB808080"
MajorStrokeThickness=".5"
滴答长度="5" 滴答声="# BB808080"
TickStrokeThickness="1"
ItemsSource="{绑定PopulationByGen}"
标签="GenerationRange"/>

<ig:NumericRadiusAxis x: 名称="RadiusAxis"
大中风="# BB808080"
& nbsp;MajorStrokeThickness=".5"
滴答长度="5" 滴答声="# BB808080"
TickStrokeThickness="1"
最小值="0" 时间间隔="40"
最大值="80"
InnerRadiusExtentScale="0.2"
RadiusExtentScale="0.7"/>

ig:XamDataChart.Axes>
<ig:XamDataChart.Series>
<ig: 放射性系列 ItemsSource="{绑定PopulationByGen}"
AngleAxis="{x: 参考AngleAxis}"
ValueAxis="{x: 参考RadiusAxis}"
TransitionDuration="200"
刷子="# A1C159F7" 大纲="# A1C159F7" 厚度="0"
ValueMemberPath=“百万女性”/>

<ig: 放射性系列 ItemsSource="{绑定PopulationByGen}"
AngleAxis="{x: 参考AngleAxis}"
ValueAxis="{x: 参考RadiusAxis}"
TransitionDuration="200"
刷子="# 86009DFF" 大纲="# 86009DFF" 厚度="0"
ValueMemberPath="MalesInMillions"/>

ig:XamDataChart.Series>
ig:XamDataChart>

实施后,径向图将显示按其生日分组的男性和女性人群之间的差异,并动态显示数据随时间的变化。

5.创建图例

如果没有标识数据集的图例,则没有完整的图表。图例是一个简单的控件,我们可以将其覆盖在XamDataChart控件上或放置在图表之外的任何位置:

<ig: 图例 x: 名称="图例" Grid.Row="1" 边距="5"
垂直选项="星号"
HorizontalOptions="EndAndExpand"/>

最后的想法

上面的代码片段显示了实现仪表板应用程序的最重要元素,该应用程序可以随着时间的推移可视化和动画显示美国人口。您可以从此GitHub存储库下载此应用程序的完整源代码。我希望您发现这篇博客文章很有趣,并受到启发,使用Ultimate UI for Xamarin产品创建自己的仪表板应用程序。

快乐编码,

马丁 </p