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); } } }