为了实现一个完整软件系统,必须具备一些基本的数据呈现控件,例如曲线图、柱状图、饼图等。本次的业务需求为:利用LiveCharts展示后台模拟的温度变化。像Winform里面,微软为我们提供了比较完整的Chart控件,但是在WPF组件中,就没有找到类似的控件,它的意图是让我们自己去实现。我们应该对当下的代码共享时代环抱感激,迄今位置有很多面向WPF的第三方控件库,大部分都是免费开源的。例如:OxyPlot、ModernuiCharts......以及我们今天的主角——LiveCharts。这是一个具备动画效果的图表控件。
首先上效果:
Windows 10
Visual Studio 2019
.Net Framework 4.7.2
Ninject
LiveCharts
LiveCharts.Wpf
解决方案资源管理器-->项目(右键)-->管理NuGet程序包
在“浏览”中搜索:
Ninject
LiveCharts
LiveCharts.Wpf
并安装,完成之后如下图所示:
在ChamberView.xaml文件的节点上添加对程序集的应用,如下:
其中 xmlns:lvc="clr-namespace:LiveCharts.Wpf;assembly=LiveCharts.Wpf"就是命名空间的引用。
如上所示,我们这里有一些属性是通过绑定来赋值的。因此接下来我们来完善这块工作。
4.1定义一个数据点结构——MeasureModel
///
/// 测量模型
///
public class MeasureModel
{
///
/// X 轴数据
///
public DateTime DateTime { get; set; }
///
/// Y 轴数据
///
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
{
///
/// 房间信息
///
public class ChamberViewModel : BaseViewModel
{
///
/// 默认构造函数
///
public ChamberViewModel()
{
// 初始化创建一个 X Y 轴上数据显示的图表,并确定 X Y 轴的数据结构
var mapper = Mappers.Xy()
.X(model => model.DateTime.Ticks)
.Y(model => model.Value);
// 配置这个图表,可以被其他地方(特定的方式)使用
Charting.For(mapper);
// 初始化测量的数据集
ChartValues = new ChartValues();
// 设置 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 公共命令
///
/// 显示导航栏命令
///
public RelayCommand ShowBarCommand
{
get
{
return new RelayCommand(() =>
{
IoC.Get().HasNavigationBar = true;
});
}
}
///
/// 显示导航栏命令
///
public RelayCommand ReadingCommand
{
get
{
return new RelayCommand(() =>
{
this.InjectStopOnClick();
RaisePropertyChanged(nameof(ReadingCommandText));
});
}
}
#endregion
#region 属性
///
/// 缓存的测量数据
///
public ChartValues ChartValues { get; set; }
///
/// 时间格式化器
///
public Func DateTimeFormatter { get; set; }
///
/// X 轴上每个数据的等距跳变(时)长
///
public double AxisStep { get; set; }
public double AxisUnit { get; set; }
///
/// 正在读取
///
public bool IsReading { get; set; }
///
/// 控制按钮的文本
///
public string ReadingCommandText
{
get
{
return IsReading ? "Stop" : "Start";
}
}
private string curvalue;
///
/// 当前值
///
public string Curvalue
{
get { return curvalue; }
set
{
curvalue = value;
RaisePropertyChanged(nameof(Curvalue));
}
}
private double axisMax;
///
/// X轴最大
///
public double AxisMax
{
get { return axisMax; }
set
{
axisMax = value;
RaisePropertyChanged(nameof(AxisMax));
}
}
private double axisMin;
///
/// X轴最小
///
public double AxisMin
{
get { return axisMin; }
set
{
axisMin = value;
RaisePropertyChanged(nameof(AxisMin));
}
}
#endregion
///
/// 模拟数据采集
///
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;
}
///
/// 设置 X 轴上呈现的时间范围
///
///
private void SetAxisLimits(DateTime now)
{
// X轴显示区域的最大时间
AxisMax = now.Ticks + TimeSpan.FromSeconds(1).Ticks;
// X轴显示区域的最大时间
AxisMin = now.Ticks - TimeSpan.FromSeconds(8).Ticks;
}
///
/// 根据标识是否启动采集
///
public void InjectStopOnClick()
{
IsReading = !IsReading;
if (IsReading)
{
Task.Factory.StartNew(Read);
}
}
}
}
4.3数据绑定和界面完善
Over
每次记录一小步...点点滴滴人生路...