using System; using System.Collections.Generic; using System.Linq; using System.Windows; using Aitex.Core.RT.Log; using Aitex.Core.Util; using Aitex.Core.Utilities; using MECF.Framework.Common.DataCenter; using MECF.Framework.UI.Client.CenterViews.Core; using MECF.Framework.UI.Client.CenterViews.Core.Charting; using MECF.Framework.UI.Client.CenterViews.Core.EventArgs; using SciChart.Charting.Model.DataSeries; using SciChart.Charting.Visuals.Axes; using Sicentury.Core.EventArgs; using Sicentury.Core.Tree; namespace MECF.Framework.UI.Client.CenterViews.Operations.RealTime { public class RealtimeViewModel : BusyIndicateableUiViewModelBase { #region Variables /// /// 允许监控的最大曲线数量 /// private const int MAX_PARAMETERS_ALLOWED = 200; private readonly RealtimeProvider _provider = new RealtimeProvider(); private bool _enableAutoZoom = true; private int _pointCount; private readonly PeriodicJob _thread; /// /// Monitor中添加数据点时直接向DataSeries对象添加,已实现跨线程操作。 /// private readonly List _dataSeriesList; /// /// 更新报告导出信息。 /// private readonly IProgress _exportingProgressReporter; #endregion #region Constructors public RealtimeViewModel() { DisplayName = "Realtime"; SelectedData = new ChartingLineSeriesCollection(DisplayName); ParameterNodes = new TreeNode(DisplayName) { MaxTerminalSelectionAllowed = MAX_PARAMETERS_ALLOWED }; ParameterNodes.ChildNodes.AddRange(_provider.GetParameters()); ParameterNodes.TerminalNodeSelectionChanged += OnNodeSelectionChanged; IntervalSaved = true; TrendInterval = 500; TimeSpanSaved = true; TrendTimeSpan = 60 * 5; _thread = new PeriodicJob(TrendInterval, MonitorData, "RealTime", true); _pointCount = Math.Max(TrendTimeSpan * 1000 / TrendInterval, 10); _dataSeriesList = new List(); _exportingProgressReporter = new Progress(e => { BusyIndicatorContent = e.Message; }); } #endregion #region Properties public bool IsPermission => this.Permission == 3; public TreeNode ParameterNodes { get; } public ChartingLineSeriesCollection SelectedData { get; set; } public AutoRange ChartAutoRange => EnableAutoZoom ? AutoRange.Always : AutoRange.Never; public bool EnableAutoZoom { get => _enableAutoZoom; set { _enableAutoZoom = value; NotifyOfPropertyChange(nameof(EnableAutoZoom)); NotifyOfPropertyChange(nameof(ChartAutoRange)); } } [IgnorePropertyChange] public int TrendInterval { get; set; } public bool IntervalSaved { get; set; } [IgnorePropertyChange] public int TrendTimeSpan { get; set; } public bool TimeSpanSaved { get; set; } #endregion #region Methods protected bool MonitorData() { try { Dictionary data = null; if (SelectedData.Count > 0) { data = QueryDataClient.Instance.Service.PollData(Array.ConvertAll(SelectedData.ToArray(), x => (x as SicFastLineSeries).DataName)); } AppendData(data); for (var j = 0; j < ParameterNodes.ChildNodes.Count; j++) { var par = ParameterNodes.ChildNodes[j]; par.IsVisibilityParentNode = Visibility.Hidden; } } catch (Exception ex) { LOG.Error(ex.Message); } return true; } public void AppendData(Dictionary data) { if (data == null) return; var dt = DateTime.Now; foreach (var ds in _dataSeriesList) { if (ds == null || !data.ContainsKey(ds.SeriesName)) continue; if (data[ds.SeriesName] is bool) { ds.Append(dt, ((bool)data[ds.SeriesName] ? 1 : 0) * ds.Factor + ds.Offset, new ParameterNodePoint(dt, ((bool)data[ds.SeriesName] ? 1 : 0))); continue; } if (!double.TryParse(data[ds.SeriesName].ToString(), out var value)) continue; ds.Append(dt, value * ds.Factor + ds.Offset, new ParameterNodePoint(dt, value)); } } private void UpdateFifoCapacity(int capacity) { foreach (var renderableSeries in SelectedData) { if (renderableSeries is SicFastLineSeries line) { var ds = line.GetDataSeries(); var copyMetaData = ds.Metadata.Where(x => x != null && ((ParameterNodePoint)x).Time > DateTime.Now.AddSeconds(-TrendTimeSpan)).ToList(); ds.FifoCapacity = capacity; foreach (ParameterNodePoint item in copyMetaData) { ds.Append(item.Time, item.Value); } } } } public void SetInterval() { _thread.ChangeInterval(TrendInterval); _pointCount = Math.Max(10, TrendTimeSpan * 1000 / TrendInterval); IntervalSaved = true; NotifyOfPropertyChange(nameof(IntervalSaved)); UpdateFifoCapacity(_pointCount); } public void SetTimeSpan() { _pointCount = Math.Max(10, TrendTimeSpan * 1000 / TrendInterval); TimeSpanSaved = true; NotifyOfPropertyChange(nameof(TimeSpanSaved)); UpdateFifoCapacity(_pointCount); } #endregion #region Events public void OnNodeSelectionChanged(object sender, TreeNodeSelectionChangedEventArgs e) { if (e.NewValue == true && SelectedData.Count >= MAX_PARAMETERS_ALLOWED) { e.Cancel = true; return; } var line = SelectedData.Cast().FirstOrDefault(x => x.DataName == e.Source.FullName); switch (e.NewValue) { case false: { if (line != null) { _dataSeriesList.Remove(line.GetDataSeries()); SelectedData.Remove(line); } break; } case true: { if (line == null) { var series = new SicFastLineSeries(e.Source.FullName) { BackendParameterNode = e.Source }; series.GetDataSeries().FifoCapacity = _pointCount; SelectedData.Add(series); _dataSeriesList.Add(series.GetDataSeries()); SelectedData.ResetColors(); } break; } default: break; } } public void Deleted(object sender, RenderableSeriesDeletingEventArgs e) { var list = e.Source.Cast().ToList(); var total = SelectedData.Count; for (var i = total - 1; i >= 0; i--) { list[i].BackendParameterNode.IsSelected = false; } _dataSeriesList.Clear(); ((RealtimeView)View).tvParameterNodes.ClearPresetGroupSelectionOnly(); } public void Exporting(object sender, EventArgs e) { BusyIndicatorContent = "Exporting ..."; IsBusy = true; } public void Exported(object sender, EventArgs e) { IsBusy = false; } public void ProgressUpdating(object sender, ProgressUpdatingEventArgs e) { _exportingProgressReporter.Report(e); } public override void Cancel() { if (View is RealtimeView view) { view.dataGrid.CancelOperation(); } } #endregion } }