* 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.uiSymbolButton2 = new Sunny.UI.UISymbolButton();
this.timer1 = new System.Windows.Forms.Timer(this.components); this.timer1 = new System.Windows.Forms.Timer(this.components);
this.uiCheckBox1 = new Sunny.UI.UICheckBox(); this.uiCheckBox1 = new Sunny.UI.UICheckBox();
this.uiSymbolButton3 = new Sunny.UI.UISymbolButton();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton3)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.uiImageButton3)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton2)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.uiImageButton2)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.uiImageButton1)).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.FillColor = System.Drawing.Color.FromArgb(((int)(((byte)(244)))), ((int)(((byte)(244)))), ((int)(((byte)(244)))));
this.LineChart.Font = new System.Drawing.Font("微软雅黑", 12F); 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.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.Location = new System.Drawing.Point(30, 55);
this.LineChart.MinimumSize = new System.Drawing.Size(1, 1); this.LineChart.MinimumSize = new System.Drawing.Size(1, 1);
this.LineChart.Name = "LineChart"; this.LineChart.Name = "LineChart";
this.LineChart.Size = new System.Drawing.Size(670, 430); 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.TabIndex = 35;
this.LineChart.Text = "uiLineChart1"; this.LineChart.Text = "uiLineChart1";
this.LineChart.PointValue += new Sunny.UI.UILineChart.OnPointValue(this.LineChart_PointValue); this.LineChart.PointValue += new Sunny.UI.UILineChart.OnPointValue(this.LineChart_PointValue);
@ -142,11 +145,27 @@
this.uiCheckBox1.Text = "显示点"; this.uiCheckBox1.Text = "显示点";
this.uiCheckBox1.CheckedChanged += new System.EventHandler(this.uiCheckBox1_CheckedChanged); 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 // FLineChart
// //
this.AllowShowTitle = true; this.AllowShowTitle = true;
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None;
this.ClientSize = new System.Drawing.Size(800, 574); this.ClientSize = new System.Drawing.Size(800, 574);
this.Controls.Add(this.uiSymbolButton3);
this.Controls.Add(this.uiCheckBox1); this.Controls.Add(this.uiCheckBox1);
this.Controls.Add(this.uiSymbolButton2); this.Controls.Add(this.uiSymbolButton2);
this.Controls.Add(this.uiSymbolButton1); this.Controls.Add(this.uiSymbolButton1);
@ -176,5 +195,6 @@
private UISymbolButton uiSymbolButton2; private UISymbolButton uiSymbolButton2;
private System.Windows.Forms.Timer timer1; private System.Windows.Forms.Timer timer1;
private UICheckBox uiCheckBox1; private UICheckBox uiCheckBox1;
private UISymbolButton uiSymbolButton3;
} }
} }

View File

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

View File

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

View File

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