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