using GeneralData; using ScottPlot.Plottables; namespace HistoryUI.ViewModels; public partial class StatusViewModel : BaseViewModel { public StatusViewModel(Hardwares hardwares, IORM orm) : base(hardwares, orm) { this.DataGirdVis = Visibility.Collapsed; this.Loading = Visibility.Collapsed; this.PlotControl = new(); this.InitPlot(); } private void InitPlot() { this.PlotControl.Plot.Grid.XAxisStyle.MajorLineStyle.Width = 1f; this.PlotControl.Plot.Grid.YAxisStyle.MajorLineStyle.Width = 1f; this.PlotControl.Plot.FigureBackground.Color = ScottPlot.Colors.Transparent; this.PlotControl.Plot.RenderManager.RenderStarting += (s, e) => { Tick[] ticks = this.PlotControl.Plot.Axes.Bottom.TickGenerator.Ticks; for (int i = 0; i < ticks.Length; i++) { DateTime dt = DateTime.FromOADate(ticks[i].Position); string label = $"{dt:MM/dd HH:mm:ss}"; ticks[i] = new Tick(ticks[i].Position, label); } }; PixelPadding padding = new(40, 20, 65, 10); this.PlotControl.Plot.Layout.Fixed(padding); this.PlotControl.Plot.FigureBackground.Color = ScottPlot.Colors.Transparent; this.Query(); } [ObservableProperty] private WpfPlot _PlotControl; [ObservableProperty] private string? _Hint; [ObservableProperty] private Visibility _DataGirdVis; [ObservableProperty] private Visibility _Loading; [RelayCommand] private void Show(string para) { switch (para) { case "Show": this.DataGirdVis = Visibility.Visible; break; case "Hide": this.DataGirdVis = Visibility.Collapsed; break; default: break; } } [RelayCommand] protected override void Query() { if (!base.QueryBase(out byte mini8, out byte Channel)) return; this.Loading = Visibility.Visible; _orm.Query($"Mini8-{mini8}-{Channel}", t => t.DateTime >= this.StartTime && t.DateTime <= this.EndTime , QueryResult); } private class Area(DateTime start, DateTime? end, bool success) { public bool Success { get; set; } = success; public DateTime Start { get; } = start; public DateTime? End { get; set; } = end; } private class DataRange { public readonly List target = []; public readonly List temp = []; public readonly List caps = []; public readonly List floor = []; public readonly List capsWarning = []; public readonly List floorWarning = []; public readonly List tcBrokenLine = []; public readonly List time = []; public readonly List autoTuneArea = []; public readonly List tcBrockenArea = []; } private readonly List _DataRangeCache = []; private readonly Dictionary _DataCache = []; private void QueryResult(List results) { if (results.Count < 1) { App.Current.Dispatcher.Invoke(() => this.Loading = Visibility.Collapsed); return; } results = [.. results.OrderBy(t => t.DateTime)]; _DataCache.Clear(); _DataRangeCache.Clear(); for (int i = 1; i <= results.Count; i++) { _DataCache.Add(i, results[i - 1]); if (_DataCache[i].ActiveTuneSet != ActiveTuneSet.AutoTune) continue; _DataCache[i].Running_P = _DataCache[i].AutoTune_P; _DataCache[i].Running_I = _DataCache[i].AutoTune_I; _DataCache[i].Running_D = _DataCache[i].AutoTune_D; } //Create Data Range DateTime? lastTime = null; AutoTuneStatus? autoTune = null; TcBorken tcBorken = TcBorken.Normal; float lastPV = 0; foreach (DBFormat dbData in results) { if (dbData.SensorBreakAlarm == TcBorken.Error) dbData.PV = lastPV; lastPV = dbData.PV; TimeSpan? span = dbData.DateTime - lastTime; if (lastTime is null || !span.HasValue || span.Value.TotalSeconds > 10) { autoTune = null; tcBorken = TcBorken.Normal; _DataRangeCache.Add(new()); } lastTime = dbData.DateTime; DataRange dataRang = _DataRangeCache.Last(); dataRang.time.Add(dbData.DateTime); dataRang.temp.Add(dbData.PV); dataRang.target.Add(dbData.SetPoint); dataRang.caps.Add(dbData.Caps); dataRang.floor.Add(dbData.Floor); dataRang.capsWarning.Add(dbData.CapsWarning); dataRang.floorWarning.Add(dbData.FloorWarning); if (tcBorken == dbData.SensorBreakAlarm) goto Step_AutoTuneStatusCheck; switch (dbData.SensorBreakAlarm) { case TcBorken.Normal: if (dataRang.tcBrockenArea.Count == 0) break; Area s = dataRang.tcBrockenArea.Last(); s.Success = true; s.End = dbData.DateTime; break; case TcBorken.Error: dataRang.tcBrockenArea.Add(new(dbData.DateTime, null, false)); break; default: break; } dataRang.tcBrokenLine.Add(dbData.DateTime); tcBorken = dbData.SensorBreakAlarm; Step_AutoTuneStatusCheck: //autoTune ??= dbData.AutoTuneStatus; if (autoTune.HasValue && autoTune == dbData.AutoTuneStatus) continue; switch (dbData.AutoTuneStatus) { case AutoTuneStatus.Triggered: case AutoTuneStatus.Tuning: autoTune = AutoTuneStatus.Tuning; dataRang.autoTuneArea.Add(new(dbData.DateTime, null, true)); break; case AutoTuneStatus.Complete: autoTune = AutoTuneStatus.Complete; if (dataRang.autoTuneArea.Count == 0) { dataRang.autoTuneArea.Add(new(dataRang.time[0], dbData.DateTime, true)); break; } Area area1 = dataRang.autoTuneArea.Last(); area1.Success = true; area1.End = dbData.DateTime; break; case AutoTuneStatus.Aborted: case AutoTuneStatus.Timeout: case AutoTuneStatus.Overflow: autoTune = AutoTuneStatus.Aborted; if (dataRang.autoTuneArea.Count == 0) { dataRang.autoTuneArea.Add(new(dataRang.time[0], dbData.DateTime, false)); break; } Area area2 = dataRang.autoTuneArea.Last(); area2.Success = false; area2.End = dbData.DateTime; break; default: break; } } App.Current.Dispatcher.Invoke(() => { this.PlotControl.Plot.Clear(); this._DataRangeCache.ForEach(range => SetDataRangeInPlot(range)); this.RefreshPlot(); this.RefreshDataTable(results); this.Loading = Visibility.Collapsed; }); } private void SetLine(List time, List value, string color, LinePattern linePattern, MarkerStyle markerStyle, float lineWidth) { Scatter mainScatter = this.PlotControl.Plot.Add.Scatter(time, value, ScottPlot.Color.FromHex(color)); mainScatter.MarkerStyle = markerStyle; mainScatter.LineWidth = lineWidth; mainScatter.LinePattern = linePattern; } private void SetDataRangeInPlot(DataRange range) { this.SetLine(range.time, range.temp, "0000cd", LinePattern.Solid, MarkerStyle.None, 2f); this.SetLine(range.time, range.caps, "FF0000", LinePattern.Dotted, MarkerStyle.None, 1.5f); this.SetLine(range.time, range.floor, "FF0000", LinePattern.Dotted, MarkerStyle.None, 1.5f); this.SetLine(range.time, range.capsWarning, "FFA500", LinePattern.Dotted, MarkerStyle.None, 1.5f); this.SetLine(range.time, range.floorWarning, "FFA500", LinePattern.Dotted, MarkerStyle.None, 1.5f); this.SetLine(range.time, range.target, "00FF00", LinePattern.Dotted, MarkerStyle.None, 1.5f); foreach (var item in range.autoTuneArea) { item.End ??= range.time.Last(); _ = item.Success switch { true => this.PlotControl.Plot.Add.HorizontalSpan(item.Start.ToOADate(), item.End.Value.ToOADate(), ScottPlot.Color.FromHex("00ff00").WithAlpha(0.2)), false => this.PlotControl.Plot.Add.HorizontalSpan(item.Start.ToOADate(), item.End.Value.ToOADate(), ScottPlot.Color.FromHex("828282").WithAlpha(0.2)) }; } foreach (var item in range.tcBrockenArea) { item.End ??= range.time.Last(); this.PlotControl.Plot.Add.HorizontalSpan(item.Start.ToOADate(), item.End.Value.ToOADate(), ScottPlot.Color.FromHex("f98083").WithAlpha(0.2)); } } private void RefreshDataTable(List results) { this.Results ??= []; this.Results.Clear(); for (int i = 0; i < results.Count; i++) this.Results.Add(i, results[i]); } private void RefreshPlot() { this.PlotControl.Plot.HideLegend(); this.PlotControl.Plot.Axes.DateTimeTicksBottom(); this.PlotControl.Plot.Axes.Bottom.TickLabelStyle.Rotation = -45; this.PlotControl.Plot.Axes.Bottom.TickLabelStyle.Alignment = Alignment.MiddleRight; this.PlotControl.Plot.Axes.AutoScale(); this.PlotControl.Refresh(); this.PlotControl.Plot.Axes.Zoom(1.085, 1); } [RelayCommand] private void ReScale(string para) { switch (para) { case "+": this.PlotControl.Plot.Axes.Zoom(1.25, 1); break; case "-": this.PlotControl.Plot.Axes.Zoom(0.8, 1); break; case "add": this.PlotControl.Plot.Axes.Zoom(1, 1.25); break; case "minus": this.PlotControl.Plot.Axes.Zoom(1, 0.8); break; default: this.PlotControl.Plot.Axes.AutoScale(); this.PlotControl.Plot.Axes.Zoom(1.085, 1); break; } this.PlotControl.Refresh(); } }