fix: 그래프 생성 기능 수정

prototype
HyungJune Kim 10 months ago
parent 9a3ae0cda2
commit 6354aa82f7

@ -186,6 +186,18 @@
<RowDefinition/>
<RowDefinition Height="50"/>
</Grid.RowDefinitions>
<Grid.Resources>
<Style TargetType="FrameworkElement">
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
<Style x:Key="VisibleWhenTank" TargetType="FrameworkElement" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedTab}" Value="Tank">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Resources>
<StackPanel>
<Grid Margin="15 15 15 10">
<Grid.ColumnDefinitions>
@ -202,7 +214,7 @@
helper:ComboBoxHelper.SelectFirstOnItemsChange="True"
IsEditable="False" IsTextSearchEnabled="False"/>
</Grid>
<Grid Margin="15 0">
<Grid Margin="15 0 15 10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80"/>
<ColumnDefinition/>
@ -216,7 +228,7 @@
DisplayMemberPath="Display"/>
</Grid>
<StackPanel Margin="15 15 15 0">
<StackPanel Margin="15 0" Style="{StaticResource VisibleWhenTank}">
<TextBlock Text="수조 (복수 선택)" VerticalAlignment="Center"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"/>
@ -239,23 +251,6 @@
<Setter Property="Visibility" Value="Collapsed"/>
</Style>
<!-- 보이기 토글용 스타일 -->
<Style x:Key="VisibleWhenTrue" 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="STEP">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="VisibleWhenTankNLine" TargetType="FrameworkElement" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsTankAndLine}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
<Style x:Key="VisibleWhenLine" TargetType="FrameworkElement" BasedOn="{StaticResource {x:Type FrameworkElement}}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="LINE">
@ -308,23 +303,6 @@
DisplayMemberPath="Display" Margin="15 0 0 0"
Height="40" Style="{StaticResource ComboBoxStyle}"/>
</Grid>
<!-- 옵션 -->
<StackPanel Orientation="Horizontal" Margin="0 15 0 0" 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}"/>
<!--<CheckBox Content="스무딩" IsChecked="{Binding UseSmoothing}"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"
VerticalContentAlignment="Center"
Style="{StaticResource MaterialDesignUserForegroundCheckBox}"/>-->
</StackPanel>
</StackPanel>
<!--STEP-->
@ -354,7 +332,7 @@
</StackPanel>
</Grid>
<Grid Margin="0 5 0 0">
<!--<Grid Margin="0 5 0 0">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed"/>
@ -376,10 +354,10 @@
Grid.Column="1"
DisplayMemberPath="Display" Margin="15 0 0 0"
Height="40" Style="{StaticResource ComboBoxStyle}"/>
</Grid>
</Grid>-->
<StackPanel>
<StackPanel.Style>
<StackPanel Margin="0 5 0 0">
<!--<StackPanel.Style>
<Style TargetType="StackPanel">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
@ -388,7 +366,7 @@
</DataTrigger>
</Style.Triggers>
</Style>
</StackPanel.Style>
</StackPanel.Style>-->
<TextBlock Text="Y축 (복수 선택)" VerticalAlignment="Center"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"/>
<!-- SelectedItems 바인딩을 위한 간단 Behavior는 아래 3) 참고 -->
@ -402,17 +380,6 @@
Style="{StaticResource MaterialDesignFilterChipListBox}"/>
</Border>
</StackPanel>
<!-- 옵션 -->
<StackPanel Orientation="Horizontal" Margin="0 15 0 0">
<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>
<!-- SCATTER: 단일 Y + 옵션 -->
@ -445,12 +412,6 @@
Grid.Row="1" Grid.Column="2" VerticalAlignment="Center"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"/>
</Grid>
<CheckBox Content="회귀선" IsChecked="{Binding ShowRegression}"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"
Margin="0" Grid.Row="1" Grid.Column="3"
VerticalContentAlignment="Center"
Style="{StaticResource MaterialDesignUserForegroundCheckBox}"/>
</StackPanel>
<!-- BOX: 값 필드 + 그룹 필드 + 옵션 -->
@ -507,7 +468,45 @@
</StackPanel>
</Grid>
</StackPanel>
<StackPanel Orientation="Horizontal" Grid.Row="1" Margin="15 0">
<CheckBox Content="범례 표시" IsChecked="{Binding ShowLegends}"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"
Margin="0 0 15 0" VerticalContentAlignment="Center"
Style="{StaticResource MaterialDesignUserForegroundCheckBox}"/>
<CheckBox Content="마커 표시" IsChecked="{Binding ShowMarkers}"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"
Margin="0 0 15 0" VerticalContentAlignment="Center">
<CheckBox.Style>
<Style TargetType="CheckBox" BasedOn="{StaticResource MaterialDesignUserForegroundCheckBox}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="LINE">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="STEP">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
<CheckBox Content="회귀선" IsChecked="{Binding ShowRegression}"
FontSize="20" FontFamily="{StaticResource SCDream4}" Foreground="White"
Margin="0 0 15 0" VerticalContentAlignment="Center">
<CheckBox.Style>
<Style TargetType="CheckBox" BasedOn="{StaticResource MaterialDesignUserForegroundCheckBox}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedGraphType}" Value="SCATTER">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="15 0" Grid.Row="1" HorizontalAlignment="Right">
<Button Content="그래프 생성" Style="{StaticResource MaterialDesignFlatLightBgButton}"
FontWeight="Bold" Command="{Binding DrawGraphCommand}"/>

@ -1,4 +1,5 @@
using System.Collections.ObjectModel;
using Microsoft.VisualBasic;
using OxyPlot;
using OxyPlot.Axes;
using OxyPlot.Legends;
@ -24,26 +25,37 @@ namespace SmartAquaViewer.ViewModel
}
public void SetTankLineGraph(
Dictionary<int, ObservableCollection<WaterQualityVO>> collection,
ReadOnlyObservableCollection<WaterQualityVO> collection,
List<int>? selectedTankNums,
FieldItem? xField, FieldItem? yField,
bool isMarker, bool showLegends)
{
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
};
bool xIsTime = string.Equals(xField!.Name, "RecordedTime", StringComparison.OrdinalIgnoreCase);
Axis xAxis = xIsTime
? new DateTimeAxis
{
Position = AxisPosition.Bottom,
Title = xField.Display,
StringFormat = "MM-dd\nHH:mm",
IntervalType = DateTimeIntervalType.Minutes,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
}
: new LinearAxis
{
Position = AxisPosition.Bottom,
Title = xField.Display,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
var yAxis = new LinearAxis
{
Position = AxisPosition.Left,
Title = yField.Display,
Title = yField!.Display,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
@ -51,34 +63,57 @@ namespace SmartAquaViewer.ViewModel
Model.Axes.Add(xAxis);
Model.Axes.Add(yAxis);
foreach (var (tankNum, datas) in collection.OrderBy(x => x.Key))
{
if (datas == null || datas.Count == 0)
continue;
var allTankIds = collection.SelectMany(w => w.Tanks)
.Select(t => t.Number)
.Distinct()
.OrderBy(id => id)
.ToList();
var tankIds = (selectedTankNums == null || !selectedTankNums.Any())
? allTankIds
: allTankIds.Where(id => selectedTankNums.Contains(id)).ToList();
if (tankIds.Count == 0) { Model.InvalidatePlot(true); return; }
var orderedCollection = collection.OrderBy(r => r.RecordedTime);
foreach (var tankId in tankIds)
{
var series = new LineSeries()
{
Title = tankNum.ToString(),
Title = tankId.ToString(),
MarkerType = isMarker ? MarkerType.Circle : MarkerType.None,
MarkerSize = isMarker ? 3 : 0
};
foreach (var data in datas.OrderBy(d => d.RecordedTime))
var points = new List<DataPoint>();
foreach (var w in orderedCollection)
{
var tank = data.Tanks.Find(t => t.Number.Equals(tankNum));
var y = ResolveTank(tank, yField.Name!);
if (!y.HasValue) continue;
var tank = w.Tanks.FirstOrDefault(t => t.Number == tankId);
if (tank is null) continue;
series.Points.Add(new DataPoint(
DateTimeAxis.ToDouble(data.RecordedTime), // 여기서 recordedTime 사용
y.Value));
double? xVal = xIsTime
? DateTimeAxis.ToDouble(w.RecordedTime)
: ResolveTank(tank, xField.Name!); // 리플렉션: double? 반환
double? yVal = ResolveTank(tank, yField.Name!);
// ⚠️ 둘 중 하나라도 없으면 스킵
if (!xVal.HasValue || !yVal.HasValue) continue;
points.Add(new DataPoint(xVal.Value, yVal.Value));
}
foreach (var p in points.OrderBy(p => p.X))
series.Points.Add(p);
if (series.Points.Count > 0)
{
var xTracker = xIsTime ? $"시간: {{2:HH:mm}}" : $"{xField.Display}: {{2:0.###}}";
// 트래커 포맷: 시간, 수조, 지표, 값
series.TrackerFormatString =
$"수조 {tankNum}\n시간: {{2:HH:mm}}\n{yField.Display}: {{4:0.###}}";
$"수조 {tankId}\n{xTracker}\n{yField.Display}: {{4:0.###}}";
Model.Series.Add(series);
}
}
@ -173,14 +208,25 @@ namespace SmartAquaViewer.ViewModel
Model.Series.Clear();
Model.Axes.Clear();
var xAxis = new DateTimeAxis
{
Position = AxisPosition.Bottom,
StringFormat = "HH:mm:ss",
IntervalType = DateTimeIntervalType.Minutes,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
bool xIsTime = string.Equals(xField!.Name, "RecordedTime", StringComparison.OrdinalIgnoreCase);
Axis xAxis = xIsTime
? new DateTimeAxis
{
Position = AxisPosition.Bottom,
Title = xField.Display,
StringFormat = "HH:mm:ss",
IntervalType = DateTimeIntervalType.Minutes,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
}
: new LinearAxis
{
Position = AxisPosition.Bottom,
Title = xField.Display,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
var yAxis = new LinearAxis
{
Position = AxisPosition.Left,
@ -198,8 +244,23 @@ namespace SmartAquaViewer.ViewModel
MarkerSize = isMarker ? 3 : 0
};
var points = new List<DataPoint>();
foreach (var r in collection.OrderBy(r => r.RecordedTime))
{
double? xVal = xIsTime
? DateTimeAxis.ToDouble(r.RecordedTime)
: double.NaN;
if (xVal.HasValue && double.IsNaN(xVal.Value))
{
xVal = selectedTab switch
{
MonitorTab.Filter => ResolveFilter(r, xField.Name!),
MonitorTab.Sterilizer => ResolveSterilizer(r, xField.Name!),
_ => xVal
};
}
double? y = null;
if (selectedTab.Equals(MonitorTab.Filter))
y = ResolveFilter(r, yField.Name!);
@ -208,16 +269,21 @@ namespace SmartAquaViewer.ViewModel
if (!y.HasValue) continue;
series.Points.Add(new DataPoint(
DateTimeAxis.ToDouble(r.RecordedTime),
points.Add(new DataPoint(
xVal!.Value,
y.Value));
}
if (series.Points.Count > 0)
{
// 트래커 포맷: 시간, 수조, 지표, 값
series.TrackerFormatString =
$"시간: {{2:HH:mm}}\n{yField.Display}: {{4:0.###}}";
}
foreach (var p in points.OrderBy(p => p.X))
series.Points.Add(p);
if (series.Points.Count > 0)
{
var xTracker = xIsTime ? $"시간: {{2:HH:mm}}" : $"{xField.Display}: {{2:0.###}}";
// 트래커 포맷: 시간, 수조, 지표, 값
series.TrackerFormatString =
$"{xTracker}\n{yField.Display}: {{4:0.###}}";
}
Model.Series.Add(series);
@ -330,22 +396,31 @@ namespace SmartAquaViewer.ViewModel
}
public void SetScatterPlot(
List<WaterQualityVO> rows,
ReadOnlyObservableCollection<WaterQualityVO> collection,
FieldItem xAxisField,
FieldItem yAxisField,
List<int> selectedTankNums,
double markerSize = 3,
bool showRegression = false)
bool showRegression = false,
bool showLegends = true)
{
Model.Series.Clear();
Model.Axes.Clear();
var xAxis = new LinearAxis
{
Position = AxisPosition.Bottom,
Title = xAxisField.Display,
MajorGridlineStyle = LineStyle.Solid,
MinorGridlineStyle = LineStyle.Dot
};
bool xIsTime = string.Equals(xAxisField.Name, "RecordedTime", StringComparison.OrdinalIgnoreCase);
Axis xAxis = xIsTime
? new DateTimeAxis
{
Position = AxisPosition.Bottom,
Title = xAxisField.Display,
StringFormat = "MM-dd\nHH:mm"
}
: new LinearAxis
{
Position = AxisPosition.Bottom,
Title = xAxisField.Display
};
var yAxis = new LinearAxis
{
Position = AxisPosition.Left,
@ -357,50 +432,110 @@ namespace SmartAquaViewer.ViewModel
Model.Axes.Add(xAxis);
Model.Axes.Add(yAxis);
var scatterSeries = new ScatterSeries
var allTankIds = collection.SelectMany(w => w.Tanks)
.Select(t => t.Number)
.Distinct()
.OrderBy(id => id)
.ToList();
var tankIds = (selectedTankNums == null || !selectedTankNums.Any())
? allTankIds
: allTankIds.Where(id => selectedTankNums.Contains(id)).ToList();
if (tankIds.Count == 0) { Model.InvalidatePlot(true); return; }
// 색상/마커
var colors = OxyPalettes.HueDistinct(tankIds.Count).Colors;
var markerCycle = new[]
{
MarkerType = MarkerType.Circle,
MarkerSize = markerSize,
MarkerStroke = OxyColors.Black,
MarkerFill = OxyColors.DeepSkyBlue
MarkerType.Circle, MarkerType.Square, MarkerType.Triangle, MarkerType.Diamond,
MarkerType.Plus, MarkerType.Star, MarkerType.Cross
};
//foreach (var row in rows)
//{
// double x = ResolveTank(row, xAxisField.Name) ?? double.NaN;
// double y = ResolveTank(row, yAxisField.Name) ?? double.NaN;
// scatterSeries.Points.Add(new ScatterPoint(x, y));
//}
int k = 0;
foreach (var tankId in tankIds)
{
var series = new ScatterSeries
{
Title = $"Tank {tankId}",
MarkerType = markerCycle[k % markerCycle.Length],
MarkerSize = markerSize,
// 색상 자동 배정에 맡기려면 MarkerFill 설정 생략해도 OK
MarkerFill = OxyColor.FromAColor(160, colors[k]), // 약간 투명
};
Model.Series.Add(scatterSeries);
// 포인트 수집 (시간순 정렬 권장)
var points = new List<ScatterPoint>();
foreach (var w in collection.OrderBy(r => r.RecordedTime))
{
foreach (var t in w.Tanks)
{
if (t.Number != tankId) continue;
//if (showRegression && rows.Count > 1)
//{
// var points = rows.Select(r
// => new DataPoint(
// ResolveTank(r, xAxisField.Name) ?? double.NaN,
// ResolveTank(r, yAxisField.Name) ?? double.NaN))
// .ToList();
// var regression = LinearRegression(points);
// if (regression != null)
// {
// var lineSeries = new LineSeries
// {
// Title = "Regression",
// Color = OxyColors.Red,
// StrokeThickness = 2
// };
// // 최소/최대 구간으로 선 그리기
// double minX = points.Min(p => p.X);
// double maxX = points.Max(p => p.X);
// lineSeries.Points.Add(new DataPoint(minX, regression.Value.Intercept + regression.Value.Slope * minX));
// lineSeries.Points.Add(new DataPoint(maxX, regression.Value.Intercept + regression.Value.Slope * maxX));
// Model.Series.Add(lineSeries);
// }
//}
// X
double? xVal = xIsTime
? DateTimeAxis.ToDouble(w.RecordedTime)
: ResolveTank(t, xAxisField.Name!); // 수조 객체의 선택필드(리플렉션)
// Y
double? yVal = ResolveTank(t, yAxisField.Name!);
if (xVal.HasValue && yVal.HasValue)
points.Add(new ScatterPoint(xVal.Value, yVal.Value));
}
}
// 포인트 반영
if (points.Count > 0)
{
series.Points.AddRange(points);
series.TrackerFormatString =
$"수조: {tankId}\nX: {{2:0.###}}\nY: {{4:0.###}}";
Model.Series.Add(series);
// 옵션: 수조별 선형회귀선
if (showRegression && !xIsTime && points.Count >= 2)
{
var (a, b) = FitLinear(points); // y = a*x + b
double minX = points.Min(p => p.X);
double maxX = points.Max(p => p.X);
var reg = new LineSeries
{
Title = $"Tank {tankId} 회귀",
StrokeThickness = 2,
Color = colors[k]
};
reg.Points.Add(new DataPoint(minX, a * minX + b));
reg.Points.Add(new DataPoint(maxX, a * maxX + b));
Model.Series.Add(reg);
}
}
k++;
}
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.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);
}
@ -410,7 +545,7 @@ namespace SmartAquaViewer.ViewModel
MonitorTab selectedTab,
FieldItem xAxisField,
ObservableCollection<FieldItem> yAxisFields,
bool showMarker = false
bool showMarker = false, bool showLegends = true
)
{
Model.Series.Clear();
@ -420,7 +555,8 @@ namespace SmartAquaViewer.ViewModel
{
Position = AxisPosition.Bottom,
Title = "시간",
GapWidth = 0.2
GapWidth = 0.2,
Angle = 45
};
foreach (var r in rows)
@ -462,38 +598,102 @@ namespace SmartAquaViewer.ViewModel
Model.InvalidatePlot(true);
}
public void SetStatusSeriesStopPlot(List<WaterQualityVO> collection,
FieldItem yAxisField, bool showMarker = false)
public void SetStatusSeriesStopPlot(
List<WaterQualityVO> collection,
FieldItem yAxisField,
ObservableCollection<FieldItem> yAxisFields,
bool showMarker = false, bool showLegends = true)
{
Model.Series.Clear();
Model.Axes.Clear();
var series = new StairStepSeries
var xAxis = new CategoryAxis
{
MarkerType = showMarker ? MarkerType.Circle : MarkerType.None,
Position = AxisPosition.Bottom,
Title = "시간",
GapWidth = 0.2
};
int i = 0;
foreach (var r in collection.OrderBy(r => r.RecordedTime))
foreach (var r in collection)
{
xAxis.Labels.Add(r.RecordedTime.ToString("HH:mm:ss"));
}
Model.Axes.Add(xAxis);
Model.Axes.Add(new LinearAxis
{
string? rawValue = ResolveStatus(r, yAxisField.Name);
if (rawValue != null)
Position = AxisPosition.Left,
Title = "값"
});
//var series = new StairStepSeries
//{
// MarkerType = showMarker ? MarkerType.Circle : MarkerType.None,
//};
//int i = 0;
//foreach (var r in collection.OrderBy(r => r.RecordedTime))
//{
// string? rawValue = ResolveStatus(r, yAxisField.Name);
// if (rawValue != null)
// {
// double y = MapDeviceStatus(rawValue);
// series.Points.Add(new DataPoint(i, y));
// }
// else
// {
// double? uvPower = ResolveUvPowerPerId(r, yAxisField.Name);
// if (uvPower.HasValue)
// {
// series.Points.Add(new DataPoint(i, uvPower.Value));
// }
// }
// i++;
//}
foreach (var field in yAxisFields)
{
var series = new StairStepSeries
{
double y = MapDeviceStatus(rawValue);
series.Points.Add(new DataPoint(i, y));
}
else
Title = field.Display,
MarkerType = showMarker ? MarkerType.Circle : MarkerType.None,
MarkerSize = 3
};
int i = 0;
foreach (var r in collection.OrderBy(r => r.RecordedTime))
{
double? uvPower = ResolveUvPowerPerId(r, yAxisField.Name);
if (uvPower.HasValue)
string? rawValue = ResolveStatus(r, field.Name!);
if (rawValue != null)
{
series.Points.Add(new DataPoint(i, uvPower.Value));
double y = MapDeviceStatus(rawValue);
series.Points.Add(new DataPoint(i, y));
}
else
{
double? uvPower = ResolveUvPowerPerId(r, field.Name!);
if (uvPower.HasValue)
{
series.Points.Add(new DataPoint(i, uvPower.Value));
}
}
}
i++;
}
Model.Series.Add(series);
}
Model.Series.Add(series);
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);
}
@ -843,6 +1043,18 @@ namespace SmartAquaViewer.ViewModel
return (slope, intercept);
}
private (double a, double b) FitLinear(IEnumerable<ScatterPoint> pts)
{
int n = 0;
double sumX = 0, sumY = 0, sumXX = 0, sumXY = 0;
foreach (var p in pts) { n++; sumX += p.X; sumY += p.Y; sumXX += p.X * p.X; sumXY += p.X * p.Y; }
double denom = n * sumXX - sumX * sumX;
if (n < 2 || Math.Abs(denom) < 1e-12) return (0, pts.First().Y); // 수직/특이 케이스
double a = (n * sumXY - sumX * sumY) / denom;
double b = (sumY - a * sumX) / n;
return (a, b);
}
private double MapDeviceStatus(string status)
{
return status switch

@ -325,11 +325,7 @@ namespace SmartAquaViewer.ViewModel
SetGraphData_Box_Tank();
break;
case GraphType.SCATTER:
var xFieldScatter = SelectedXField;
var yFiledScatter = SelectedYField;
var markerSIze = ScatterMarkerSize;
var showRegression = ShowRegression;
GraphControlVM.SetScatterPlot(WaterQualityList.ToList(), xFieldScatter, yFiledScatter, markerSIze, showRegression);
SetGraphData_Scatter_Tank();
break;
case GraphType.STEP:
var xFieldStep = SelectedXField?.Name == "RecordedTime" ? SelectedXField : null;
@ -337,9 +333,9 @@ namespace SmartAquaViewer.ViewModel
var yFiledStep = SelectedYField;
var showMarkerStep = ShowMarkers;
if (SelectedKind.Equals(StepFieldKind.Status))
GraphControlVM.SetStatusSeriesStopPlot(WaterQualityList.ToList(), yFiledStep, showMarkerStep);
GraphControlVM.SetStatusSeriesStopPlot(WaterQualityList.ToList(), SelectedYField, SelectedYFields, ShowMarkers, ShowLegends);
else
GraphControlVM.SetStepPlot(WaterQualityList.ToList(), SelectedTab, xFieldStep, tFieldsStep, showMarkerStep);
GraphControlVM.SetStepPlot(WaterQualityList.ToList(), SelectedTab, xFieldStep, SelectedYFields, ShowMarkers, ShowLegends);
break;
default:
break;
@ -363,7 +359,7 @@ namespace SmartAquaViewer.ViewModel
g.Select(x => x.VO).OrderBy(vo => vo.RecordedTime))
);
GraphControlVM.SetTankLineGraph(selectedTanks, SelectedXField, SelectedYField, ShowMarkers, ShowLegends);
GraphControlVM.SetTankLineGraph(WaterQualityList, keys, SelectedXField, SelectedYField, ShowMarkers, ShowLegends);
}
private void SetGraphData_Box_Tank()
@ -378,6 +374,16 @@ namespace SmartAquaViewer.ViewModel
GraphControlVM.SetBoxPlot(WaterQualityList, keys, SelectedXField, SelectedYField, BoxWidth, boxTimeSpan);
}
private void SetGraphData_Scatter_Tank()
{
if (SelectedTab != MonitorTab.Tank) return;
if (SelectedYField == null) return;
var keys = SelectedWaterTanks.Keys.ToList();
GraphControlVM.SetScatterPlot(WaterQualityList, SelectedXField, SelectedYField, keys, ScatterMarkerSize, ShowRegression, ShowLegends);
}
private void SetGraphType()
{
GraphTypes.Clear();
@ -491,6 +497,8 @@ namespace SmartAquaViewer.ViewModel
SelectedXField = AvailableFields.FirstOrDefault(f => f.DataType == typeof(DateTime))
?? AvailableFields.FirstOrDefault();
if (SelectedGraphType != GraphType.STEP) SelectedKind = StepFieldKind.Sensor;
IEnumerable<FieldItem> src = AvailableFields.Where(f => f.Kind == SelectedKind);
if (SelectedGraphType is GraphType.LINE or GraphType.SCATTER or GraphType.BOX)
@ -520,8 +528,6 @@ namespace SmartAquaViewer.ViewModel
UseSmoothing = false;
break;
case GraphType.STEP:
var def = YFieldCandidates.FirstOrDefault();
if (def != null) SelectedYFields.Add(def);
ShowMarkers = false;
UseSmoothing = false;
break;

Loading…
Cancel
Save