feat: 스택 영역 그래프 생성

hhsung_work
HyungJune Kim 10 months ago
parent 9a2bcb47bd
commit ff95feeafc

@ -18,13 +18,16 @@ namespace SmartAquaViewer.Model
LINE, LINE,
BOX, BOX,
SCATTER, SCATTER,
STEP STEP,
STACKAREA,
PIE
} }
public enum StepFieldKind public enum StepFieldKind
{ {
Status, // 전원/상태 Status, // 전원/상태
Sensor // 센서 값 Sensor, // 센서 값
Energy, // 에너지 소비량
} }
public enum PowerStatus public enum PowerStatus

@ -5,6 +5,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SmartAquaViewer.View" xmlns:local="clr-namespace:SmartAquaViewer.View"
xmlns:control="clr-namespace:SmartAquaViewer.Controls" xmlns:control="clr-namespace:SmartAquaViewer.Controls"
xmlns:helper="clr-namespace:SmartAquaViewer.Helper"
mc:Ignorable="d" mc:Ignorable="d"
d:DesignHeight="940" d:DesignWidth="1650"> d:DesignHeight="940" d:DesignWidth="1650">
<Grid Background="#243851"> <Grid Background="#243851">
@ -122,7 +123,120 @@
<Border Grid.Row="1" Grid.Column="1" Margin="0 0 20 20" CornerRadius="10" <Border Grid.Row="1" Grid.Column="1" Margin="0 0 20 20" CornerRadius="10"
Background="#384659" BorderBrush="#404F63" BorderThickness="1"> Background="#384659" BorderBrush="#404F63" BorderThickness="1">
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<StackPanel>
<Grid Margin="15 15 15 15">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="그래프" VerticalAlignment="Center"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"/>
<ComboBox Margin="15 0 0 0" Height="40" Grid.Column="1"
Style="{StaticResource ComboBoxStyle}"
ItemsSource="{Binding GraphTypes}"
SelectedIndex="{Binding SelectedGraphIndex, Mode=TwoWay}"
helper:ComboBoxHelper.SelectFirstOnItemsChange="True"
IsEditable="False" IsTextSearchEnabled="False"/>
</Grid>
<Grid Margin="15 0">
<Grid.Resources>
<Style TargetType="FrameworkElement">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
<Style x:Key="VisibleWhenLine" TargetType="FrameworkElement" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="LINE">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="VisibleWhenStackArea" TargetType="FrameworkElement" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="STACKAREA">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="VisibleWhenLineNStackArea" TargetType="FrameworkElement" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="LINE">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="STACKAREA">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="VisibleWhenPie" TargetType="FrameworkElement" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="PIE">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel Style="{StaticResource VisibleWhenLineNStackArea}">
<Grid Margin="0 0 0 20" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="X축" VerticalAlignment="Center"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"/>
<TextBlock Text="{Binding SelectedXField.Display}" VerticalAlignment="Center"
Margin="15 0 0 0" Grid.Column="1"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"/>
</Grid>
<Grid Margin="0 0 0 15" Style="{StaticResource VisibleWhenLineNStackArea}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Text="Y축" VerticalAlignment="Top"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"/>
<Border Grid.Column="1" CornerRadius="10"
Background="White" Margin="15 0 0 15">
<ListBox ItemsSource="{Binding YFieldCandidates}"
DisplayMemberPath="Display"
SelectionMode="Extended"
helper:MultiSelectBehavior.SelectedItems="{Binding SelectedYFields, Mode=OneWay}"
helper:MultiSelectBehavior.KeyPath="Key"
helper:MultiSelectBehavior.ValuePath="Value"
Height="Auto" Background="White"
FontSize="16" FontWeight="Bold"
Style="{StaticResource MaterialDesignFilterChipListBox}"/>
</Border>
</Grid>
<StackPanel Orientation="Horizontal" Margin="0 0 0 15" Grid.Row="4">
<CheckBox Content="마커 표시" IsChecked="{Binding ShowMarkers}" Margin="0 0 15 0"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"
VerticalContentAlignment="Center"
Style="{StaticResource MaterialDesignUserForegroundCheckBox}"/>
<CheckBox Content="범례 표시" IsChecked="{Binding ShowLegends}" Margin="0 0 15 0"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"
VerticalContentAlignment="Center"
Style="{StaticResource MaterialDesignUserForegroundCheckBox}"/>
</StackPanel>
</StackPanel>
</Grid>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="15 0" Grid.Row="1" HorizontalAlignment="Right">
<Button Content="그래프 생성" Style="{StaticResource MaterialDesignFlatLightBgButton}"
FontWeight="Bold" Command="{Binding DrawGraphCommand}"/>
</StackPanel>
</Grid>
</Border> </Border>
<Border Grid.Row="2" Grid.Column="1" Margin="0 0 20 20" CornerRadius="10" <Border Grid.Row="2" Grid.Column="1" Margin="0 0 20 20" CornerRadius="10"

@ -384,7 +384,7 @@
</Border> </Border>
</StackPanel> </StackPanel>
<!-- 옵션 --> <!-- 옵션 -->
<StackPanel Orientation="Horizontal" Margin="0 15 0 0" Grid.Row="4"> <StackPanel Orientation="Horizontal" Margin="0 15 0 0">
<CheckBox Content="마커 표시" IsChecked="{Binding ShowMarkers}" Margin="0 0 15 0" <CheckBox Content="마커 표시" IsChecked="{Binding ShowMarkers}" Margin="0 0 15 0"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White" FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"
VerticalContentAlignment="Center" VerticalContentAlignment="Center"

@ -1,11 +1,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Input;
using OxyPlot.Axes; using OxyPlot.Axes;
using SmartAquaViewer.Controls;
using SmartAquaViewer.DataAnalysis; using SmartAquaViewer.DataAnalysis;
using SmartAquaViewer.Model; using SmartAquaViewer.Model;
@ -13,6 +16,9 @@ namespace SmartAquaViewer.ViewModel
{ {
public class EnergyViewModel : INotifyPropertyChanged public class EnergyViewModel : INotifyPropertyChanged
{ {
public GraphControlViewModel GraphControlVM { get; } = new GraphControlViewModel();
public ObservableCollection<GraphType> GraphTypes { get; }
public List<WaterQualityVO> WaterQualityList { get; set; } public List<WaterQualityVO> WaterQualityList { get; set; }
private double _totalEnergy; private double _totalEnergy;
@ -141,37 +147,211 @@ namespace SmartAquaViewer.ViewModel
} }
} }
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<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(); } }
}
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 EnergyViewModel() public EnergyViewModel()
{ {
WaterQualityList = Datas.GetWaterQualityVO(); WaterQualityList = Datas.GetWaterQualityVO();
GraphTypes = new ObservableCollection<GraphType>
TotalSandFilterEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "Filtering.SandFilterEnergy") ?? 0); {
TotalCirculationPumpEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "Filtering.CirculationPumpEnergy") ?? 0); GraphType.LINE,
TotalHeatPumpEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "Filtering.HeatPumpEnergy") ?? 0); GraphType.STACKAREA,
TotalAirBlowerEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "Filtering.AirBlowerEnergy") ?? 0); GraphType.PIE
TotalOzoneGeneratorEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "Sterilizing.OzoneGeneratorEnergy") ?? 0);
TotalUVSterilizerEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "Sterilizing.UVSterilizerEnergy") ?? 0);
TotalOzoneDissolverEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "Sterilizing.OzoneDissolverEnergy") ?? 0);
TotalExcessOzoneDestroyerEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "Sterilizing.ExcessOzoneDestroyerEnergy") ?? 0);
TotalEnergy = WaterQualityList.Sum(vo => ResolveEnergyField(vo, "TotalEnergy") ?? 0);
}
private double? ResolveEnergyField(WaterQualityVO vo, string fieldName)
{
return fieldName switch
{
"Filtering.SandFilterEnergy" => vo.Filtering.SandFilterEnergy,
"Filtering.CirculationPumpEnergy" => vo.Filtering.CirculationPumpEnergy,
"Filtering.HeatPumpEnergy" => vo.Filtering.HeatPumpEnergy,
"Filtering.AirBlowerEnergy" => vo.Filtering.AirBlowerEnergy,
"Sterilizing.OzoneGeneratorEnergy" => vo.Sterilizing.OzoneGeneratorEnergy,
"Sterilizing.UVSterilizerEnergy" => vo.Sterilizing.UVSterilizerEnergy,
"Sterilizing.OzoneDissolverEnergy" => vo.Sterilizing.OzoneDissolverEnergy,
"Sterilizing.ExcessOzoneDestroyerEnergy" => vo.Sterilizing.ExcessOzoneDestroyerEnergy,
"TotalEnergy" => vo.TotalEnergy,
_ => null
}; };
TotalSandFilterEnergy = WaterQualityList.Sum(vo => vo.Filtering.SandFilterEnergy);
TotalCirculationPumpEnergy = WaterQualityList.Sum(vo => vo.Filtering.CirculationPumpEnergy);
TotalHeatPumpEnergy = WaterQualityList.Sum(vo => vo.Filtering.HeatPumpEnergy);
TotalAirBlowerEnergy = WaterQualityList.Sum(vo => vo.Filtering.AirBlowerEnergy);
TotalOzoneGeneratorEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneGeneratorEnergy);
TotalUVSterilizerEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.UVSterilizerEnergy);
TotalOzoneDissolverEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.OzoneDissolverEnergy);
TotalExcessOzoneDestroyerEnergy = WaterQualityList.Sum(vo => vo.Sterilizing.ExcessOzoneDestroyerEnergy);
TotalEnergy = WaterQualityList.Sum(vo => vo.TotalEnergy);
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:
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<FieldItem> 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 == GraphType.STACKAREA && 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; public event PropertyChangedEventHandler? PropertyChanged;

@ -90,6 +90,72 @@ namespace SmartAquaViewer.ViewModel
Model.InvalidatePlot(true); Model.InvalidatePlot(true);
} }
public void SetMultiLineGraph(
List<WaterQualityVO> collection,
ObservableCollection<FieldItem> yFields,
bool showMarker, bool showLegend)
{
Model.Series.Clear();
Model.Axes.Clear();
var xAxis = new DateTimeAxis
{
Position = AxisPosition.Bottom,
Title = "시간",
StringFormat = "HH:mm:ss",
IntervalType = DateTimeIntervalType.Minutes,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
var yAxis = new LinearAxis
{
Position = AxisPosition.Left,
Title = "전력 (kW)",
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
Model.Axes.Add(xAxis);
Model.Axes.Add(yAxis);
foreach (var field in yFields)
{
var series = new LineSeries()
{
Title = field.Display,
MarkerType = showMarker ? MarkerType.Circle : MarkerType.None,
MarkerSize = showMarker ? 3 : 0
};
foreach (var r in collection.OrderBy(r => r.RecordedTime))
{
double? y = null;
y = ResolveEnergyField(r, field.Name!);
if (!y.HasValue) continue;
series.Points.Add(new DataPoint(
DateTimeAxis.ToDouble(r.RecordedTime),
y.Value));
}
if (series.Points.Count > 0)
{
// 트래커 포맷: 시간, 수조, 지표, 값
series.TrackerFormatString =
$"시간: {{2:HH:mm}}\n{field.Display}: {{4:0.###}}";
Model.Series.Add(series);
}
}
Model.Legends.Clear();
Model.IsLegendVisible = showLegend;
Model.Legends.Add(new Legend
{
LegendPlacement = LegendPlacement.Outside,
LegendPosition = LegendPosition.RightTop,
LegendOrientation = LegendOrientation.Vertical,
LegendTitle = "전력 소비량",
TextColor = OxyColors.Black
});
Model.InvalidatePlot(true);
}
public void SetDefaultLineGraph( public void SetDefaultLineGraph(
List<WaterQualityVO> collection, List<WaterQualityVO> collection,
MonitorTab selectedTab, MonitorTab selectedTab,
@ -372,7 +438,7 @@ namespace SmartAquaViewer.ViewModel
Model.InvalidatePlot(true); Model.InvalidatePlot(true);
} }
public void SetStatusSeriesStopPlot(List<WaterQualityVO> rows, public void SetStatusSeriesStopPlot(List<WaterQualityVO> collection,
FieldItem yAxisField, bool showMarker = false) FieldItem yAxisField, bool showMarker = false)
{ {
Model.Series.Clear(); Model.Series.Clear();
@ -384,7 +450,7 @@ namespace SmartAquaViewer.ViewModel
}; };
int i = 0; int i = 0;
foreach (var r in rows.OrderBy(r => r.RecordedTime)) foreach (var r in collection.OrderBy(r => r.RecordedTime))
{ {
string? rawValue = ResolveStatus(r, yAxisField.Name); string? rawValue = ResolveStatus(r, yAxisField.Name);
if (rawValue != null) if (rawValue != null)
@ -407,6 +473,97 @@ namespace SmartAquaViewer.ViewModel
Model.InvalidatePlot(true); Model.InvalidatePlot(true);
} }
public void SetStackAreaPlot(List<WaterQualityVO> collection, ObservableCollection<FieldItem> yFields,
bool showMarker, bool showLegends)
{
Model.Series.Clear();
Model.Axes.Clear();
// X축: 시간
var xAxis = new DateTimeAxis
{
Position = AxisPosition.Bottom,
Title = "시간",
StringFormat = "HH:mm:ss",
IntervalType = DateTimeIntervalType.Minutes,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
// Y축: 전력
var yAxis = new LinearAxis
{
Position = AxisPosition.Left,
Title = "전력 (kW)",
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
Model.Axes.Add(xAxis);
Model.Axes.Add(yAxis);
// 시간순 정렬
var records = collection.OrderBy(r => r.RecordedTime).ToList();
int n = records.Count;
// 누적 버퍼
double[] prevStack = new double[n];
foreach (var field in yFields)
{
var area = new AreaSeries
{
Title = field.Display,
StrokeThickness = 1,
MarkerType = showMarker ? MarkerType.Circle : MarkerType.None,
};
var upper = new List<DataPoint>();
var lower = new List<DataPoint>();
for (int i = 0; i < n; i++)
{
double? y = ResolveEnergyField(records[i], field.Name!);
double value = y ?? 0;
double x = DateTimeAxis.ToDouble(records[i].RecordedTime);
// 현재 값 누적
double bottom = prevStack[i];
double top = bottom + value;
lower.Add(new DataPoint(x, bottom));
upper.Add(new DataPoint(x, top));
prevStack[i] = top;
}
area.Points.AddRange(upper); // 위쪽 경계
area.Points2.AddRange(lower); // 아래쪽 경계
Model.Series.Add(area);
}
// 레전드
Model.Legends.Clear();
Model.IsLegendVisible = showLegends;
Model.Legends.Add(new Legend
{
LegendPlacement = LegendPlacement.Outside,
LegendPosition = LegendPosition.RightTop,
LegendOrientation = LegendOrientation.Vertical,
LegendTitle = "전력 소비량",
TextColor = OxyColors.Black
});
Model.InvalidatePlot(true);
}
public void SetStackAreaPlot(
List<WaterQualityVO> rows,
FieldItem yAxisField)
{
}
private DateTime FloorToBucket(DateTime dt, TimeSpan bucket) private DateTime FloorToBucket(DateTime dt, TimeSpan bucket)
{ {
long ticks = bucket.Ticks; long ticks = bucket.Ticks;
@ -478,6 +635,23 @@ namespace SmartAquaViewer.ViewModel
}; };
} }
private double? ResolveEnergyField(WaterQualityVO vo, string fieldName)
{
return fieldName switch
{
"Filtering.SandFilterEnergy" => vo.Filtering.SandFilterEnergy,
"Filtering.CirculationPumpEnergy" => vo.Filtering.CirculationPumpEnergy,
"Filtering.HeatPumpEnergy" => vo.Filtering.HeatPumpEnergy,
"Filtering.AirBlowerEnergy" => vo.Filtering.AirBlowerEnergy,
"Sterilizing.OzoneGeneratorEnergy" => vo.Sterilizing.OzoneGeneratorEnergy,
"Sterilizing.UVSterilizerEnergy" => vo.Sterilizing.UVSterilizerEnergy,
"Sterilizing.OzoneDissolverEnergy" => vo.Sterilizing.OzoneDissolverEnergy,
"Sterilizing.ExcessOzoneDestroyerEnergy" => vo.Sterilizing.ExcessOzoneDestroyerEnergy,
"TotalEnergy" => vo.TotalEnergy,
_ => null
};
}
private double? ResolveUvPowerPerId(WaterQualityVO r, string fieldName) private double? ResolveUvPowerPerId(WaterQualityVO r, string fieldName)
{ {
// 케이스 A // 케이스 A

@ -254,7 +254,7 @@ namespace SmartAquaViewer.ViewModel
g.OrderBy(r => r.RecordedTime)) g.OrderBy(r => r.RecordedTime))
); );
GraphTypes = new ObservableCollection<GraphType>(); GraphTypes = [];
SelectedTab = MonitorTab.Tank; // Default system SelectedTab = MonitorTab.Tank; // Default system
SetGraphType(); SetGraphType();

Loading…
Cancel
Save