WPF 图表控件LiveCharts的应用——室内监控可视化
·
需求
为了实现一个完整软件系统,必须具备一些基本的数据呈现控件,例如曲线图、柱状图、饼图等。本次的业务需求为:利用LiveCharts展示后台模拟的温度变化。像Winform里面,微软为我们提供了比较完整的Chart控件,但是在WPF组件中,就没有找到类似的控件,它的意图是让我们自己去实现。我们应该对当下的代码共享时代环抱感激,迄今位置有很多面向WPF的第三方控件库,大部分都是免费开源的。例如:OxyPlot、ModernuiCharts......以及我们今天的主角——LiveCharts。这是一个具备动画效果的图表控件。
首先上效果:
环境
Windows 10
Visual Studio 2019
.Net Framework 4.7.2
Ninject
LiveCharts
LiveCharts.Wpf
实现
1.通过NuGet引入Ninject、LiveCharts.Wpf、LiveCharts程序包。
解决方案资源管理器-->项目(右键)-->管理NuGet程序包
在“浏览”中搜索:
Ninject
LiveCharts
LiveCharts.Wpf
并安装,完成之后如下图所示:
2.在XAML中引用LiveCharts控件的程序集。
在ChamberView.xaml文件的节点上添加对程序集的应用,如下:
<local:AnimationPageBaseView x:Class="Deamon.View.ChamberView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Deamon.View"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
Title="ChamberView">
其中 xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"就是命名空间的引用。
3.使用LiveCharts控件,值用绑定的方式传递到界面。
<lvc:CartesianChart Grid.Row="1" AnimationsSpeed="0:0:0.2" Hoverable="False" DataTooltip="{x:Null}">
<lvc:CartesianChart.Series>
<lvc:LineSeries Values="{Binding ChartValues}"
PointGeometry="{x:Null}"
LineSmoothness="0"
StrokeThickness="3"
Stroke="#12B1AD"
Fill="#E6F7F8"/>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.AxisX>
<lvc:Axis LabelFormatter="{Binding DateTimeFormatter}"
MaxValue="{Binding AxisMax}"
MinValue="{Binding AxisMin}" ShowLabels="False"
Unit="{Binding AxisUnit}">
<lvc:Axis.Separator>
<lvc:Separator Step="{Binding AxisStep}" />
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis ShowLabels="false" />
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
如上所示,我们这里有一些属性是通过绑定来赋值的。因此接下来我们来完善这块工作。
4.数据绑定与刷新
4.1定义一个数据点结构——MeasureModel
/// <summary>
/// 测量模型
/// </summary>
public class MeasureModel
{
/// <summary>
/// X 轴数据
/// </summary>
public DateTime DateTime { get; set; }
/// <summary>
/// Y 轴数据
/// </summary>
public double Value { get; set; }
}
4.2定义界面相关的视图模型ViewModel
定义通知属性、普通属性和命令以及构造时初始化等工作。
using Deamon.Util.DI;
using LiveCharts;
using LiveCharts.Configurations;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Deamon.ViewModel
{
/// <summary>
/// 房间信息
/// </summary>
public class ChamberViewModel : BaseViewModel
{
/// <summary>
/// 默认构造函数
/// </summary>
public ChamberViewModel()
{
// 初始化创建一个 X Y 轴上数据显示的图表,并确定 X Y 轴的数据结构
var mapper = Mappers.Xy<MeasureModel>()
.X(model => model.DateTime.Ticks)
.Y(model => model.Value);
// 配置这个图表,可以被其他地方(特定的方式)使用
Charting.For<MeasureModel>(mapper);
// 初始化测量的数据集
ChartValues = new ChartValues<MeasureModel>();
// 设置 X 轴显示的标签格式
DateTimeFormatter = value => new DateTime((long)value).ToString("mm:ss");
// 设置图表的其他相关属性
AxisStep = TimeSpan.FromSeconds(1).Ticks;
AxisUnit = TimeSpan.TicksPerSecond;
SetAxisLimits(DateTime.Now);
// 默认情况下开始数据刷新
IsReading = false;
this.InjectStopOnClick();
}
#region 公共命令
/// <summary>
/// 显示导航栏命令
/// </summary>
public RelayCommand ShowBarCommand
{
get
{
return new RelayCommand(() =>
{
IoC.Get<ApplicationViewModel>().HasNavigationBar = true;
});
}
}
/// <summary>
/// 显示导航栏命令
/// </summary>
public RelayCommand ReadingCommand
{
get
{
return new RelayCommand(() =>
{
this.InjectStopOnClick();
RaisePropertyChanged(nameof(ReadingCommandText));
});
}
}
#endregion
#region 属性
/// <summary>
/// 缓存的测量数据
/// </summary>
public ChartValues<MeasureModel> ChartValues { get; set; }
/// <summary>
/// 时间格式化器
/// </summary>
public Func<double, string> DateTimeFormatter { get; set; }
/// <summary>
/// X 轴上每个数据的等距跳变(时)长
/// </summary>
public double AxisStep { get; set; }
public double AxisUnit { get; set; }
/// <summary>
/// 正在读取
/// </summary>
public bool IsReading { get; set; }
/// <summary>
/// 控制按钮的文本
/// </summary>
public string ReadingCommandText
{
get
{
return IsReading ? "Stop" : "Start";
}
}
private string curvalue;
/// <summary>
/// 当前值
/// </summary>
public string Curvalue
{
get { return curvalue; }
set
{
curvalue = value;
RaisePropertyChanged(nameof(Curvalue));
}
}
private double axisMax;
/// <summary>
/// X轴最大
/// </summary>
public double AxisMax
{
get { return axisMax; }
set
{
axisMax = value;
RaisePropertyChanged(nameof(AxisMax));
}
}
private double axisMin;
/// <summary>
/// X轴最小
/// </summary>
public double AxisMin
{
get { return axisMin; }
set
{
axisMin = value;
RaisePropertyChanged(nameof(AxisMin));
}
}
#endregion
/// <summary>
/// 模拟数据采集
/// </summary>
public void Read()
{
while (IsReading)
{
Thread.Sleep(400);
var now = DateTime.Now;
double value = Math.Round(new Random().Next(10, 11) + (1 * new Random().NextDouble()), 2);
ChartValues.Add(new MeasureModel
{
DateTime = now,
Value = value
});
SetAxisLimits(now);
Curvalue = value + "℃";
// 只保留160个数据,满了就把前面的数据移除
if (ChartValues.Count > 160) ChartValues.RemoveAt(0);
}
IsReading = false;
}
/// <summary>
/// 设置 X 轴上呈现的时间范围
/// </summary>
/// <param name="now"></param>
private void SetAxisLimits(DateTime now)
{
// X轴显示区域的最大时间
AxisMax = now.Ticks + TimeSpan.FromSeconds(1).Ticks;
// X轴显示区域的最大时间
AxisMin = now.Ticks - TimeSpan.FromSeconds(8).Ticks;
}
/// <summary>
/// 根据标识是否启动采集
/// </summary>
public void InjectStopOnClick()
{
IsReading = !IsReading;
if (IsReading)
{
Task.Factory.StartNew(Read);
}
}
}
}
4.3数据绑定和界面完善
<local:AnimationPageBaseView x:Class="Deamon.View.ChamberView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Deamon.View"
xmlns:vm="clr-namespace:Deamon.ViewModel"
xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"
DataContext="{Binding ChamberVM, Source={ x:Static vm:ViewModelLocator.Locator}}"
Title="ChamberView">
<Grid Background="#FF033F42">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Margin="10 0 0 0">
<Button VerticalAlignment="Center"
Style="{StaticResource OnlyTextButton}"
Command="{Binding ReadingCommand}"
Content="{Binding ReadingCommandText}"/>
</StackPanel>
<DockPanel Grid.Row="1">
<Grid Width="300" Height="200">
<Border CornerRadius="5" Background="White"/>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="60"/>
<RowDefinition/>
</Grid.RowDefinitions>
<StackPanel Margin="5 0 0 0" VerticalAlignment="Center">
<TextBlock Text="NO.607" FontSize="22" VerticalAlignment="Center" Foreground="#6C757D"/>
<TextBlock Text="Temperature:" FontSize="15" Foreground="#6C757D"/>
</StackPanel>
<TextBlock Text="{Binding Curvalue}" Margin="10 5 0 0" Grid.Row="1" FontFamily="Consolas" FontSize="22" Foreground="#6C757D" FontWeight="Bold" />
<lvc:CartesianChart Grid.Row="1" AnimationsSpeed="0:0:0.2" Hoverable="False" DataTooltip="{x:Null}">
<lvc:CartesianChart.Series>
<lvc:LineSeries Values="{Binding ChartValues}"
PointGeometry="{x:Null}"
LineSmoothness="0"
StrokeThickness="3"
Stroke="#12B1AD"
Fill="#E6F7F8"/>
</lvc:CartesianChart.Series>
<lvc:CartesianChart.AxisX>
<lvc:Axis LabelFormatter="{Binding DateTimeFormatter}"
MaxValue="{Binding AxisMax}"
MinValue="{Binding AxisMin}" ShowLabels="False"
Unit="{Binding AxisUnit}">
<lvc:Axis.Separator>
<lvc:Separator Step="{Binding AxisStep}" />
</lvc:Axis.Separator>
</lvc:Axis>
</lvc:CartesianChart.AxisX>
<lvc:CartesianChart.AxisY>
<lvc:Axis ShowLabels="True" />
</lvc:CartesianChart.AxisY>
</lvc:CartesianChart>
</Grid>
</Grid>
</DockPanel>
</Grid>
</local:AnimationPageBaseView>
Over
每次记录一小步...点点滴滴人生路...
更多推荐
已为社区贡献3条内容
所有评论(0)