|
|
|
|
@ -9,6 +9,7 @@ using System.Text;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using System.Windows;
|
|
|
|
|
using System.Windows.Input;
|
|
|
|
|
using System.Windows.Threading;
|
|
|
|
|
using SmartAquaViewer.Controls;
|
|
|
|
|
using SmartAquaViewer.DataAnalysis;
|
|
|
|
|
using SmartAquaViewer.Model;
|
|
|
|
|
@ -16,11 +17,15 @@ using static SmartAquaViewer.Model.Enums;
|
|
|
|
|
|
|
|
|
|
namespace SmartAquaViewer.ViewModel
|
|
|
|
|
{
|
|
|
|
|
public class MonitoringViewModel : INotifyPropertyChanged
|
|
|
|
|
public sealed class FieldItem
|
|
|
|
|
{
|
|
|
|
|
public delegate void SystemChangedEventHandler(MonitorTab selectedTab);
|
|
|
|
|
public event SystemChangedEventHandler OnSystemChanged;
|
|
|
|
|
public string? Name { get; init; } // 바인딩 경로 키 (예: "Tank.DOValue")
|
|
|
|
|
public string? Display { get; init; } // UI 표시명 (예: "DO (mg/L)")
|
|
|
|
|
public Type? DataType { get; init; } // typeof(double), typeof(DateTime) 등
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class MonitoringViewModel : INotifyPropertyChanged
|
|
|
|
|
{
|
|
|
|
|
public ObservableCollection<GraphType> GraphTypes { get; }
|
|
|
|
|
|
|
|
|
|
public List<WaterQualityVO> WaterQualityList { get; }
|
|
|
|
|
@ -37,14 +42,36 @@ namespace SmartAquaViewer.ViewModel
|
|
|
|
|
_selectedTab = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
|
|
|
|
|
Debug.WriteLine($"CurrentTab changed to: {_selectedTab}");
|
|
|
|
|
RebuildAvailableFields(); // 탭에 맞춰 필드 목록 재구성
|
|
|
|
|
SetGraphType();
|
|
|
|
|
SelectedGraphType = GraphTypes.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
|
|
|
|
{
|
|
|
|
|
RebuildFieldCandidates();
|
|
|
|
|
}), DispatcherPriority.Background);
|
|
|
|
|
|
|
|
|
|
OnSystemChanged?.Invoke(SelectedTab);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int _selectedGraphIndex = -1;
|
|
|
|
|
public int SelectedGraphIndex
|
|
|
|
|
{
|
|
|
|
|
get => _selectedGraphIndex;
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
if (_selectedGraphIndex != value)
|
|
|
|
|
{
|
|
|
|
|
_selectedGraphIndex = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
// 인덱스가 바뀌면 enum도 맞춰준다
|
|
|
|
|
if (value >= 0 && value < GraphTypes.Count)
|
|
|
|
|
SelectedGraphType = GraphTypes[value];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private GraphType _selectedGraphType;
|
|
|
|
|
public GraphType SelectedGraphType
|
|
|
|
|
{
|
|
|
|
|
@ -55,6 +82,14 @@ namespace SmartAquaViewer.ViewModel
|
|
|
|
|
{
|
|
|
|
|
_selectedGraphType = value;
|
|
|
|
|
OnPropertyChanged();
|
|
|
|
|
RebuildFieldCandidates();
|
|
|
|
|
|
|
|
|
|
var idx = GraphTypes.IndexOf(value);
|
|
|
|
|
if (idx != -1 && idx != _selectedGraphIndex)
|
|
|
|
|
{
|
|
|
|
|
_selectedGraphIndex = idx;
|
|
|
|
|
OnPropertyChanged(nameof(SelectedGraphIndex));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@ -104,8 +139,64 @@ namespace SmartAquaViewer.ViewModel
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [필드 후보 목록] 탭/시스템에 따라 달라짐
|
|
|
|
|
public ObservableCollection<FieldItem> AvailableFields { get; } = new();
|
|
|
|
|
|
|
|
|
|
// [X축 후보/선택]
|
|
|
|
|
public ObservableCollection<FieldItem> XFieldCandidates { get; } = new();
|
|
|
|
|
private FieldItem? _selectedXField;
|
|
|
|
|
public FieldItem? SelectedXField
|
|
|
|
|
{
|
|
|
|
|
get => _selectedXField;
|
|
|
|
|
set { if (_selectedXField != value) { _selectedXField = value; OnPropertyChanged(); } }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [Y축 후보/선택] — Line/Step: 다중, Scatter/Box: 단일
|
|
|
|
|
public ObservableCollection<FieldItem> YFieldCandidates { get; } = new();
|
|
|
|
|
|
|
|
|
|
// 다중 선택(Y)용
|
|
|
|
|
public ObservableCollection<FieldItem> SelectedYFields { get; } = new();
|
|
|
|
|
|
|
|
|
|
// 단일 선택(Y)용
|
|
|
|
|
private FieldItem? _selectedYField;
|
|
|
|
|
public FieldItem? SelectedYField
|
|
|
|
|
{
|
|
|
|
|
get => _selectedYField;
|
|
|
|
|
set { if (_selectedYField != value) { _selectedYField = value; OnPropertyChanged(); } }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BoxPlot 전용 그룹 기준(예: 수조/시간 등)
|
|
|
|
|
public ObservableCollection<FieldItem> GroupFieldCandidates { get; } = new();
|
|
|
|
|
private FieldItem? _selectedGroupField;
|
|
|
|
|
public FieldItem? SelectedGroupField
|
|
|
|
|
{
|
|
|
|
|
get => _selectedGroupField;
|
|
|
|
|
set { if (_selectedGroupField != value) { _selectedGroupField = value; OnPropertyChanged(); } }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// [옵션] 예시 — 필요하면 추가
|
|
|
|
|
private bool _showMarkers; // Line
|
|
|
|
|
public bool ShowMarkers { get => _showMarkers; set { _showMarkers = value; OnPropertyChanged(); } }
|
|
|
|
|
|
|
|
|
|
private bool _useSmoothing; // Line
|
|
|
|
|
public bool UseSmoothing { get => _useSmoothing; set { _useSmoothing = value; OnPropertyChanged(); } }
|
|
|
|
|
|
|
|
|
|
private double _scatterMarkerSize = 3; // Scatter
|
|
|
|
|
public double ScatterMarkerSize { get => _scatterMarkerSize; set { _scatterMarkerSize = value; OnPropertyChanged(); } }
|
|
|
|
|
|
|
|
|
|
private bool _showRegression; // Scatter
|
|
|
|
|
public bool ShowRegression { get => _showRegression; set { _showRegression = value; OnPropertyChanged(); } }
|
|
|
|
|
|
|
|
|
|
private double _boxWidth = 0.3; // Box
|
|
|
|
|
public double BoxWidth { get => _boxWidth; set { _boxWidth = value; OnPropertyChanged(); } }
|
|
|
|
|
|
|
|
|
|
public ICommand ChangeDrawerStatusCommand { get; }
|
|
|
|
|
|
|
|
|
|
public delegate void SystemChangedEventHandler(MonitorTab selectedTab);
|
|
|
|
|
public event SystemChangedEventHandler OnSystemChanged;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public MonitoringViewModel()
|
|
|
|
|
{
|
|
|
|
|
IsOpenMode = true;
|
|
|
|
|
@ -126,9 +217,11 @@ namespace SmartAquaViewer.ViewModel
|
|
|
|
|
GraphTypes = new ObservableCollection<GraphType>();
|
|
|
|
|
SelectedTab = MonitorTab.Tank; // Default system
|
|
|
|
|
SetGraphType();
|
|
|
|
|
SelectedGraphType = GraphTypes.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
ChangeDrawerStatusCommand = new RelayCommand(_ => IsOpenMode = !IsOpenMode);
|
|
|
|
|
|
|
|
|
|
RebuildAvailableFields();
|
|
|
|
|
RebuildFieldCandidates();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void SetGraphType()
|
|
|
|
|
@ -143,9 +236,6 @@ namespace SmartAquaViewer.ViewModel
|
|
|
|
|
GraphTypes.Add(GraphType.SCATTER);
|
|
|
|
|
break;
|
|
|
|
|
case MonitorTab.Filter:
|
|
|
|
|
GraphTypes.Add(GraphType.LINE);
|
|
|
|
|
GraphTypes.Add(GraphType.STEP);
|
|
|
|
|
break;
|
|
|
|
|
case MonitorTab.Sterilizer:
|
|
|
|
|
GraphTypes.Add(GraphType.LINE);
|
|
|
|
|
GraphTypes.Add(GraphType.STEP);
|
|
|
|
|
@ -154,11 +244,97 @@ namespace SmartAquaViewer.ViewModel
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string graphTypes = string.Empty;
|
|
|
|
|
foreach (GraphType graphType in GraphTypes)
|
|
|
|
|
graphTypes += (graphType.ToString() + ", ");
|
|
|
|
|
Application.Current.Dispatcher.BeginInvoke(new Action(() =>
|
|
|
|
|
{
|
|
|
|
|
SelectedGraphIndex = -1;
|
|
|
|
|
SelectedGraphIndex = GraphTypes.Count > 0 ? 0 : -1;
|
|
|
|
|
}), System.Windows.Threading.DispatcherPriority.Background);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void RebuildAvailableFields()
|
|
|
|
|
{
|
|
|
|
|
AvailableFields.Clear();
|
|
|
|
|
|
|
|
|
|
// 공통 시간
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "RecordedTime", Display = "시간", DataType = typeof(DateTime) });
|
|
|
|
|
|
|
|
|
|
if (SelectedTab == MonitorTab.Tank)
|
|
|
|
|
{
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Tank.Number", Display = "수조", DataType = typeof(int) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Tank.DOValue", Display = "DO (mg/L)", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Tank.PH", Display = "pH", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Tank.ORP", Display = "ORP (mV)", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Tank.Temperature", Display = "온도 (℃)", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Tank.FlowRate", Display = "유량 (m³/s)", DataType = typeof(double) });
|
|
|
|
|
}
|
|
|
|
|
else if (SelectedTab == MonitorTab.Filter)
|
|
|
|
|
{
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Filtering.SumpPH", Display = "섬프 pH", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Filtering.SumpORP", Display = "섬프 ORP (mV)", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Filtering.SumpWaterLevel", Display = "섬프 수위 (m)", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Filtering.SumpFlowRate", Display = "섬프 유량 (m³/s)", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Filtering.SumpTemperature", Display = "섬프 수온 (°C)", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Filtering.FlowRate", Display = "순환펌프 유량", DataType = typeof(double) });
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Filtering.HeatPumpTemperature", Display = "히트펌프 온도", DataType = typeof(double) });
|
|
|
|
|
}
|
|
|
|
|
else // Sterilizer
|
|
|
|
|
{
|
|
|
|
|
AvailableFields.Add(new FieldItem { Name = "Sterilizing.OzoneDissolverPressure", Display = "용해장치 압력 (kPa)", DataType = typeof(double) });
|
|
|
|
|
// 필요한 다른 수치 필드들 추가
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 그래프 타입이 바뀔 때 후보/기본 선택 재구성
|
|
|
|
|
private void RebuildFieldCandidates()
|
|
|
|
|
{
|
|
|
|
|
// 후보 초기화
|
|
|
|
|
XFieldCandidates.Clear();
|
|
|
|
|
YFieldCandidates.Clear();
|
|
|
|
|
GroupFieldCandidates.Clear();
|
|
|
|
|
|
|
|
|
|
// X축: 시간 우선
|
|
|
|
|
foreach (var f in AvailableFields) XFieldCandidates.Add(f);
|
|
|
|
|
SelectedXField = AvailableFields.FirstOrDefault(f => f.DataType == typeof(DateTime))
|
|
|
|
|
?? AvailableFields.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
// Y축 후보: 수치형
|
|
|
|
|
foreach (var f in AvailableFields.Where(f => f.DataType == typeof(double)))
|
|
|
|
|
YFieldCandidates.Add(f);
|
|
|
|
|
|
|
|
|
|
Debug.WriteLine(graphTypes);
|
|
|
|
|
// BoxPlot 그룹 후보: 시간/수조/카테고리 등
|
|
|
|
|
foreach (var f in AvailableFields)
|
|
|
|
|
GroupFieldCandidates.Add(f);
|
|
|
|
|
|
|
|
|
|
// 기본 선택 세팅 (타입별)
|
|
|
|
|
SelectedYFields.Clear();
|
|
|
|
|
SelectedYField = null;
|
|
|
|
|
SelectedGroupField = null;
|
|
|
|
|
|
|
|
|
|
switch (SelectedGraphType)
|
|
|
|
|
{
|
|
|
|
|
case GraphType.LINE:
|
|
|
|
|
case GraphType.STEP:
|
|
|
|
|
var def = YFieldCandidates.FirstOrDefault();
|
|
|
|
|
if (def != null) SelectedYFields.Add(def);
|
|
|
|
|
ShowMarkers = false;
|
|
|
|
|
UseSmoothing = false;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GraphType.SCATTER:
|
|
|
|
|
SelectedYField = YFieldCandidates.FirstOrDefault();
|
|
|
|
|
ScatterMarkerSize = 3;
|
|
|
|
|
ShowRegression = false;
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
case GraphType.BOX:
|
|
|
|
|
SelectedYField = YFieldCandidates.FirstOrDefault();
|
|
|
|
|
// 그룹은 시간으로 기본
|
|
|
|
|
SelectedGroupField = AvailableFields.FirstOrDefault(f => f.DataType == typeof(DateTime))
|
|
|
|
|
?? AvailableFields.FirstOrDefault();
|
|
|
|
|
BoxWidth = 0.3;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
OnPropertyChanged(nameof(SelectedYFields));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public event PropertyChangedEventHandler? PropertyChanged;
|
|
|
|
|
|