using DevExpress.CodeParser;
|
using DevExpress.Utils;
|
using DevExpress.XtraCharts;
|
using System.Linq;
|
using Yw.WinFrmUI.Phart;
|
|
namespace PBS.WinFrmUI.Hydro
|
{
|
public partial class SystemCurveChartCtrl : DevExpress.XtraEditors.XtraUserControl
|
{
|
public SystemCurveChartCtrl()
|
{
|
InitializeComponent();
|
this.layoutControl1.SetupLayoutControl();
|
this.gridView1.SetNormalView();
|
this.sidePanel1.Visible = false;
|
InitialChart();
|
}
|
|
public void InitialChart()
|
{
|
this.chartControl1.Legend.Direction = LegendDirection.LeftToRight;
|
this.chartControl1.RuntimeHitTesting = true;
|
|
_xyDiagram = this.chartControl1.Diagram as XYDiagram;
|
_xyDiagramPane = _xyDiagram.DefaultPane;
|
|
_seriesWaterPoint = this.chartControl1.GetSeriesByName("SeriesWaterPoint");
|
_seriesUpperPressure = this.chartControl1.GetSeriesByName("SeriesUpperPressure");
|
_seriesLowerPressure = this.chartControl1.GetSeriesByName("SeriesLowerPressure");
|
_seriesAveragePressure = this.chartControl1.GetSeriesByName("SeriesAveragePressure");
|
|
_constantLineDynamicPressure = _xyDiagram.AxisY.ConstantLines.GetElementByName("ConstantLineDynamicPressure") as ConstantLine;
|
_constantLineStaticPressure = _xyDiagram.AxisY.ConstantLines.GetElementByName("ConstantLineStaticPressure") as ConstantLine;
|
|
_seriesWaterPoint.LegendTextPattern = "用水点";
|
_seriesUpperPressure.LegendTextPattern = "压力-上限";
|
_seriesLowerPressure.LegendTextPattern = "压力-下限";
|
_seriesAveragePressure.LegendTextPattern = "压力-平均";
|
|
_seriesWaterPoint.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.False;
|
_seriesUpperPressure.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.True;
|
_seriesLowerPressure.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.True;
|
_seriesAveragePressure.CrosshairEnabled = DevExpress.Utils.DefaultBoolean.True;
|
|
this.chartControl1.CrosshairOptions.ShowOnlyInFocusedPane = false;
|
this.chartControl1.CrosshairOptions.GroupHeaderPattern = "{A:N2}";
|
this.chartControl1.CrosshairOptions.GroupHeaderTextOptions.EnableAntialiasing = DevExpress.Utils.DefaultBoolean.True;
|
_seriesUpperPressure.CrosshairLabelPattern = "{V:N2}";
|
_seriesLowerPressure.CrosshairLabelPattern = "{V:N2}";
|
_seriesAveragePressure.CrosshairLabelPattern = "{V:N2}";
|
|
_constantLineDynamicPressure.ShowInLegend = false;
|
_constantLineStaticPressure.ShowInLegend = false;
|
|
_constantLineDynamicPressure.Title.Alignment = ConstantLineTitleAlignment.Far;
|
_constantLineStaticPressure.Title.Alignment = ConstantLineTitleAlignment.Far;
|
|
_constantLineDynamicPressure.LineStyle.Thickness = 2;
|
_constantLineStaticPressure.LineStyle.Thickness = 2;
|
|
_xyDiagram.EnableAxisXZooming = true;
|
_xyDiagram.EnableAxisYZooming = true;
|
_xyDiagram.EnableAxisXScrolling = true;
|
_xyDiagram.EnableAxisYScrolling = true;
|
|
}
|
|
private XYDiagram _xyDiagram;
|
private XYDiagramDefaultPane _xyDiagramPane;
|
|
private Series _seriesWaterPoint;
|
private Series _seriesUpperPressure;
|
private Series _seriesLowerPressure;
|
private Series _seriesAveragePressure;
|
|
private ConstantLine _constantLineDynamicPressure;
|
private ConstantLine _constantLineStaticPressure;
|
|
|
/// <summary>
|
/// 刷新数据事件
|
/// </summary>
|
public event Action RefreshDataEvent;
|
|
|
public event Action<Yw.Ahart.CubicCurve, Yw.Ahart.CubicCurve, Yw.Ahart.CubicCurve> RefreshCurveEvent;
|
|
/// <summary>
|
/// 清空
|
/// </summary>
|
public void Clear()
|
{
|
|
this.chartControl1.BeginInit();
|
_seriesWaterPoint.Points.Clear();
|
_seriesUpperPressure.Points.Clear();
|
_seriesLowerPressure.Points.Clear();
|
_seriesAveragePressure.Points.Clear();
|
|
this.chartControl1.CrosshairEnabled = DefaultBoolean.False;
|
this.chartControl1.CrosshairOptions.ShowArgumentLine = false;
|
|
_constantLineDynamicPressure.Visible = false;
|
_constantLineStaticPressure.Visible = false;
|
_xyDiagram.AxisX.Visibility = DefaultBoolean.False;
|
_xyDiagram.AxisX.GridLines.Visible = false;
|
|
_xyDiagram.AxisY.Visibility = DefaultBoolean.False;
|
_xyDiagram.AxisY.GridLines.Visible = false;
|
|
|
_xyDiagram.EnableAxisXZooming = false;
|
_xyDiagram.EnableAxisYZooming = false;
|
_xyDiagram.EnableAxisXScrolling = false;
|
_xyDiagram.EnableAxisYScrolling = false;
|
|
this.chartControl1.EndInit();
|
|
this.RefreshCurveEvent?.Invoke(null, null, null);
|
}
|
|
/// <summary>
|
/// 绑定数据
|
/// </summary>
|
public void SetBindingData(List<WaterPointCalcViewModel> waterPointCalcList)
|
{
|
if (waterPointCalcList == null || !waterPointCalcList.Any())
|
{
|
Clear();
|
return;
|
}
|
|
|
this.chartControl1.BeginInit();
|
|
_xyDiagram.EnableAxisXZooming = true;
|
_xyDiagram.EnableAxisYZooming = true;
|
_xyDiagram.EnableAxisXScrolling = true;
|
_xyDiagram.EnableAxisYScrolling = true;
|
|
this.chartControl1.CrosshairOptions.ShowArgumentLine = true;
|
|
_seriesWaterPoint.Points.Clear();
|
_seriesUpperPressure.Points.Clear();
|
_seriesLowerPressure.Points.Clear();
|
_seriesAveragePressure.Points.Clear();
|
|
_constantLineDynamicPressure.Visible = false;
|
_constantLineStaticPressure.Visible = false;
|
|
|
var upperPressurePtList = new List<Yw.Geometry.Point2d>();
|
var lowerPressurePtList = new List<Yw.Geometry.Point2d>();
|
var averagePressurePtList = new List<Yw.Geometry.Point2d>();
|
|
var waterPointGroup = waterPointCalcList.GroupBy(x => Math.Round(x.TotalFlow, 1));
|
foreach (var item in waterPointGroup)
|
{
|
var x = item.Key;
|
var list = item.Select(t => new Yw.Geometry.Point2d(x, t.EndPressure)).ToList();
|
|
//var yList = item.Select(x => x.EndPressure).OrderBy(x => x).ToList();
|
|
//var yUpper = CalculatePercentile(yList, 0.95);
|
//var yLower = CalculatePercentile(yList, 0.05);
|
//var yNumList = yList.Where(x => x >= yLower && x <= yUpper);
|
//if (yNumList == null || !yNumList.Any())
|
//{
|
// continue;
|
//}
|
//var yAverage = yNumList.Where(x => x >= yLower && x <= yUpper).Average();
|
|
//upperPressurePtList.Add(new Yw.Geometry.Point2d(x, yUpper));
|
//lowerPressurePtList.Add(new Yw.Geometry.Point2d(x, yLower));
|
//averagePressurePtList.Add(new Yw.Geometry.Point2d(x, yAverage));
|
|
var fList = DynamicThresholdProcessor.FilterWithDynamicSigma(list);
|
// var fList = FilterByStdDev(list);
|
if (fList == null || !fList.Any())
|
{
|
continue;
|
}
|
|
var yUpper = fList.Max(x => x.Y);
|
var yLower = fList.Min(x => x.Y);
|
var yAverage = fList.Average(x => x.Y);
|
|
upperPressurePtList.Add(new Yw.Geometry.Point2d(x, yUpper));
|
lowerPressurePtList.Add(new Yw.Geometry.Point2d(x, yLower));
|
averagePressurePtList.Add(new Yw.Geometry.Point2d(x, yAverage));
|
|
|
}
|
|
if (averagePressurePtList.Count > 4)
|
{
|
var curveUpperPressure = new Yw.Ahart.CubicCurve(upperPressurePtList);
|
var curveLowerPressure = new Yw.Ahart.CubicCurve(lowerPressurePtList);
|
var curveAveragePressure = new Yw.Ahart.CubicCurve(averagePressurePtList);
|
|
var upperPressureCurvePtList = curveUpperPressure.GetPointList(100);
|
var lowerPressureCurvePtList = curveLowerPressure.GetPointList(100);
|
var averagePressureCurvePtList = curveAveragePressure.GetPointList(100);
|
|
|
foreach (var item in waterPointCalcList)
|
{
|
_seriesWaterPoint.Points.Add(new SeriesPoint(item.TotalFlow, item.EndPressure));
|
}
|
foreach (var item in upperPressureCurvePtList)
|
{
|
_seriesUpperPressure.Points.Add(new SeriesPoint(item.X, item.Y));
|
}
|
foreach (var item in lowerPressureCurvePtList)
|
{
|
_seriesLowerPressure.Points.Add(new SeriesPoint(item.X, item.Y));
|
}
|
foreach (var item in averagePressureCurvePtList)
|
{
|
_seriesAveragePressure.Points.Add(new SeriesPoint(item.X, item.Y));
|
}
|
|
|
|
this.RefreshCurveEvent?.Invoke(curveUpperPressure, curveLowerPressure, curveAveragePressure);
|
}
|
|
|
var minX = waterPointCalcList.Min(x => x.TotalFlow);
|
var maxX = waterPointCalcList.Max(x => x.TotalFlow);
|
var minY = waterPointCalcList.Min(x => x.EndPressure);
|
var maxY = waterPointCalcList.Max(x => x.EndPressure);
|
|
SetChartAxis(minX, maxX, minY, maxY);
|
this.chartControl1.EndInit();
|
}
|
|
//计算百分位数
|
private double CalculatePercentile(List<double> sortedData, double percentile)
|
{
|
int n = sortedData.Count;
|
double index = (n - 1) * percentile;
|
int lower = (int)Math.Floor(index);
|
int upper = (int)Math.Ceiling(index);
|
double fraction = index - lower;
|
return sortedData[lower] * (1 - fraction) + sortedData[upper] * fraction;
|
}
|
|
|
public List<Yw.Geometry.Point2d> FilterByStdDev(List<Yw.Geometry.Point2d> group)
|
{
|
|
var filteredData = new List<Yw.Geometry.Point2d>();
|
|
|
// 小样本数据不处理(阈值设为10)
|
if (group.Count() < 3)
|
{
|
filteredData.AddRange(group);
|
return default;
|
}
|
|
// 计算统计量
|
var pressures = group.Select(p => p.Y).ToList();
|
double mean = pressures.Average();
|
double stdDev = CalculateSampleStdDev(pressures); // 样本标准差
|
|
// 设置3σ边界[1,3,4](@ref)
|
double lowerBound = mean - 3 * stdDev;
|
double upperBound = mean + 3 * stdDev;
|
|
// 过滤异常值
|
filteredData.AddRange(
|
group.Where(p => p.Y >= lowerBound && p.Y <= upperBound)
|
);
|
|
|
return filteredData;
|
}
|
|
// 样本标准差计算(n-1)[7,8](@ref)
|
private double CalculateSampleStdDev(List<double> values)
|
{
|
if (values.Count < 2) return 0;
|
|
double mean = values.Average();
|
double sumSquares = values.Sum(v => Math.Pow(v - mean, 2));
|
return Math.Sqrt(sumSquares / (values.Count - 1));
|
}
|
|
#region Set Axis
|
|
|
/// <summary>
|
/// 设置图表轴
|
/// </summary>
|
private void SetChartAxis(double minX, double maxX, double minY, double maxY)
|
{
|
var coordinate = Yw.WinFrmUI.Phart.UniversalCoordinate.CalcCoordinate(minX, maxX, minY, maxY);
|
if (coordinate == null)
|
{
|
_xyDiagram.AxisX.Visibility = DefaultBoolean.False;
|
_xyDiagram.AxisX.GridLines.Visible = false;
|
|
_xyDiagram.AxisY.Visibility = DefaultBoolean.False;
|
_xyDiagram.AxisY.GridLines.Visible = false;
|
return;
|
}
|
if (coordinate.CoordMinX + coordinate.CoordSpaceX * coordinate.GridNumberX < maxX * 1.05)
|
coordinate.GridNumberX++;
|
|
|
//计算刻度 X
|
var axis_x_labels = new List<CustomAxisLabel>();
|
var display_x = coordinate.CoordMinX;
|
for (int i = 0; i < coordinate.GridNumberX + 1; i++)
|
{
|
axis_x_labels.Add(new CustomAxisLabel(display_x.ToString("N0"), display_x));
|
display_x = display_x + coordinate.CoordSpaceX;
|
}
|
|
_xyDiagram.AxisX.CustomLabels.Clear();
|
_xyDiagram.AxisX.CustomLabels.AddRange(axis_x_labels.ToArray());
|
_xyDiagram.AxisX.Visibility = DefaultBoolean.True;
|
_xyDiagram.AxisX.GridLines.Visible = true;
|
|
|
//计算刻度 Y
|
var axis_y_labels = new List<CustomAxisLabel>();
|
var display_y = coordinate.CoordMinY + coordinate.CoordSpaceY * coordinate.StartLineNoY;
|
for (int i = coordinate.StartLineNoY; i < coordinate.EndLineNoY + 1; i++)
|
{
|
axis_y_labels.Add(new CustomAxisLabel(display_y.ToString(), display_y));
|
display_y = display_y + coordinate.CoordSpaceY;
|
}
|
|
_xyDiagram.AxisY.CustomLabels.Clear();
|
_xyDiagram.AxisY.CustomLabels.AddRange(axis_y_labels.ToArray());
|
_xyDiagram.AxisY.Visibility = DefaultBoolean.True;
|
_xyDiagram.AxisY.GridLines.Visible = true;
|
|
_xyDiagram.AxisY.SetAxisRange(coordinate.CoordMinX, coordinate.DispMaxX());
|
_xyDiagram.AxisY.SetAxisRange(coordinate.DispMinY(), coordinate.DispMaxY());
|
|
}
|
|
#endregion
|
|
}
|
}
|