* UILineChart: 支持数据包括Nan

This commit is contained in:
Sunny 2021-10-03 00:05:07 +08:00
parent 44ce5325a4
commit f11a898b84
9 changed files with 170 additions and 54 deletions

Binary file not shown.

Binary file not shown.

View File

@ -37,6 +37,7 @@
this.uiSymbolButton2 = new Sunny.UI.UISymbolButton();
this.timer1 = new System.Windows.Forms.Timer(this.components);
this.uiCheckBox1 = new Sunny.UI.UICheckBox();
this.uiSymbolButton3 = new Sunny.UI.UISymbolButton();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton3)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton2)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton1)).BeginInit();
@ -103,10 +104,12 @@
this.LineChart.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(244)))), ((int)(((byte)(244)))), ((int)(((byte)(244)))));
this.LineChart.Font = new System.Drawing.Font("微软雅黑", 12F);
this.LineChart.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(54)))), ((int)(((byte)(54)))), ((int)(((byte)(54)))));
this.LineChart.LegendFont = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.LineChart.Location = new System.Drawing.Point(30, 55);
this.LineChart.MinimumSize = new System.Drawing.Size(1, 1);
this.LineChart.Name = "LineChart";
this.LineChart.Size = new System.Drawing.Size(670, 430);
this.LineChart.SubFont = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(134)));
this.LineChart.TabIndex = 35;
this.LineChart.Text = "uiLineChart1";
this.LineChart.PointValue += new Sunny.UI.UILineChart.OnPointValue(this.LineChart_PointValue);
@ -142,11 +145,27 @@
this.uiCheckBox1.Text = "显示点";
this.uiCheckBox1.CheckedChanged += new System.EventHandler(this.uiCheckBox1_CheckedChanged);
//
// uiSymbolButton3
//
this.uiSymbolButton3.Cursor = System.Windows.Forms.Cursors.Hand;
this.uiSymbolButton3.Font = new System.Drawing.Font("微软雅黑", 12F);
this.uiSymbolButton3.Location = new System.Drawing.Point(665, 505);
this.uiSymbolButton3.MinimumSize = new System.Drawing.Size(1, 1);
this.uiSymbolButton3.Name = "uiSymbolButton3";
this.uiSymbolButton3.Padding = new System.Windows.Forms.Padding(28, 0, 0, 0);
this.uiSymbolButton3.Size = new System.Drawing.Size(100, 27);
this.uiSymbolButton3.Symbol = 61952;
this.uiSymbolButton3.TabIndex = 38;
this.uiSymbolButton3.Text = "数据";
this.uiSymbolButton3.Visible = false;
this.uiSymbolButton3.Click += new System.EventHandler(this.uiSymbolButton3_Click);
//
// FLineChart
//
this.AllowShowTitle = true;
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.ClientSize = new System.Drawing.Size(800, 574);
this.Controls.Add(this.uiSymbolButton3);
this.Controls.Add(this.uiCheckBox1);
this.Controls.Add(this.uiSymbolButton2);
this.Controls.Add(this.uiSymbolButton1);
@ -176,5 +195,6 @@
private UISymbolButton uiSymbolButton2;
private System.Windows.Forms.Timer timer1;
private UICheckBox uiCheckBox1;
private UISymbolButton uiSymbolButton3;
}
}

View File

@ -144,5 +144,38 @@ namespace Sunny.UI.Demo
LineChart.Refresh();
}
private void uiSymbolButton3_Click(object sender, EventArgs e)
{
timer1.Stop();
UILineOption option = new UILineOption();
option.ToolTip.Visible = true;
option.Title = new UITitle();
option.Title.Text = "SunnyUI";
option.Title.SubText = "LineChart";
var series = option.AddSeries(new UILineSeries("Line1"));
series.Add(0, 1.2);
series.Add(1, 2.2);
series.Add(2, double.NaN);
series.Add(3, 4.2);
series.Add(4, 3.2);
series.Add(5, 2.2);
series.Symbol = UILinePointSymbol.Square;
series.SymbolSize = 4;
series.SymbolLineWidth = 2;
series.SymbolColor = Color.Red;
series.ContainsNan = true;
option.XAxis.Name = "日期";
option.YAxis.Name = "数值";
option.XAxis.AxisLabel.DateTimeFormat = "yyyy-MM-dd HH:mm";
option.XAxis.AxisLabel.AutoFormat = false;
option.YAxis.AxisLabel.DecimalCount = 1;
option.YAxis.AxisLabel.AutoFormat = false;
LineChart.SetOption(option);
}
}
}

View File

@ -48,6 +48,7 @@ namespace Sunny.UI
protected Point DrawOrigin;
protected Size DrawSize;
private Rectangle DrawRect;
protected override void CalcData()
{
@ -57,6 +58,8 @@ namespace Sunny.UI
DrawOrigin = new Point(Option.Grid.Left, Height - Option.Grid.Bottom);
DrawSize = new Size(Width - Option.Grid.Left - Option.Grid.Right,
Height - Option.Grid.Top - Option.Grid.Bottom);
DrawRect = new Rectangle(Option.Grid.Left, Option.Grid.Top, DrawSize.Width, DrawSize.Height);
if (DrawSize.Width <= 0 || DrawSize.Height <= 0) return;
CalcAxises();
@ -307,15 +310,32 @@ namespace Sunny.UI
return;
}
if (series.Points.Count == 2)
{
g.DrawTwoPoints(color, series.Points[0], series.Points[1], DrawRect);
return;
}
if (series.ShowLine || series.Symbol == UILinePointSymbol.None)
{
using (Pen pen = new Pen(color, series.Width))
{
g.SetHighQuality();
if (series.ContainsNan)
{
for (int i = 0; i < series.Points.Count - 1; i++)
{
g.DrawTwoPoints(pen, series.Points[i], series.Points[i + 1], DrawRect);
}
}
else
{
if (series.Smooth)
g.DrawCurve(pen, series.Points.ToArray());
else
g.DrawLines(pen, series.Points.ToArray());
}
g.SetDefaultQuality();
}
}
@ -444,6 +464,7 @@ namespace Sunny.UI
{
if (p.X <= Option.Grid.Left || p.X >= Width - Option.Grid.Right) continue;
if (p.Y <= Option.Grid.Top || p.Y >= Height - Option.Grid.Bottom) continue;
if (double.IsNaN(p.X) || double.IsNaN(p.Y)) continue;
switch (series.Symbol)
{

View File

@ -206,6 +206,17 @@ namespace Sunny.UI
foreach (var series in Series.Values)
{
if (series.DataCount > 0)
{
if (series.ContainsNan)
{
foreach (var d in series.YData)
{
if (d.IsNan() || d.IsInfinity()) continue;
min = Math.Min(min, d);
max = Math.Max(max, d);
}
}
else
{
min = Math.Min(min, series.YData.Min());
max = Math.Max(max, series.YData.Max());
@ -213,6 +224,7 @@ namespace Sunny.UI
}
}
}
}
public void GetAllDataXRange(out double min, out double max)
{
@ -225,9 +237,21 @@ namespace Sunny.UI
{
min = double.MaxValue;
max = double.MinValue;
foreach (var series in Series.Values)
{
if (series.DataCount > 0)
{
if (series.ContainsNan)
{
foreach (var d in series.XData)
{
if (d.IsNan() || d.IsInfinity()) continue;
min = Math.Min(min, d);
max = Math.Max(max, d);
}
}
else
{
min = Math.Min(min, series.XData.Min());
max = Math.Max(max, series.XData.Max());
@ -236,6 +260,7 @@ namespace Sunny.UI
}
}
}
}
public class UILineToolTip : UIChartToolTip
{
@ -263,6 +288,8 @@ namespace Sunny.UI
public bool ShowLine { get; set; } = true;
public bool ContainsNan { get; set; }
public UILineSeries(string name)
{
Name = name;
@ -359,6 +386,7 @@ namespace Sunny.UI
public void Add(double x, double y)
{
XData.Add(x);
if (double.IsInfinity(y)) y = double.NaN;
YData.Add(y);
}
@ -366,6 +394,7 @@ namespace Sunny.UI
{
DateTimeInt64 t = new DateTimeInt64(x);
XData.Add(t);
if (double.IsInfinity(y)) y = double.NaN;
YData.Add(y);
}
@ -373,6 +402,7 @@ namespace Sunny.UI
{
int cnt = XData.Count;
XData.Add(cnt);
if (double.IsInfinity(y)) y = double.NaN;
YData.Add(y);
}
}

View File

@ -31,13 +31,9 @@ namespace Sunny.UI
protected bool _minAuto = true;
protected bool _maxAuto = true;
protected double _minGrace = 0.1;
protected double _maxGrace = 0.1;
protected double _min, _max;
protected bool _formatAuto = true;
protected string _format;
protected bool _magAuto = true;
protected int _mag;
protected static double TargetSteps = 7.0;
@ -76,6 +72,9 @@ namespace Sunny.UI
float[] result = new float[labels.Length];
for (int i = 0; i < labels.Length; i++)
{
if (labels[i].IsInfinity() || labels[i].IsNan())
result[i] = float.NaN;
else
result[i] = CalcXPixel(labels[i], origin, width);
}
@ -88,6 +87,9 @@ namespace Sunny.UI
float[] result = new float[labels.Length];
for (int i = 0; i < labels.Length; i++)
{
if (labels[i].IsInfinity() || labels[i].IsNan())
result[i] = float.NaN;
else
result[i] = CalcYPixel(labels[i], origin, height);
}
@ -96,38 +98,22 @@ namespace Sunny.UI
public int Mag
{
get { return _mag; }
set { _mag = value; _magAuto = false; }
get => _mag;
set { _mag = value; MagAuto = false; }
}
public bool MagAuto
{
get { return _magAuto; }
set { _magAuto = value; }
}
public bool MagAuto { get; set; } = true;
public double MinGrace
{
get { return _minGrace; }
set { _minGrace = value; }
}
public double MinGrace { get; set; } = 0.1;
public double MaxGrace
{
get { return _maxGrace; }
set { _maxGrace = value; }
}
public double MaxGrace { get; set; } = 0.1;
public bool FormatAuto
{
get { return _formatAuto; }
set { _formatAuto = value; }
}
public bool FormatAuto { get; set; } = true;
public string Format
{
get { return _format; }
set { _format = value; _formatAuto = false; }
get => _format;
set { _format = value; FormatAuto = false; }
}
public virtual double Min
@ -171,14 +157,14 @@ namespace Sunny.UI
if (_minAuto)
{
_min = minVal;
if (_min < 0 || minVal - _minGrace * range >= 0.0)
_min = minVal - _minGrace * range;
if (_min < 0 || minVal - MinGrace * range >= 0.0)
_min = minVal - MinGrace * range;
}
if (_maxAuto)
{
_max = maxVal;
if (_max > 0 || maxVal + _maxGrace * range <= 0.0)
_max = maxVal + _maxGrace * range;
if (_max > 0 || maxVal + MaxGrace * range <= 0.0)
_max = maxVal + MaxGrace * range;
}
if (_max.Equals(_min) && _maxAuto && _minAuto)
@ -247,7 +233,7 @@ namespace Sunny.UI
private void SetScaleMag()
{
if (_magAuto)
if (MagAuto)
{
double minMag = Math.Floor(Math.Log10(Math.Abs(_min)));
double maxMag = Math.Floor(Math.Log10(Math.Abs(_max)));
@ -256,7 +242,7 @@ namespace Sunny.UI
_mag = (int)(Math.Floor(mag / 3.0) * 3.0);
}
if (_formatAuto)
if (FormatAuto)
{
int numDec = 0 - (int)(Math.Floor(Math.Log10(Step)) - _mag);
if (numDec < 0) numDec = 0;
@ -433,37 +419,37 @@ namespace Sunny.UI
if (span.TotalDays > 1825) // 5 years
{
_scaleLevel = UIDateScaleLevel.Year;
if (_formatAuto) _format = "yyyy";
if (FormatAuto) _format = "yyyy";
tempStep = Math.Ceiling(tempStep / 365.0);
}
else if (span.TotalDays > 730) // 2 years
{
_scaleLevel = UIDateScaleLevel.Year;
if (_formatAuto) _format = "yyyy-MM";
if (FormatAuto) _format = "yyyy-MM";
tempStep = Math.Ceiling(tempStep / 365.0);
}
else if (span.TotalDays > 300) // 10 months
{
_scaleLevel = UIDateScaleLevel.Month;
if (_formatAuto) _format = "yyyy-MM";
if (FormatAuto) _format = "yyyy-MM";
tempStep = Math.Ceiling(tempStep / 30.0);
}
else if (span.TotalDays > 10) // 10 days
{
_scaleLevel = UIDateScaleLevel.Day;
if (_formatAuto) _format = "yyyy-MM-dd";
if (FormatAuto) _format = "yyyy-MM-dd";
tempStep = Math.Ceiling(tempStep);
}
else if (span.TotalDays > 3) // 3 days
{
_scaleLevel = UIDateScaleLevel.Day;
if (_formatAuto) _format = "yyyy-MM-dd HH:mm";
if (FormatAuto) _format = "yyyy-MM-dd HH:mm";
tempStep = Math.Ceiling(tempStep);
}
else if (span.TotalHours > 10) // 10 hours
{
_scaleLevel = UIDateScaleLevel.Hour;
if (_formatAuto) _format = "HH:mm";
if (FormatAuto) _format = "HH:mm";
tempStep = Math.Ceiling(tempStep * 24.0);
if (tempStep > 12.0) tempStep = 24.0;
@ -475,13 +461,13 @@ namespace Sunny.UI
else if (span.TotalHours > 3) // 3 hours
{
_scaleLevel = UIDateScaleLevel.Hour;
if (_formatAuto) _format = "HH:mm";
if (FormatAuto) _format = "HH:mm";
tempStep = Math.Ceiling(tempStep * 24.0);
}
else if (span.TotalMinutes > 10) // 10 Minutes
{
_scaleLevel = UIDateScaleLevel.Minute;
if (_formatAuto) _format = "HH:mm";
if (FormatAuto) _format = "HH:mm";
tempStep = Math.Ceiling(tempStep * 1440.0);
// make sure the minute step size is 1, 5, 15, or 30 minutes
if (tempStep > 15.0) tempStep = 30.0;
@ -492,13 +478,13 @@ namespace Sunny.UI
else if (span.TotalMinutes > 3) // 3 Minutes
{
_scaleLevel = UIDateScaleLevel.Minute;
if (_formatAuto) _format = "mm:ss";
if (FormatAuto) _format = "mm:ss";
tempStep = Math.Ceiling(tempStep * 1440.0);
}
else if (span.TotalSeconds > 3) // 3 Seconds
{
_scaleLevel = UIDateScaleLevel.Second;
if (_formatAuto) _format = "mm:ss";
if (FormatAuto) _format = "mm:ss";
tempStep = Math.Ceiling(tempStep * 86400.0);
// make sure the second step size is 1, 5, 15, or 30 seconds
@ -510,7 +496,7 @@ namespace Sunny.UI
else
{
_scaleLevel = UIDateScaleLevel.Millisecond;
if (_formatAuto) _format = "ss.fff";
if (FormatAuto) _format = "ss.fff";
tempStep = CalcStepSize(span.TotalMilliseconds, targetSteps);
}

View File

@ -934,6 +934,7 @@ namespace Sunny.UI
public static void DrawTwoPoints(this Graphics g, Pen pen, PointF pf1, PointF pf2, Rectangle rect, bool smooth = true)
{
if (pf1.X.IsNan() || pf1.Y.IsNan() || pf2.X.IsNan() || pf2.Y.IsNan()) return;
bool haveLargePixel = Math.Abs(pf1.X - pf2.X) >= rect.Width * 100 || Math.Abs(pf1.Y - pf2.Y) >= rect.Height * 100;
//两点都在区域内

25
SunnyUI/Common/UOther.cs Normal file
View File

@ -0,0 +1,25 @@
namespace Sunny.UI
{
public static class UIOther
{
public static bool IsNan(this double d)
{
return double.IsNaN(d);
}
public static bool IsNan(this float d)
{
return float.IsNaN(d);
}
public static bool IsInfinity(this double d)
{
return double.IsInfinity(d);
}
public static bool IsInfinity(this float d)
{
return float.IsInfinity(d);
}
}
}