From 2e5647572551ac3915f0e64059c1fd915256b947 Mon Sep 17 00:00:00 2001 From: hj615 Date: Mon, 25 Aug 2025 10:43:38 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=97=90=EB=84=88=EC=A7=80=20->=20?= =?UTF-8?q?=EC=98=A8=EC=8B=A4=EA=B0=80=EC=8A=A4=20=EB=B3=80=ED=99=98?= =?UTF-8?q?=EC=8B=9D=20=EB=B0=8F=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=9C?= =?UTF-8?q?=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SmartAquaViewer/Classes/Converter.cs | 41 ++++ SmartAquaViewer/View/EnegyView.xaml | 35 ++- SmartAquaViewer/View/EnegyView.xaml.cs | 5 - SmartAquaViewer/View/GreenHouseView.xaml | 71 +++++- SmartAquaViewer/ViewModel/EnergyViewModel.cs | 10 +- .../ViewModel/GraphControlViewModel.cs | 3 +- .../ViewModel/GreenHouseGasViewModel.cs | 202 +++++++++++++++++- 7 files changed, 347 insertions(+), 20 deletions(-) diff --git a/SmartAquaViewer/Classes/Converter.cs b/SmartAquaViewer/Classes/Converter.cs index 19c258c..a955903 100644 --- a/SmartAquaViewer/Classes/Converter.cs +++ b/SmartAquaViewer/Classes/Converter.cs @@ -31,4 +31,45 @@ namespace SmartAquaViewer.Classes return value as string; } } + + public class InverseBoolConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + => !(value is bool b && b); + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + => !(value is bool b && b); + } + + public class EnergyToGreenHouseGasConverter : IValueConverter + { + // 2024년 배출계수 (kgCO₂/kWh) + private const double EmissionFactor = 0.4747; + + /// + /// kWh → tCO₂ 변환 + /// + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is double kWh) + { + double tonCO2 = (kWh * EmissionFactor) / 1000.0; + return tonCO2.ToString("F2"); // 소수점 3자리 + } + return null; + } + + /// + /// tCO₂ → kWh 역변환 (Binding TwoWay 대응) + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (double.TryParse(value?.ToString(), out double tonCO2)) + { + double kWh = (tonCO2 * 1000.0) / EmissionFactor; + return kWh; + } + return null; + } + } } diff --git a/SmartAquaViewer/View/EnegyView.xaml b/SmartAquaViewer/View/EnegyView.xaml index 74850c4..bc56c08 100644 --- a/SmartAquaViewer/View/EnegyView.xaml +++ b/SmartAquaViewer/View/EnegyView.xaml @@ -6,8 +6,14 @@ xmlns:local="clr-namespace:SmartAquaViewer.View" xmlns:control="clr-namespace:SmartAquaViewer.Controls" xmlns:helper="clr-namespace:SmartAquaViewer.Helper" + xmlns:classes="clr-namespace:SmartAquaViewer.Classes" mc:Ignorable="d" d:DesignHeight="940" d:DesignWidth="1650"> + + + + + @@ -85,7 +91,7 @@ HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto" HorizontalAlignment="Center"> + RowStyle="{StaticResource DataGridRowStyle}" ColumnHeaderStyle="{StaticResource DataGridColumnHeaderStyle}"> + Style="{StaticResource MaterialDesignUserForegroundRadioButton}" + IsChecked="{Binding UseAverage, Converter={StaticResource InverseBoolConverter}, Mode=TwoWay}"/> + Style="{StaticResource MaterialDesignUserForegroundRadioButton}" + IsChecked="{Binding UseAverage, Mode=TwoWay}"/> @@ -279,9 +287,22 @@ - + + + + + diff --git a/SmartAquaViewer/View/EnegyView.xaml.cs b/SmartAquaViewer/View/EnegyView.xaml.cs index b279bff..4da873d 100644 --- a/SmartAquaViewer/View/EnegyView.xaml.cs +++ b/SmartAquaViewer/View/EnegyView.xaml.cs @@ -24,10 +24,5 @@ namespace SmartAquaViewer.View { InitializeComponent(); } - - private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - - } } } diff --git a/SmartAquaViewer/View/GreenHouseView.xaml b/SmartAquaViewer/View/GreenHouseView.xaml index c7368f7..150ba6e 100644 --- a/SmartAquaViewer/View/GreenHouseView.xaml +++ b/SmartAquaViewer/View/GreenHouseView.xaml @@ -4,25 +4,90 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:local="clr-namespace:SmartAquaViewer.View" + xmlns:helper="clr-namespace:SmartAquaViewer.Helper" + xmlns:classes="clr-namespace:SmartAquaViewer.Classes" mc:Ignorable="d" d:DesignHeight="940" d:DesignWidth="1650"> + + + + + + - - + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + _useAverage; set { _useAverage = value; OnPropertyChanged(); } } + + private bool _showMarkers; public bool ShowMarkers { get => _showMarkers; set { _showMarkers = value; OnPropertyChanged(); } } private bool _showLegends; public bool ShowLegends { get => _showLegends; set { _showLegends = value; OnPropertyChanged(); } } + private bool _isDonut; + public bool IsDonut { get => _isDonut; set { _isDonut = value; OnPropertyChanged(); } } + public ICommand DrawGraphCommand { get; } @@ -277,7 +283,7 @@ namespace SmartAquaViewer.ViewModel GraphControlVM.SetStackAreaPlot(WaterQualityList, SelectedYFields, ShowMarkers, ShowLegends); break; case GraphType.PIE: - GraphControlVM.SetPieChart(WaterQualityList, SelectedYFields); + GraphControlVM.SetPieChart(WaterQualityList, SelectedYFields, UseAverage, IsDonut); break; default: break; diff --git a/SmartAquaViewer/ViewModel/GraphControlViewModel.cs b/SmartAquaViewer/ViewModel/GraphControlViewModel.cs index 9249a95..e4ab0ed 100644 --- a/SmartAquaViewer/ViewModel/GraphControlViewModel.cs +++ b/SmartAquaViewer/ViewModel/GraphControlViewModel.cs @@ -594,6 +594,7 @@ namespace SmartAquaViewer.ViewModel AngleSpan = 360, StartAngle = 0, StrokeThickness = 0.5, + InsideLabelFormat = "{1}\n {0:F2}", InsideLabelPosition = 0.8, OutsideLabelFormat = null // 라벨은 내부만 }; @@ -609,8 +610,6 @@ namespace SmartAquaViewer.ViewModel Model.Title = $"설비별 {(useAverage ? "평균" : "합계")} 소비 비중"; Model.Series.Add(ps); - Model.IsLegendVisible = true; - Model.InvalidatePlot(true); } diff --git a/SmartAquaViewer/ViewModel/GreenHouseGasViewModel.cs b/SmartAquaViewer/ViewModel/GreenHouseGasViewModel.cs index 8b25718..ec84244 100644 --- a/SmartAquaViewer/ViewModel/GreenHouseGasViewModel.cs +++ b/SmartAquaViewer/ViewModel/GreenHouseGasViewModel.cs @@ -1,18 +1,218 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; +using System.Windows.Input; +using SmartAquaViewer.Controls; +using SmartAquaViewer.DataAnalysis; +using SmartAquaViewer.Model; namespace SmartAquaViewer.ViewModel { public class GreenHouseGasViewModel : INotifyPropertyChanged { + public GraphControlViewModel GraphControlVM { get; } = new GraphControlViewModel(); + public ObservableCollection GraphTypes { get; } + + public List WaterQualityList { get; set; } + + 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 + { + get => _selectedGraphType; + set + { + if (_selectedGraphType != value) + { + _selectedGraphType = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(ShowXSelector)); + RebuildFieldCandidates(); + + var idx = GraphTypes.IndexOf(value); + if (idx != -1 && idx != _selectedGraphIndex) + { + _selectedGraphIndex = idx; + OnPropertyChanged(nameof(SelectedGraphIndex)); + } + } + } + } + + private StepFieldKind _selectedKind = StepFieldKind.Sensor; // 기본값은 센서 + public StepFieldKind SelectedKind + { + get => _selectedKind; + set + { + if (_selectedKind != value) + { + _selectedKind = value; + OnPropertyChanged(); + } + } + } + + public bool ShowXSelector => SelectedGraphType == GraphType.SCATTER; + + // [필드 후보 목록] 탭/시스템에 따라 달라짐 + 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(); } } + } + + private bool _showMarkers; // Line + public bool ShowMarkers { get => _showMarkers; set { _showMarkers = value; OnPropertyChanged(); } } + + private bool _showLegends; + public bool ShowLegends { get => _showLegends; set { _showLegends = value; OnPropertyChanged(); } } + + public ICommand DrawGraphCommand { get; } + public GreenHouseGasViewModel() { - // Initialization logic for GreenHouseGasViewModel can go here + WaterQualityList = Datas.GetWaterQualityVO(); + GraphTypes = new ObservableCollection + { + GraphType.LINE, + GraphType.STACKAREA, + GraphType.PIE + }; + + SelectedKind = StepFieldKind.Energy; // 기본적으로 에너지 관련 필드만 표시 + + DrawGraphCommand = new RelayCommand(DrawGraph); + + RebuildAvailableFields(); + RebuildFieldCandidates(); + } + + private void DrawGraph(object obj) + { + switch (SelectedGraphType) + { + case GraphType.LINE: + GraphControlVM.SetMultiLineGraph(WaterQualityList, SelectedYFields, ShowMarkers, ShowLegends); + break; + case GraphType.STACKAREA: + GraphControlVM.SetStackAreaPlot(WaterQualityList, SelectedYFields, ShowMarkers, ShowLegends); + break; + case GraphType.PIE: + GraphControlVM.SetPieChart(WaterQualityList, SelectedYFields); + break; + default: + break; + } + } + + private void RebuildAvailableFields() + { + AvailableFields.Clear(); + + // 공통 시간 + AvailableFields.Add(new FieldItem { Name = "RecordedTime", Display = "시간", DataType = typeof(DateTime) }); + + AvailableFields.Add(new FieldItem { Name = "TotalEnergy", Display = "총 전력", DataType = typeof(double), Kind = StepFieldKind.Energy }); + AvailableFields.Add(new FieldItem { Name = "Filtering.SandFilterEnergy", Display = "모래여과기", DataType = typeof(double), Kind = StepFieldKind.Energy }); + AvailableFields.Add(new FieldItem { Name = "Filtering.CirculationPumpEnergy", Display = "순환펌프", DataType = typeof(double), Kind = StepFieldKind.Energy }); + AvailableFields.Add(new FieldItem { Name = "Filtering.HeatPumpEnergy", Display = "히트펌프", DataType = typeof(double), Kind = StepFieldKind.Energy }); + AvailableFields.Add(new FieldItem { Name = "Filtering.AirBlowerEnergy", Display = "에어브로와", DataType = typeof(double), Kind = StepFieldKind.Energy }); + AvailableFields.Add(new FieldItem { Name = "Sterilizing.OzoneGeneratorEnergy", Display = "오존발생기", DataType = typeof(double), Kind = StepFieldKind.Energy }); + AvailableFields.Add(new FieldItem { Name = "Sterilizing.UVSterilizerEnergy", Display = "자외선 살균기", DataType = typeof(double), Kind = StepFieldKind.Energy }); + AvailableFields.Add(new FieldItem { Name = "Sterilizing.OzoneDissolverEnergy", Display = "오존용해장치", DataType = typeof(double), Kind = StepFieldKind.Energy }); + AvailableFields.Add(new FieldItem { Name = "Sterilizing.ExcessOzoneDestroyerEnergy", Display = "배오존장치", DataType = typeof(double), Kind = StepFieldKind.Energy }); + } + + private void RebuildFieldCandidates() + { + // 후보 초기화 + XFieldCandidates.Clear(); + YFieldCandidates.Clear(); + + // X축: 시간 우선 + foreach (var f in AvailableFields) + { + XFieldCandidates.Add(f); + if (SelectedGraphType == GraphType.LINE || SelectedGraphType == GraphType.STACKAREA) break; + } + SelectedXField = AvailableFields.FirstOrDefault(f => f.DataType == typeof(DateTime)) + ?? AvailableFields.FirstOrDefault(); + + IEnumerable src = AvailableFields.Where(f => f.Kind == SelectedKind); + + if (SelectedGraphType is GraphType.LINE or GraphType.STACKAREA or GraphType.PIE) + { + // 수치형만 (LINE/STACKAREA/PIE 연속값 위주) + src = src.Where(f => f.DataType == typeof(double)); + } + + // Y축 후보: 수치형 + foreach (var f in src) + { + if (SelectedGraphType is GraphType.STACKAREA or GraphType.PIE && f.Name.Equals("TotalEnergy")) continue; + YFieldCandidates.Add(f); + } + + // 기본 선택 세팅 (타입별) + SelectedYFields.Clear(); + SelectedYField = null; + + switch (SelectedGraphType) + { + case GraphType.LINE: + //var def = YFieldCandidates.FirstOrDefault(); + //if (def != null) SelectedYFields.Add(def); + break; + case GraphType.STACKAREA: + SelectedYField = YFieldCandidates.FirstOrDefault(); + break; + + case GraphType.PIE: + //SelectedYField = YFieldCandidates.FirstOrDefault(); + break; + } + OnPropertyChanged(nameof(SelectedYFields)); } public event PropertyChangedEventHandler? PropertyChanged;