diff --git a/SmartAquaViewer/Helper/ComboBoxHelper.cs b/SmartAquaViewer/Helper/ComboBoxHelper.cs
new file mode 100644
index 0000000..ea10a35
--- /dev/null
+++ b/SmartAquaViewer/Helper/ComboBoxHelper.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace SmartAquaViewer.Helper
+{
+ public static class ComboBoxHelper
+ {
+ public static readonly DependencyProperty SelectFirstOnItemsChangeProperty =
+ DependencyProperty.RegisterAttached(
+ "SelectFirstOnItemsChange", typeof(bool), typeof(ComboBoxHelper),
+ new PropertyMetadata(false, OnSelectFirstChanged));
+
+ public static void SetSelectFirstOnItemsChange(DependencyObject obj, bool value)
+ => obj.SetValue(SelectFirstOnItemsChangeProperty, value);
+ public static bool GetSelectFirstOnItemsChange(DependencyObject obj)
+ => (bool)obj.GetValue(SelectFirstOnItemsChangeProperty);
+
+ private static void OnSelectFirstChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is not ComboBox cb) return;
+
+ void TrySelectFirst()
+ {
+ if (cb.Items.Count > 0 && cb.SelectedIndex < 0)
+ cb.SelectedIndex = 0;
+ }
+
+ cb.Loaded += (_, __) => TrySelectFirst();
+
+ void Hook()
+ {
+ if (cb.ItemsSource is INotifyCollectionChanged incc)
+ {
+ incc.CollectionChanged -= OnItemsChanged;
+ incc.CollectionChanged += OnItemsChanged;
+ }
+ }
+ void OnItemsChanged(object? s, NotifyCollectionChangedEventArgs args) => TrySelectFirst();
+
+ // ItemsSource가 바뀌어도 다시 훅
+ cb.DataContextChanged += (_, __) => Hook();
+ Hook();
+ }
+ }
+}
diff --git a/SmartAquaViewer/Helper/MultiSelectBehavior.cs b/SmartAquaViewer/Helper/MultiSelectBehavior.cs
new file mode 100644
index 0000000..b68e3a5
--- /dev/null
+++ b/SmartAquaViewer/Helper/MultiSelectBehavior.cs
@@ -0,0 +1,48 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Controls;
+using System.Windows;
+
+namespace SmartAquaViewer.Helper
+{
+ public static class MultiSelectBehavior
+ {
+ public static readonly DependencyProperty SelectedItemsProperty =
+ DependencyProperty.RegisterAttached(
+ "SelectedItems", typeof(IList), typeof(MultiSelectBehavior),
+ new PropertyMetadata(null, OnSelectedItemsChanged));
+
+ public static void SetSelectedItems(DependencyObject element, IList? value)
+ => element.SetValue(SelectedItemsProperty, value);
+ public static IList? GetSelectedItems(DependencyObject element)
+ => (IList?)element.GetValue(SelectedItemsProperty);
+
+ private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+ {
+ if (d is not ListBox lb) return;
+
+ lb.SelectionChanged -= Lb_SelectionChanged;
+ lb.SelectionChanged += Lb_SelectionChanged;
+
+ if (e.NewValue is IList target)
+ {
+ target.Clear();
+ foreach (var item in lb.SelectedItems) target.Add(item);
+ }
+ }
+
+ private static void Lb_SelectionChanged(object? sender, SelectionChangedEventArgs e)
+ {
+ if (sender is not ListBox lb) return;
+ var list = GetSelectedItems(lb);
+ if (list == null) return;
+
+ foreach (var removed in e.RemovedItems) list.Remove(removed);
+ foreach (var added in e.AddedItems) list.Add(added);
+ }
+ }
+}
diff --git a/SmartAquaViewer/View/MonitoringView.xaml b/SmartAquaViewer/View/MonitoringView.xaml
index 77983c7..61878f4 100644
--- a/SmartAquaViewer/View/MonitoringView.xaml
+++ b/SmartAquaViewer/View/MonitoringView.xaml
@@ -132,33 +132,173 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
GraphTypes { get; }
public List 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 AvailableFields { get; } = new();
+
+ // [X축 후보/선택]
+ public ObservableCollection 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 YFieldCandidates { get; } = new();
+
+ // 다중 선택(Y)용
+ public ObservableCollection SelectedYFields { get; } = new();
+
+ // 단일 선택(Y)용
+ private FieldItem? _selectedYField;
+ public FieldItem? SelectedYField
+ {
+ get => _selectedYField;
+ set { if (_selectedYField != value) { _selectedYField = value; OnPropertyChanged(); } }
+ }
+
+ // BoxPlot 전용 그룹 기준(예: 수조/시간 등)
+ public ObservableCollection 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();
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) });
- Debug.WriteLine(graphTypes);
+ 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);
+
+ // 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;