using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Drawing; using System.Linq; using System.Windows; using Aitex.Core.RT.Log; using Aitex.Core.Util; using Aitex.Core.Utilities; using MECF.Framework.Common.ControlDataContext; using MECF.Framework.Common.DataCenter; using MECF.Framework.Common.Utilities; using MECF.Framework.UI.Client.CenterViews.DataLogs.ProcessHistory; using MECF.Framework.UI.Client.ClientBase; using MECF.Framework.UI.Client.ClientBase.Charting; using MECF.Framework.UI.Client.ClientBase.EventArgs; using MECF.Framework.UI.Client.ClientBase.Tree; using OpenSEMI.ClientBase; using SciChart.Charting.Visuals.Axes; using SciChart.Charting.Visuals.RenderableSeries; using SciChart.Core.Extensions; 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.GetTreeNodeParameters()); 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; } private bool _IsHold; public bool IsHold { get { return _IsHold; } set { if (_IsHold!=value) { _IsHold = value; if (_IsHold) EnableAutoZoom = false; else EnableAutoZoom = true; NotifyOfPropertyChange(nameof(IsHold)); } } } #endregion #region Methods protected bool MonitorData() { try { Dictionary data = null; if (!IsHold&&SelectedData.Count > 0) { data = QueryDataClient.Instance.Service.PollData(SelectedData.Select(r => (r as FastLineSeries)?.DataName).ToList()); 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 FastLineSeries 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 FastLineSeries(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--) { if (i < list.Count) { 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(); } } public void ArrowClick(string direction) { var view = GetView() as RealtimeView; switch (direction) { case "Up": view.sciChart.ChartModifier.YAxis.Scroll(50, SciChart.Charting.ClipMode.ClipAtMax); break; case "Down": view.sciChart.ChartModifier.YAxis.Scroll(-50, SciChart.Charting.ClipMode.ClipAtMin); break; case "Left": view.sciChart.ChartModifier.XAxis.Scroll(-50, SciChart.Charting.ClipMode.ClipAtMin); break; case "Right": view.sciChart.ChartModifier.XAxis.Scroll(50, SciChart.Charting.ClipMode.ClipAtMax); break; } } public void ZoomInClick(int index) { var view = GetView() as RealtimeView; index += 1; if ((index & 0x01) == 1) view.sciChart.ChartModifier.XAxis.ZoomBy(-0.1, -0.1); if ((index & 0x02) == 2) view.sciChart.ChartModifier.YAxis.ZoomBy(-0.1, -0.1); } public void ZoomOutClick(int index) { var view = GetView() as RealtimeView; index += 1; if ((index & 0x01) == 1) view.sciChart.ChartModifier.XAxis.ZoomBy(0.1, 0.1); if ((index & 0x02) == 2) view.sciChart.ChartModifier.YAxis.ZoomBy(0.1, 0.1); } #endregion } }