using FLY.Thick.Base.Client;
using FLY.Thick.Base.Common;
using FLY.Thick.Base.IService;
using FLY.UI.Module;
using FObjBase;
using LiveCharts;
using Misc;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using ThickTcpUiInWindow;
using ThickTcpUiInWindow.UIModule;
namespace FLY.Thick.BlowingScan.UI.Client.UIModule
{
///
/// FixGraph.xaml 的交互逻辑
///
public partial class FixGraph : UserControl
{
TDGage gage;
FixGraphViewModel viewModel;
FixGraphItemParam graphparam;
public FixGraph()
{
InitializeComponent();
}
public void Init(int id)
{
//查找参数
graphparam = FixGraphParams.Current.Items.Find(p => p.ID == id);
if (graphparam == null)
{
graphparam = new FixGraphItemParam() { ID = id };
FixGraphParams.Current.Items.Add(graphparam);
FixGraphParams.Current.Save();
}
//获取设备
gage = Application.Current.Properties["ThickGage"] as TDGage;
viewModel = new FixGraphViewModel();
viewModel.Init(gage.mFixService, graphparam);
this.DataContext = viewModel;
}
private void Button_info_click(object sender, RoutedEventArgs e)
{
bool isPaused = viewModel.IsPaused;
if (!isPaused)
viewModel.IsPaused = true;
FixGraphConfig w = new FixGraphConfig();
w.Init(graphparam);
w.Owner = FLY.ControlLibrary.COMMON.GetWindow(this);
w.ShowDialog();
viewModel.IsPaused = isPaused;
}
private void button_pause_Click(object sender, RoutedEventArgs e)
{
viewModel.IsPaused = !viewModel.IsPaused;
}
private void button_save_Click(object sender, RoutedEventArgs e)
{
viewModel.PreSave();
Window_SaveDatas w = new Window_SaveDatas();
w.Init(viewModel);
w.ShowDialog();
}
private void button_analyze_Click(object sender, RoutedEventArgs e)
{
viewModel.GetDatas(out double intervalms, out List datas);
Page_FixAnalyze p = new Page_FixAnalyze();
p.Init(intervalms, datas);
(Application.Current.Properties["NavigationService"] as NavigationService).Navigate(p);
}
}
public class FixGraphViewModel : ISaveManager, INotifyPropertyChanged
{
#region 图表控制
///
/// 图表Y轴最大值
///
public double YMax { get; set; } = double.NaN;
///
/// 图表Y轴最小值
///
public double YMin { get; set; } = double.NaN;
///
/// X轴坐标文字显示格式
///
public Func XFormatter { get; set; }
///
/// Y轴坐标文字显示格式
///
public Func YFormatter { get; set; }
///
/// 图表X轴最大值
///
public double XMax { get; set; } = double.NaN;
///
/// 图表X轴最小值
///
public double XMin { get; set; } = double.NaN;
///
/// 显示在chart上的数据
///
public ChartValues Values { get; } = new ChartValues();
#endregion
#region 界面统计值
///
/// 平均值
///
public double Average { get; set; }
///
/// 最大值
///
public double Max { get; set; }
///
/// 最小值
///
public double Min { get; set; }
///
/// 3sigma
///
public double Sigma3 { get; set; }
#endregion
#region 状态
///
/// 测试时间
///
public DateTime Time { get; set; }
///
/// 间隔
///
public TimeSpan Interval { get; set; }
///
/// 间隔,单位ms
///
public double Intervalms { get; set; }
///
/// 混合数
///
public int Mix { get; set; }
///
/// 暂停
///
public bool IsPaused { get; set; }
///
/// AD值模式
///
public bool IsADMode { get; private set; }
#endregion
///
/// 真实接收的数据,与 Values 不一样, Values只是mDatas的一部分
///
List mDatas = new List();
///
/// 数据保存路径
///
public string SavePath { get; set; }
///
/// 自动Y轴中值时,Y轴中值的Value
///
public double AutoTarget { get; set; }
///
/// 多少个 mDatas 显示为一个 Values, 限定屏幕最大显示2000个数据
///
int values_scale = 1;
///
/// 限定屏幕最大显示2000个数据
///
const int values_cap = 1000;
FixServiceClient mFixService;
FixGraphItemParam graphparam;
public FixGraphViewModel()
{
#region 与数据无关界面参数
XFormatter = (x) =>
{
int index = (int)x;
double s = Interval.TotalSeconds * index * values_scale;
return $"{s:F1}s";
};
YFormatter = (y) => y.ToString("F1");
#endregion
}
public void Init(FixServiceClient fixService, FixGraphItemParam bulkGraphItemFix)
{
mFixService = fixService;
graphparam = bulkGraphItemFix;
Update4Graphparam();
graphparam.PropertyChanged += Graphparam_PropertyChanged;
mFixService.RegistTimeGridEvent(new FixEventHandler(mFixService_TimeGridEvent));
this.PropertyChanged += FixGraphViewModel_PropertyChanged;
}
private void FixGraphViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "AutoTarget")
{
updateAutoTarget();
}
}
void updateAutoTarget()
{
if (!graphparam.IsAutoY && graphparam.IsAutoTarget)
{
if ((!double.IsNaN(YMax)) && (!double.IsNaN(YMin)))
{
double mid = (YMax + YMin) / 2;
double range = (YMax - YMin) / 2;
double ymax2 = mid + range / 2;
double ymin2 = mid - range / 2;
if ((AutoTarget < ymax2) && (AutoTarget > ymin2))
{
if (Math.Abs(range - graphparam.YRange) < 0.05)
{
//还在范围内,不需要改变 Y轴范围
return;
}
}
}
YMax = AutoTarget + graphparam.YRange;
YMin = AutoTarget - graphparam.YRange;
}
}
private void Graphparam_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == "Interval")
{
UpdateInterval();
}
else if (e.PropertyName == "Mix")
{
UpdateMix();
}
else if (e.PropertyName == "Len")
{
UpdateX();
}
else if ((e.PropertyName == "IsAutoY") ||
(e.PropertyName == "IsAutoTarget") ||
(e.PropertyName == "YRange") ||
(e.PropertyName == "YTarget"))
{
UpdateY();
}
else if (e.PropertyName == "IsADMode")
{
UpdateIsADMode();
}
}
void Update4Graphparam()
{
UpdateInterval();
UpdateMix();
UpdateX();
UpdateY();
UpdateIsADMode();
}
void UpdateX()
{
if (graphparam.Len > values_cap)
values_scale = graphparam.Len / values_cap;
else
values_scale = 1;
XMax = (int)(graphparam.Len / values_scale);//最后算出来 的数量肯定大于 values_cap,没关系,反正数据量少了
XMin = 0;
}
void UpdateY()
{
if (graphparam.IsAutoY)
{
YMax = double.NaN;
YMin = double.NaN;
}
else if (graphparam.IsAutoTarget)
{
updateAutoTarget();
//YMax = AutoTarget + graphparam.YRange;
//YMin = AutoTarget - graphparam.YRange;
}
else
{
YMax = graphparam.YTarget + graphparam.YRange;
YMin = graphparam.YTarget - graphparam.YRange;
}
}
void UpdateInterval()
{
TimeSpan interval;
if (graphparam.Interval < 10)
interval = TimeSpan.FromTicks((long)(graphparam.Interval * TimeSpan.TicksPerMillisecond * 1.28));
else
interval = TimeSpan.FromTicks((long)(graphparam.Interval * TimeSpan.TicksPerMillisecond));
Interval = interval;
Intervalms = ((double)(Interval.Ticks)) / TimeSpan.TicksPerMillisecond;
}
void UpdateMix()
{
Mix = graphparam.Mix;
}
void UpdateIsADMode()
{
IsADMode = graphparam.IsADMode;
//清空全部数据
Clear();
UpdateStat();
}
///
/// 全部 mDatas 重新放入Values
///
///
void RefreshValues(List datas)
{
Values.Clear();
//显示在屏幕的数据,只能有2000个
if (values_scale <= 1)
{
foreach (var d in datas)
Values.Add(d);
}
else
{
data_show.Clear();
foreach (var d in datas)
{
data_show.Add(d);
if (data_show.Count >= values_scale)
{
Values.Add(data_show.Average());
data_show.Clear();
}
}
}
}
///
/// 增量数据,添加到 Values
///
///
void RefreshValuesMore(List datas)
{
//显示在屏幕的数据,只能有2000个
if (values_scale <=1)
{
foreach (var d in datas)
Values.Add(d);
while (Values.Count > graphparam.Len)
Values.RemoveAt(0);
}
else
{
foreach (var d in datas)
{
data_show.Add(d);
if (data_show.Count >= values_scale)
{
Values.Add(data_show.Average());
data_show.Clear();
}
}
//删除数据
while (Values.Count > graphparam.Len / values_scale)
Values.RemoveAt(0);
}
}
public void Clear()
{
mDatas.Clear();
data_interval.Clear();
data_mix.Clear();
data_show.Clear();
data_interval_totaltime = TimeSpan.Zero;
}
#region 用于间隔
///
/// 显示数据用,mDatas->mPoints 的转换
///
List data_show = new List();
///
/// interval 时间内数据整合
///
List data_interval = new List();
///
/// data_interval 的总时间
///
TimeSpan data_interval_totaltime = TimeSpan.Zero;
///
/// 数据混合
///
List data_mix = new List();
#endregion
void mFixService_TimeGridEvent(object sender, FixEventArgs e)
{
if (IsPaused)
return;
//输入1.28ms 的数据, 输出 混合为mix,间隔为 interval 的数据
List data_out = new List();
for (int i = 0; i < e.thicks.Length; i++)
{
double v = (IsADMode) ? e.ADs[i] : e.thicks[i];
data_interval.Add(v);
data_interval_totaltime += e.ts;
if (data_interval_totaltime >= Interval)
{
double avg = data_interval.Average();
data_interval.Clear();
data_interval_totaltime -= Interval;
data_mix.Add(avg);
int len = data_mix.Count - graphparam.Mix;
if (len > 0)
data_mix.RemoveRange(0, len);
data_out.Add(data_mix.Average());
}
}
if (data_out.Count() > 0)
{
Time = e.time;
AddPoint(data_out);
}
}
void UpdateStat()
{
if (mDatas.Count() == 0)
{
Max = double.NaN;
Min = double.NaN;
Average = double.NaN;
Sigma3 = double.NaN;
AutoTarget = double.NaN;
return;
}
Max = mDatas.Max();
Min = mDatas.Min();
Average = mDatas.Average();
Sigma3 = mDatas.Sigma() * 3;
if (mDatas.Count > 100)
{
//最后100个数据的平均值
AutoTarget = mDatas.Skip(mDatas.Count - 100).Average();
}
else
{
AutoTarget = Average;
}
}
///
/// 1.28ms 经过重新 划分间隔, 混合后的数据,添加到 mDatas
///
///
void AddPoint(List datas)
{
mDatas.AddRange(datas);
//删除数据
if (mDatas.Count > graphparam.Len)
mDatas.RemoveRange(0, mDatas.Count - graphparam.Len);
//更新状态
UpdateStat();
//增量数据,添加到 Values
RefreshValuesMore(datas);
}
public void PreSave()
{
string directory = System.IO.Path.GetDirectoryName(SavePath);
if (string.IsNullOrEmpty(directory))
{
directory = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
}
string filename = $"{DateTime.Now:yyyyMMdd_HHmmss}.csv";
SavePath = System.IO.Path.Combine(directory, filename);
}
AsyncCBHandler mAsyncDelegate;
object mAsyncState;
///
/// 成功或失败,会调用 AsyncDelegate
///
///
///
public void Save(AsyncCBHandler AsyncDelegate, object AsyncState)
{
//需要异步!!!!
if (!IsPaused)
{
mAsyncDelegate(mAsyncState, false);
return;
}
mAsyncDelegate = AsyncDelegate;
mAsyncState = AsyncState;
Task.Factory.StartNew(Output);
}
void Output()
{
string path = SavePath;
try
{
using (StreamWriter sw = new StreamWriter(path, false, Encoding.GetEncoding("GB2312")))
{
double ms = graphparam.Interval;
if (ms < 10)
ms *= 1.28;
if (IsADMode)
{
sw.WriteLine("时间(ms),AD");
for (int i = 0; i < mDatas.Count(); i++)
{
double d = mDatas[i];
sw.WriteLine($"{(i * ms):F2},{d:F0}");
}
}
else
{
sw.WriteLine("时间(ms),数据");
for (int i = 0; i < mDatas.Count(); i++)
{
double d = mDatas[i];
sw.WriteLine($"{(i * ms):F2},{d:F2}");
}
}
sw.Flush();
sw.Close();
}
mAsyncDelegate(mAsyncState, true);
}
catch (Exception e)
{
mAsyncDelegate(mAsyncState, false);
}
}
public void GetDatas(out double intervalms, out List datas)
{
intervalms = Intervalms;
datas = mDatas;
}
#region INotifyPropertyChanged 成员
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class FixGraphViewModel_UnitTest : INotifyPropertyChanged
{
#region 图表控制
public double YMax { get; set; } = double.NaN;
public double YMin { get; set; } = double.NaN;
public Func XFormatter { get; set; }
public Func YFormatter { get; set; }
public double XMax { get; set; } = double.NaN;
public ChartValues Values { get; } = new ChartValues();
#endregion
#region 界面统计值
///
/// 平均值
///
public double Average { get; set; }
///
/// 最大值
///
public double Max { get; set; }
///
/// 最小值
///
public double Min { get; set; }
///
/// 3sigma
///
public double Sigma3 { get; set; }
#endregion
#region 状态
public DateTime Time { get; set; }
public TimeSpan Interval { get; set; }
public double Intervalms { get; set; }
public int Mix { get; set; }
public bool IsPaused { get; set; }
#endregion
///
/// 为了画图快,曲线只能显示最多1000个点
///
const int TotalValueCountInView = 1000;
public FixGraphViewModel_UnitTest()
{
#region 与数据无关界面参数
XFormatter = (x) =>
{
int index = (int)x;
double s = Interval.TotalSeconds * index;
return $"{s:F1}s";
};
YFormatter = (y) => y.ToString("F1");
Interval = TimeSpan.FromTicks((long)(TimeSpan.TicksPerMillisecond * 1.28));
Intervalms = ((double)(Interval.Ticks)) / TimeSpan.TicksPerMillisecond;
#endregion
Random random = new Random();
List values = new List();
for (int i = 0; i < 1000; i++)
{
double v = (Math.Sin(i * Math.PI / 400.0) * 0.6) * 2 + 72 + (random.NextDouble() - 0.5) * 1;
values.Add(v);
}
Values.AddRange(values);
}
public event PropertyChangedEventHandler PropertyChanged;
}
///
///
///
public class FixGraphItemParam : ThickTcpUiInWindow.UIModule.UIModuleParam
{
int len = 120;
public int Len
{
get { return len; }
set {
if (value < 100)
value= 100;
else if (value > 50000)
value = 50000;
if (len != value)
{
len = value;
}
}
}
private int interval = 1000;
///
/// 间隔,只能选 1, 10, 100, 1000, 这些单位;
/// 特别的,1 = 1.28ms,10=10ms,100=100ms
///
public int Interval
{
get { return interval; }
set
{
if (value < 1)//最短1.28ms
value = 1;
else if (value > 3000)//最长3s
value = 3000;
if(interval!=value)
{
interval = value;
}
}
}
private int mix = 3;
public int Mix
{
get {
return mix;
}
set {
if (value < 1)
value = 1;
else if (value > 100)
value = 100;
if (mix != value)
{
mix = value;
}
}
}
///
/// 自动目标值
///
public bool IsAutoTarget { get; set; }
///
/// 显示AD值
///
public bool IsADMode { get; set; }
///
/// 自动Y轴
///
public bool IsAutoY { get; set; }
public double YTarget { get; set; }
double yrange = 1;
public double YRange
{
get { return yrange; }
set {
if (value <= 0)
value = 1;
if (yrange != value)
{
yrange = value;
}
}
}
public void CopyTo(FixGraphItemParam dest)
{
dest.ID = ID;
dest.Len = Len;
dest.Interval = Interval;
dest.Mix = Mix;
dest.IsAutoTarget = IsAutoTarget;
dest.IsAutoY = IsAutoY;
dest.IsADMode = IsADMode;
dest.YRange = YRange;
dest.YTarget = YTarget;
}
public override string[] GetSavePropertyNames()
{
List names = new List(base.GetSavePropertyNames());
names.AddRange(new string[]{
"Len",
"Interval",
"Mix",
"IsAutoTarget",
"IsAutoY",
"IsADMode",
"YTarget",
"YRange"
});
return names.ToArray();
}
}
///
/// 全部扫描图的参数
///
public class FixGraphParams : UIModuleParams
{
static FixGraphParams()
{
Misc.SaveToXmlHepler.Regist(typeof(FixGraphParams));
}
public FixGraphParams()
{
string path = System.IO.Path.Combine(FLYLayout.BasePath, "fixgraph.xml");
FilePath = path;
}
static FixGraphParams current = null;
public static FixGraphParams Current
{
get
{
if (current == null)
{
current = new FixGraphParams();
current.Load();
}
return current;
}
}
}
///
/// 扫描图控件模块
///
public class UIModule_FixGraph : IUIModule
{
///
/// 控件标题
/// 它的值取决于culture
///
public string Title
{
get
{
return "定点图";
}
}
///
/// 控件
/// 创建时,需要给它唯一ID,让加载自己的数据
///
///
///
public FrameworkElement GetComponent(int id)
{
FixGraph graph = new FixGraph();
graph.Init(id);
return graph;
}
///
/// 控件缩略图,用于编辑界面时,大致看看
/// 创建时,需要给它唯一ID,让加载自己的数据
///
///
///
public FrameworkElement GetThumbnail(int id)
{
return new System.Windows.Controls.Grid();
}
///
/// 给出全部控件ID, 控件自行删除没有的参数
///
///
public void MatchParam(int[] IDs)
{
FixGraphParams.Current.MatchParam(IDs);
}
}
}