diff --git a/Bin/SunnyUI.Demo.exe b/Bin/SunnyUI.Demo.exe index 0f468222..adee834f 100644 Binary files a/Bin/SunnyUI.Demo.exe and b/Bin/SunnyUI.Demo.exe differ diff --git a/Bin/SunnyUI.dll b/Bin/SunnyUI.dll index ec373534..8fec2023 100644 Binary files a/Bin/SunnyUI.dll and b/Bin/SunnyUI.dll differ diff --git a/Bin/SunnyUI.pdb b/Bin/SunnyUI.pdb deleted file mode 100644 index 7776b9f8..00000000 Binary files a/Bin/SunnyUI.pdb and /dev/null differ diff --git a/SunnyUI.Demo/Charts/FLineChart.cs b/SunnyUI.Demo/Charts/FLineChart.cs index 8f7ae9f7..d018a3a2 100644 --- a/SunnyUI.Demo/Charts/FLineChart.cs +++ b/SunnyUI.Demo/Charts/FLineChart.cs @@ -18,28 +18,28 @@ namespace Sunny.UI.Demo.Charts option.Title.Text = "SunnyUI"; option.Title.SubText = "LineChart"; - option.XAxisType = UIAxisType.Time; + option.XAxisType = UIAxisType.DateTime; var series = option.AddSeries(new UILineSeries("Line1")); DateTime dt = new DateTime(2020, 10, 4); series.Add(dt.AddHours(0), 1.2); - series.Add(dt.AddHours(0.1), 2.2); - series.Add(dt.AddHours(0.2), 3.2); - series.Add(dt.AddHours(0.3), 4.2); - series.Add(dt.AddHours(0.4), 3.2); - series.Add(dt.AddHours(0.5), 2.2); + series.Add(dt.AddHours(1), 2.2); + series.Add(dt.AddHours(2), 3.2); + series.Add(dt.AddHours(3), 4.2); + series.Add(dt.AddHours(4), 3.2); + series.Add(dt.AddHours(5), 2.2); series.Symbol = UILinePointSymbol.Square; series.SymbolSize = 4; series.SymbolLineWidth = 2; series.SymbolColor = Color.Red; series = option.AddSeries(new UILineSeries("Line2", Color.Lime)); - series.Add(dt.AddHours(0.3), 3.3); - series.Add(dt.AddHours(0.4), 2.3); - series.Add(dt.AddHours(0.5), 2.3); - series.Add(dt.AddHours(0.6), 1.3); - series.Add(dt.AddHours(0.7), 2.3); - series.Add(dt.AddHours(0.8), 4.3); + series.Add(dt.AddHours(3), 3.3); + series.Add(dt.AddHours(4), 2.3); + series.Add(dt.AddHours(5), 2.3); + series.Add(dt.AddHours(6), 1.3); + series.Add(dt.AddHours(7), 2.3); + series.Add(dt.AddHours(8), 4.3); series.Symbol = UILinePointSymbol.Star; series.SymbolSize = 4; series.SymbolLineWidth = 2; @@ -75,17 +75,17 @@ namespace Sunny.UI.Demo.Charts Console.WriteLine(sb.ToString()); } - private void uiImageButton1_Click(object sender, System.EventArgs e) + private void uiImageButton1_Click(object sender, EventArgs e) { LineChart.ChartStyleType = UIChartStyleType.Default; } - private void uiImageButton2_Click(object sender, System.EventArgs e) + private void uiImageButton2_Click(object sender, EventArgs e) { LineChart.ChartStyleType = UIChartStyleType.Plain; } - private void uiImageButton3_Click(object sender, System.EventArgs e) + private void uiImageButton3_Click(object sender, EventArgs e) { LineChart.ChartStyleType = UIChartStyleType.Dark; } diff --git a/SunnyUI/Charts/UIBarChart.cs b/SunnyUI/Charts/UIBarChart.cs index b7d833ef..01b942a6 100644 --- a/SunnyUI/Charts/UIBarChart.cs +++ b/SunnyUI/Charts/UIBarChart.cs @@ -42,6 +42,67 @@ namespace Sunny.UI CalcData(); } + /// + /// 计算刻度 + /// 起始值必须小于结束值 + /// + /// 起始值 + /// 结束值 + /// 期望刻度数量,实际数接近此数 + /// 刻度起始值,须乘以间隔使用 + /// 刻度结束值,须乘以间隔使用 + /// 刻度间隔 + public void CalcDegreeScale(double start, double end, int expect_num, + out int degree_start, out int degree_end, out double degree_gap) + { + if (start >= end) + { + throw new Exception("起始值必须小于结束值"); + } + + double differ = end - start; + double differ_gap = differ / (expect_num - 1); //35, 4.6, 0.27 + + double exponent = Math.Log10(differ_gap) - 1; //0.54, -0.34, -1.57 + int _exponent = (int)exponent; //0, 0=>-1, -1=>-2 + if (exponent < 0 && Math.Abs(exponent) > 1e-8) + { + _exponent--; + } + + int step = (int)(differ_gap / Math.Pow(10, _exponent)); //35, 46, 27 + int[] fix_steps = new int[] { 10, 20, 25, 50, 100 }; + int fix_step = 10; //25, 50, 25 + for (int i = fix_steps.Length - 1; i >= 1; i--) + { + if (step > (fix_steps[i] + fix_steps[i - 1]) / 2) + { + fix_step = fix_steps[i]; + break; + } + } + + degree_gap = fix_step * Math.Pow(10, _exponent); //25, 5, 0.25 + + double start1 = start / degree_gap; + int start2 = (int)start1; + if (start1 < 0 && Math.Abs(start1 - start2) > 1e-8) + { + start2--; + } + + degree_start = start2; + + double end1 = end / degree_gap; + int end2 = (int)end1; + if (end1 >= 0 && Math.Abs(end1 - end2) > 1e-8) + { + end2++; + } + + degree_end = end2; + } + protected override void CalcData() { Bars.Clear(); @@ -80,7 +141,7 @@ namespace Sunny.UI min = 0; } - UIChartHelper.CalcDegreeScale(min, max, BarOption.YAxis.SplitNumber, + CalcDegreeScale(min, max, BarOption.YAxis.SplitNumber, out int start, out int end, out double interval); YAxisStart = start; diff --git a/SunnyUI/Charts/UIBarChartEx.cs b/SunnyUI/Charts/UIBarChartEx.cs index 16480fe7..7f8b3c2d 100644 --- a/SunnyUI/Charts/UIBarChartEx.cs +++ b/SunnyUI/Charts/UIBarChartEx.cs @@ -73,7 +73,7 @@ namespace Sunny.UI min = 0; } - UIChartHelper.CalcDegreeScale(min, max, BarOption.YAxis.SplitNumber, + CalcDegreeScale(min, max, BarOption.YAxis.SplitNumber, out int start, out int end, out double interval); YAxisStart = start; diff --git a/SunnyUI/Charts/UIBarChartOption.cs b/SunnyUI/Charts/UIBarChartOption.cs index d22bb981..e918f47f 100644 --- a/SunnyUI/Charts/UIBarChartOption.cs +++ b/SunnyUI/Charts/UIBarChartOption.cs @@ -165,7 +165,7 @@ namespace Sunny.UI { case UIAxisType.Value: return Formatter != null ? Formatter?.Invoke(value, index) : value.ToString("F" + DecimalCount); - case UIAxisType.Time: + case UIAxisType.DateTime: DateTimeInt64 dt = new DateTimeInt64((long)value); return Formatter != null ? Formatter?.Invoke(dt, index) : (DateTimeFormat.IsNullOrEmpty() ? dt.ToString() : dt.ToString(DateTimeFormat)); case UIAxisType.Category: diff --git a/SunnyUI/Charts/UILineChart.cs b/SunnyUI/Charts/UILineChart.cs index 988f3b00..6ac46972 100644 --- a/SunnyUI/Charts/UILineChart.cs +++ b/SunnyUI/Charts/UILineChart.cs @@ -22,13 +22,6 @@ namespace Sunny.UI private Point DrawOrigin; private Size DrawSize; - private int YAxisStart; - private int YAxisEnd; - private double YAxisInterval; - private int XAxisStart; - private int XAxisEnd; - private double XAxisInterval; - protected override void CalcData() { NeedDraw = false; @@ -44,86 +37,48 @@ namespace Sunny.UI foreach (var series in LineOption.Series.Values) { series.ClearPoints(); - - for (int i = 0; i < series.XData.Count; i++) - { - float x = (float)((series.XData[i] - XAxisStart * XAxisInterval) * 1.0f * DrawSize.Width / XAxisInterval / (XAxisEnd - XAxisStart)); - float y = (float)((series.YData[i] - YAxisStart * YAxisInterval) * 1.0f * DrawSize.Height / YAxisInterval / (YAxisEnd - YAxisStart)); - series.AddPoint(new PointF(DrawOrigin.X + x, DrawOrigin.Y - y)); - } + float[] x = XScale.CalcXPixels(series.XData.ToArray(), DrawOrigin.X, DrawSize.Width); + float[] y = YScale.CalcYPixels(series.YData.ToArray(), DrawOrigin.Y, DrawSize.Height); + series.AddPoints(x, y); } NeedDraw = true; } + private UIScale XScale; + private UIScale YScale; + private double[] YLabels; + private double[] XLabels; + private void CalcAxises() { + if (LineOption.XAxisType == UIAxisType.DateTime) + XScale = new UIDateScale(); + else + XScale = new UILinearScale(); + + YScale = new UILinearScale(); + //Y轴 - double min = double.MaxValue; - double max = double.MinValue; - foreach (var series in LineOption.Series.Values) { - if (series.DataCount > 0) - { - min = Math.Min(min, series.YData.Min()); - max = Math.Max(max, series.YData.Max()); - } + LineOption.GetAllDataYRange(out double min, out double max); + if (min > 0 && max > 0 && !LineOption.YAxis.Scale) min = 0; + if (min < 0 && max < 0 && !LineOption.YAxis.Scale) max = 0; + YScale.SetRange(min, max); + YScale.AxisChange(); + if (!LineOption.YAxis.MaxAuto) YScale.Max = LineOption.YAxis.Max; + if (!LineOption.YAxis.MinAuto) YScale.Min = LineOption.YAxis.Min; + YLabels = YScale.CalcLabels(); } - if (min > 0 && max > 0 && !LineOption.YAxis.Scale) min = 0; - if (min < 0 && max < 0 && !LineOption.YAxis.Scale) max = 0; - if (!LineOption.YAxis.MaxAuto) max = LineOption.YAxis.Max; - if (!LineOption.YAxis.MinAuto) min = LineOption.YAxis.Min; - - if ((max - min).IsZero()) - { - max = 100; - min = 0; - } - - UIChartHelper.CalcDegreeScale(min, max, LineOption.YAxis.SplitNumber, - out int startY, out int endY, out double intervalY); - - YAxisStart = startY; - YAxisEnd = endY; - YAxisInterval = intervalY; - //X轴 - min = double.MaxValue; - max = double.MinValue; - foreach (var series in LineOption.Series.Values) { - min = Math.Min(min, series.XData.Min()); - max = Math.Max(max, series.XData.Max()); - } - - if (min > 0 && max > 0 && !LineOption.XAxis.Scale && LineOption.XAxisType == UIAxisType.Value) min = 0; - if (min < 0 && max < 0 && !LineOption.XAxis.Scale && LineOption.XAxisType == UIAxisType.Value) max = 0; - if (!LineOption.XAxis.MaxAuto) max = LineOption.XAxis.Max; - if (!LineOption.XAxis.MinAuto) min = LineOption.XAxis.Min; - - if ((max - min).IsZero()) - { - max = 100; - min = 0; - } - - if (LineOption.XAxisType == UIAxisType.Value || LineOption.XAxisType == UIAxisType.Category) - { - UIChartHelper.CalcDegreeScale(min, max, LineOption.XAxis.SplitNumber, - out int startX, out int endX, out double intervalX); - XAxisStart = startX; - XAxisEnd = endX; - XAxisInterval = intervalX; - } - - if (LineOption.XAxisType == UIAxisType.Time) - { - UIChartHelper.CalcDateTimeDegreeScale(min, max, LineOption.XAxis.SplitNumber, - out int startX, out int endX, out double intervalX); - XAxisStart = startX; - XAxisEnd = endX; - XAxisInterval = intervalX; + LineOption.GetAllDataXRange(out double min, out double max); + XScale.SetRange(min, max); + XScale.AxisChange(); + if (!LineOption.XAxis.MaxAuto) XScale.Max = LineOption.XAxis.Max; + if (!LineOption.XAxis.MinAuto) XScale.Min = LineOption.XAxis.Min; + XLabels = XScale.CalcLabels(); } } @@ -192,55 +147,37 @@ namespace Sunny.UI private void DrawAxis(Graphics g) { - if (YAxisStart >= 0) g.DrawLine(ChartStyle.ForeColor, DrawOrigin, - new Point(DrawOrigin.X + DrawSize.Width, DrawOrigin.Y)); - if (YAxisEnd <= 0) g.DrawLine(ChartStyle.ForeColor, new Point(DrawOrigin.X, LineOption.Grid.Top), - new Point(DrawOrigin.X + DrawSize.Width, LineOption.Grid.Top)); - - g.DrawLine(ChartStyle.ForeColor, DrawOrigin, new Point(DrawOrigin.X, DrawOrigin.Y - DrawSize.Height)); + g.DrawRectangle(ChartStyle.ForeColor, LineOption.Grid.Left, LineOption.Grid.Top, DrawSize.Width, DrawSize.Height); + if (XScale == null || YScale == null) return; //X Tick if (LineOption.XAxis.AxisTick.Show) { - float start = DrawOrigin.X; - float DrawBarWidth = DrawSize.Width * 1.0f / (XAxisEnd - XAxisStart); - for (int i = XAxisStart; i <= XAxisEnd; i++) + float[] xlabels = XScale.CalcXPixels(XLabels, DrawOrigin.X, DrawSize.Width); + for (int i = 0; i < xlabels.Length; i++) { - g.DrawLine(ChartStyle.ForeColor, start, DrawOrigin.Y, start, DrawOrigin.Y + LineOption.XAxis.AxisTick.Length); + float x = xlabels[i]; + if (LineOption.XAxis.AxisLabel.Show) + { + string label; + if (LineOption.XAxisType == UIAxisType.DateTime) + label = new DateTimeInt64(XLabels[i]).ToString(XScale.Format); + else + label = XLabels[i].ToString(XScale.Format); - if (i != 0) - { - using (Pen pn = new Pen(ChartStyle.ForeColor)) - { - pn.DashStyle = DashStyle.Dash; - pn.DashPattern = new float[] { 3, 3 }; - g.DrawLine(pn, start, DrawOrigin.Y, start, LineOption.Grid.Top); - } - } - else - { - g.DrawLine(ChartStyle.ForeColor, start, DrawOrigin.Y, start, LineOption.Grid.Top); + SizeF sf = g.MeasureString(label, SubFont); + g.DrawString(label, SubFont, ChartStyle.ForeColor, x - sf.Width / 2.0f, DrawOrigin.Y + LineOption.XAxis.AxisTick.Length); } - start += DrawBarWidth; - } - } + if (x.Equals(DrawOrigin.X)) continue; + if (x.Equals(DrawOrigin.X + DrawSize.Width)) continue; - //X Label - if (LineOption.XAxis.AxisLabel.Show) - { - float start = DrawOrigin.X; - float DrawBarWidth = DrawSize.Width * 1.0f / (XAxisEnd - XAxisStart); - int idx = 0; - float wmax = 0; - for (int i = XAxisStart; i <= XAxisEnd; i++) - { - string label = LineOption.XAxis.AxisLabel.GetLabel(i * XAxisInterval, idx, LineOption.XAxisType); - SizeF sf = g.MeasureString(label, SubFont); - wmax = Math.Max(wmax, sf.Width); - g.DrawString(label, SubFont, ChartStyle.ForeColor, start - sf.Width / 2.0f, - DrawOrigin.Y + LineOption.XAxis.AxisTick.Length); - start += DrawBarWidth; + using (Pen pn = new Pen(ChartStyle.ForeColor)) + { + pn.DashStyle = DashStyle.Dash; + pn.DashPattern = new float[] { 3, 3 }; + g.DrawLine(pn, x, DrawOrigin.Y, x, LineOption.Grid.Top); + } } SizeF sfname = g.MeasureString(LineOption.XAxis.Name, SubFont); @@ -252,64 +189,51 @@ namespace Sunny.UI //Y Tick if (LineOption.YAxis.AxisTick.Show) { - float start = DrawOrigin.Y; - float DrawBarHeight = DrawSize.Height * 1.0f / (YAxisEnd - YAxisStart); - for (int i = YAxisStart; i <= YAxisEnd; i++) - { - g.DrawLine(ChartStyle.ForeColor, DrawOrigin.X, start, DrawOrigin.X - LineOption.YAxis.AxisTick.Length, start); - - if (i != 0) - { - using (Pen pn = new Pen(ChartStyle.ForeColor)) - { - pn.DashStyle = DashStyle.Dash; - pn.DashPattern = new float[] { 3, 3 }; - g.DrawLine(pn, DrawOrigin.X, start, Width - LineOption.Grid.Right, start); - } - } - else - { - g.DrawLine(ChartStyle.ForeColor, DrawOrigin.X, start, Width - LineOption.Grid.Right, start); - } - - start -= DrawBarHeight; - } - } - - //Y Label - if (LineOption.YAxis.AxisLabel.Show) - { - float start = DrawOrigin.Y; - float DrawBarHeight = DrawSize.Height * 1.0f / (YAxisEnd - YAxisStart); - int idx = 0; + float[] ylabels = YScale.CalcYPixels(YLabels, DrawOrigin.Y, DrawSize.Height); float wmax = 0; - for (int i = YAxisStart; i <= YAxisEnd; i++) + for (int i = 0; i < ylabels.Length; i++) { - string label = LineOption.YAxis.AxisLabel.GetLabel(i * YAxisInterval, idx); - SizeF sf = g.MeasureString(label, SubFont); - wmax = Math.Max(wmax, sf.Width); - g.DrawString(label, SubFont, ChartStyle.ForeColor, DrawOrigin.X - LineOption.YAxis.AxisTick.Length - sf.Width, start - sf.Height / 2.0f); - start -= DrawBarHeight; + float y = ylabels[i]; + if (LineOption.YAxis.AxisLabel.Show) + { + string label = YLabels[i].ToString(YScale.Format); + SizeF sf = g.MeasureString(label, SubFont); + wmax = Math.Max(wmax, sf.Width); + g.DrawString(label, SubFont, ChartStyle.ForeColor, DrawOrigin.X - LineOption.YAxis.AxisTick.Length - sf.Width, y - sf.Height / 2.0f); + } + + if (y.Equals(DrawOrigin.Y)) continue; + if (y.Equals(DrawOrigin.X - DrawSize.Height)) continue; + + using (Pen pn = new Pen(ChartStyle.ForeColor)) + { + pn.DashStyle = DashStyle.Dash; + pn.DashPattern = new float[] { 3, 3 }; + g.DrawLine(pn, DrawOrigin.X, y, Width - LineOption.Grid.Right, y); + } } + SizeF sfname = g.MeasureString(LineOption.YAxis.Name, SubFont); - int x = (int)(DrawOrigin.X - LineOption.YAxis.AxisTick.Length - wmax - sfname.Height); - int y = (int)(LineOption.Grid.Top + (DrawSize.Height - sfname.Width) / 2); - g.DrawString(LineOption.YAxis.Name, SubFont, ChartStyle.ForeColor, new Point(x, y), + int xx = (int)(DrawOrigin.X - LineOption.YAxis.AxisTick.Length - wmax - sfname.Height); + int yy = (int)(LineOption.Grid.Top + (DrawSize.Height - sfname.Width) / 2); + g.DrawString(LineOption.YAxis.Name, SubFont, ChartStyle.ForeColor, new Point(xx, yy), new StringFormat() { Alignment = StringAlignment.Center }, 270); } } private void DrawSeries(Graphics g) { - int idx = 0; - foreach (var series in LineOption.Series.Values) - { - Color color = series.Color; - if (!series.CustomColor) color = ChartStyle.GetColor(idx); + if (YScale == null) return; - if (LineOption.GreaterWarningArea == null && LineOption.LessWarningArea == null) + int idx = 0; + if (LineOption.GreaterWarningArea == null && LineOption.LessWarningArea == null) + { + foreach (var series in LineOption.Series.Values) { + Color color = series.Color; + if (!series.CustomColor) color = ChartStyle.GetColor(idx); + using (Pen pen = new Pen(color, series.Width)) { g.SetHighQuality(); @@ -319,15 +243,22 @@ namespace Sunny.UI g.DrawLines(pen, series.Points.ToArray()); g.SetDefaultQuality(); } - } - else - { - Bitmap bmp = new Bitmap(Width, Height); - Bitmap bmpGreater; - Bitmap bmpLess; - float wTop = 0; - float wBottom = Height; + idx++; + } + } + else + { + Bitmap bmp = new Bitmap(Width, Height); + Bitmap bmpGreater = new Bitmap(Width, Height); + Bitmap bmpLess = new Bitmap(Width, Height); + float wTop = 0; + float wBottom = Height; + + foreach (var series in LineOption.Series.Values) + { + Color color = series.Color; + if (!series.CustomColor) color = ChartStyle.GetColor(idx); using (Pen pen = new Pen(color, series.Width)) { @@ -343,9 +274,7 @@ namespace Sunny.UI if (LineOption.GreaterWarningArea != null) { using (Pen pen = new Pen(LineOption.GreaterWarningArea.Color, series.Width)) - using (bmpGreater = new Bitmap(Width, Height)) { - bmpGreater = new Bitmap(Width, Height); Graphics graphics = bmpGreater.Graphics(); graphics.SetHighQuality(); if (series.Smooth) @@ -353,18 +282,12 @@ namespace Sunny.UI else graphics.DrawLines(pen, series.Points.ToArray()); graphics.SetDefaultQuality(); - - wTop = (float)((LineOption.GreaterWarningArea.Value - YAxisStart * YAxisInterval) * 1.0f * DrawSize.Height / YAxisInterval / (YAxisEnd - YAxisStart)); - wTop = DrawOrigin.Y - wTop; - g.DrawImage(bmpGreater, new Rectangle(0, 0, Width, (int)wTop), - new Rectangle(0, 0, Width, (int)wTop), GraphicsUnit.Pixel); } } if (LineOption.LessWarningArea != null) { using (Pen pen = new Pen(LineOption.LessWarningArea.Color, series.Width)) - using (bmpLess = new Bitmap(Width, Height)) { Graphics graphics = bmpLess.Graphics(); graphics.SetHighQuality(); @@ -373,19 +296,42 @@ namespace Sunny.UI else graphics.DrawLines(pen, series.Points.ToArray()); graphics.SetDefaultQuality(); - - wBottom = (float)((LineOption.LessWarningArea.Value - YAxisStart * YAxisInterval) * 1.0f * DrawSize.Height / YAxisInterval / (YAxisEnd - YAxisStart)); - wBottom = DrawOrigin.Y - wBottom; - g.DrawImage(bmpLess, new Rectangle(0, (int)wBottom, Width, Height - (int)wBottom), - new Rectangle(0, (int)wBottom, Width, Height - (int)wBottom), GraphicsUnit.Pixel); } } - g.DrawImage(bmp, new Rectangle(0, (int)wTop, Width, (int)wBottom - (int)wTop), - new Rectangle(0, (int)wTop, Width, (int)wBottom - (int)wTop), GraphicsUnit.Pixel); - bmp.Dispose(); + idx++; } + if (LineOption.GreaterWarningArea != null) + { + wTop = YScale.CalcYPixel(LineOption.GreaterWarningArea.Value, DrawOrigin.Y, DrawSize.Height); + wTop = DrawOrigin.Y - wTop; + g.DrawImage(bmpGreater, new Rectangle(0, 0, Width, (int)wTop), + new Rectangle(0, 0, Width, (int)wTop), GraphicsUnit.Pixel); + } + + if (LineOption.LessWarningArea != null) + { + wBottom = YScale.CalcYPixel(LineOption.LessWarningArea.Value, DrawOrigin.Y, DrawSize.Height); + wBottom = DrawOrigin.Y - wBottom; + g.DrawImage(bmpLess, new Rectangle(0, (int)wBottom, Width, Height - (int)wBottom), + new Rectangle(0, (int)wBottom, Width, Height - (int)wBottom), GraphicsUnit.Pixel); + } + + g.DrawImage(bmp, new Rectangle(0, (int)wTop, Width, (int)wBottom - (int)wTop), + new Rectangle(0, (int)wTop, Width, (int)wBottom - (int)wTop), GraphicsUnit.Pixel); + + bmpGreater.Dispose(); + bmpLess.Dispose(); + bmp.Dispose(); + } + + idx = 0; + foreach (var series in LineOption.Series.Values) + { + Color color = series.Color; + if (!series.CustomColor) color = ChartStyle.GetColor(idx); + if (series.Symbol != UILinePointSymbol.None) { using (Brush br = new SolidBrush(ChartStyle.BackColor)) @@ -456,11 +402,11 @@ namespace Sunny.UI private void DrawAxisScales(Graphics g) { + if (YScale == null) return; + foreach (var line in LineOption.YAxisScaleLines) { - double ymin = YAxisStart * YAxisInterval; - double ymax = YAxisEnd * YAxisInterval; - float pos = (float)((line.Value - ymin) * (Height - LineOption.Grid.Top - LineOption.Grid.Bottom) / (ymax - ymin)); + float pos = YScale.CalcYPixel(line.Value, DrawOrigin.Y, DrawSize.Height); pos = (Height - LineOption.Grid.Bottom - pos); using (Pen pn = new Pen(line.Color, line.Size)) { diff --git a/SunnyUI/Charts/UILineChartOption.cs b/SunnyUI/Charts/UILineChartOption.cs index 7cf67929..77bea830 100644 --- a/SunnyUI/Charts/UILineChartOption.cs +++ b/SunnyUI/Charts/UILineChartOption.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Drawing; +using System.Linq; namespace Sunny.UI { @@ -157,6 +158,61 @@ namespace Sunny.UI XAxis.Data.Add(label); } } + + public int AllDataCount() + { + int cnt = 0; + foreach (var series in Series.Values) + { + cnt += series.DataCount; + } + + return cnt; + } + + public void GetAllDataYRange(out double min, out double max) + { + if (AllDataCount() == 0) + { + min = 0; + max = 1; + } + else + { + min = double.MaxValue; + max = double.MinValue; + foreach (var series in Series.Values) + { + if (series.DataCount > 0) + { + min = Math.Min(min, series.YData.Min()); + max = Math.Max(max, series.YData.Max()); + } + } + } + } + + public void GetAllDataXRange(out double min, out double max) + { + if (AllDataCount() == 0) + { + min = 0; + max = 1; + } + else + { + min = double.MaxValue; + max = double.MinValue; + foreach (var series in Series.Values) + { + if (series.DataCount > 0) + { + min = Math.Min(min, series.XData.Min()); + max = Math.Max(max, series.XData.Max()); + } + } + } + } } public class UILineSeries @@ -239,6 +295,16 @@ namespace Sunny.UI PointsY.Add(point.Y); } + public void AddPoints(float[] x, float[] y) + { + if (x.Length != y.Length) return; + if (x.Length == 0) return; + for (int i = 0; i < x.Length; i++) + { + AddPoint(new PointF(x[i], y[i])); + } + } + public void Add(double x, double y) { XData.Add(x); diff --git a/SunnyUI/Charts/UIOption.cs b/SunnyUI/Charts/UIOption.cs index e7cb2998..2742acf4 100644 --- a/SunnyUI/Charts/UIOption.cs +++ b/SunnyUI/Charts/UIOption.cs @@ -19,7 +19,6 @@ * 2020-06-06: V2.2.5 增加文件说明 ******************************************************************************/ -using System; using System.Collections.Generic; using System.Drawing; @@ -96,7 +95,7 @@ namespace Sunny.UI { Value, Category, - Time + DateTime } public class UITitle @@ -175,155 +174,5 @@ namespace Sunny.UI } } - public static class UIChartHelper - { - /// - /// 计算刻度 - /// 起始值必须小于结束值 - /// - /// 起始值 - /// 结束值 - /// 期望刻度数量,实际数接近此数 - /// 刻度起始值,须乘以间隔使用 - /// 刻度结束值,须乘以间隔使用 - /// 刻度间隔 - public static void CalcDegreeScale(double start, double end, int expect_num, - out int degree_start, out int degree_end, out double degree_gap) - { - if (start >= end) - { - throw new Exception("起始值必须小于结束值"); - } - double differ = end - start; - double differ_gap = differ / (expect_num - 1); //35, 4.6, 0.27 - - double exponent = Math.Log10(differ_gap) - 1; //0.54, -0.34, -1.57 - int _exponent = (int)exponent; //0, 0=>-1, -1=>-2 - if (exponent < 0 && Math.Abs(exponent) > 1e-8) - { - _exponent--; - } - - int step = (int)(differ_gap / Math.Pow(10, _exponent)); //35, 46, 27 - int[] fix_steps = new int[] { 10, 20, 25, 50, 100 }; - int fix_step = 10; //25, 50, 25 - for (int i = fix_steps.Length - 1; i >= 1; i--) - { - if (step > (fix_steps[i] + fix_steps[i - 1]) / 2) - { - fix_step = fix_steps[i]; - break; - } - } - - degree_gap = fix_step * Math.Pow(10, _exponent); //25, 5, 0.25 - - double start1 = start / degree_gap; - int start2 = (int)start1; - if (start1 < 0 && Math.Abs(start1 - start2) > 1e-8) - { - start2--; - } - - degree_start = start2; - - double end1 = end / degree_gap; - int end2 = (int)end1; - if (end1 >= 0 && Math.Abs(end1 - end2) > 1e-8) - { - end2++; - } - - degree_end = end2; - } - - /// - /// 计算刻度 - /// 起始值必须小于结束值 - /// - /// 起始值 - /// 结束值 - /// 期望刻度数量,实际数接近此数 - /// 刻度起始值,须乘以间隔使用 - /// 刻度结束值,须乘以间隔使用 - /// 刻度间隔 - public static void CalcDateTimeDegreeScale(double start, double end, int expect_num, - out int degree_start, out int degree_end, out double degree_gap) - { - if (start >= end) - { - throw new Exception("起始值必须小于结束值"); - } - - double differ = end - start; - double differ_gap = differ / (expect_num - 1); //35, 4.6, 0.27 - - double exponent = Math.Log10(differ_gap) - 1; //0.54, -0.34, -1.57 - int _exponent = (int)exponent; //0, 0=>-1, -1=>-2 - if (exponent < 0 && Math.Abs(exponent) > 1e-8) - { - _exponent--; - } - - int step = (int)(differ_gap / Math.Pow(10, _exponent)); //35, 46, 27 - int[] fix_steps = new int[] { 10, 20, 30, 60, 120 }; - int fix_step = 10; //25, 50, 25 - for (int i = fix_steps.Length - 1; i >= 1; i--) - { - if (step > (fix_steps[i] + fix_steps[i - 1]) / 2) - { - fix_step = fix_steps[i]; - break; - } - } - - degree_gap = fix_step * Math.Pow(10, _exponent); //25, 5, 0.25 - - double start1 = start / degree_gap; - int start2 = (int)start1; - if (start1 < 0 && Math.Abs(start1 - start2) > 1e-8) - { - start2--; - } - - degree_start = start2; - - double end1 = end / degree_gap; - int end2 = (int)end1; - if (end1 >= 0 && Math.Abs(end1 - end2) > 1e-8) - { - end2++; - } - - degree_end = end2; - } - - /// - /// 计算刻度 - /// 起始值必须小于结束值 - /// - /// 起始值 - /// 结束值 - /// 期望刻度数量,实际数接近此数 - /// 刻度列表 - public static double[] CalcDegreeScale(double start, double end, int expect_num) - { - if (start >= end) - { - throw new Exception("起始值必须小于结束值"); - } - - CalcDegreeScale(start, end, expect_num, out int degree_start, out int degree_end, - out double degree_gap); - - double[] list = new double[degree_end - degree_start + 1]; - for (int i = degree_start; i <= degree_end; i++) - { - list[i - degree_start] = i * degree_gap; - } - - return list; - } - } } \ No newline at end of file diff --git a/SunnyUI/Charts/UIScale.cs b/SunnyUI/Charts/UIScale.cs new file mode 100644 index 00000000..1cba343a --- /dev/null +++ b/SunnyUI/Charts/UIScale.cs @@ -0,0 +1,560 @@ +using System; +using System.Globalization; +using static System.Double; + +namespace Sunny.UI +{ + public abstract class UIScale + { + protected double _rangeMin; + protected double _rangeMax; + + 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; + + public void SetRange(double rangeMin, double rangeMax) + { + _rangeMax = rangeMax; + _rangeMin = rangeMin; + } + + public abstract double[] CalcLabels(); + + public float CalcXPixel(double value, int origin, int width) + { + return origin + (float)((value - _min) * 1.0f * width / (_max - _min)); + } + + public float CalcYPixel(double value, int origin, int height) + { + return origin - (float)((value - _min) * 1.0f * height / (_max - _min)); + } + + public float[] CalcXPixels(double[] labels, int origin, int width) + { + if (labels == null) return null; + float[] result = new float[labels.Length]; + for (int i = 0; i < labels.Length; i++) + { + result[i] = CalcXPixel(labels[i], origin, width); + } + + return result; + } + + public float[] CalcYPixels(double[] labels, int origin, int height) + { + if (labels == null) return null; + float[] result = new float[labels.Length]; + for (int i = 0; i < labels.Length; i++) + { + result[i] = CalcYPixel(labels[i], origin, height); + } + + return result; + } + + public int Mag + { + get { return _mag; } + set { _mag = value; _magAuto = false; } + } + + public bool MagAuto + { + get { return _magAuto; } + set { _magAuto = value; } + } + + public double MinGrace + { + get { return _minGrace; } + set { _minGrace = value; } + } + + public double MaxGrace + { + get { return _maxGrace; } + set { _maxGrace = value; } + } + + public bool FormatAuto + { + get { return _formatAuto; } + set { _formatAuto = value; } + } + + public string Format + { + get { return _format; } + set { _format = value; _formatAuto = false; } + } + + public virtual double Min + { + get => _min; + set { _min = value; _minAuto = false; } + } + + public virtual double Max + { + get => _max; + set { _max = value; _maxAuto = false; } + } + + public bool MinAuto + { + get => _minAuto; + set => _minAuto = value; + } + + public bool MaxAuto + { + get => _maxAuto; + set => _maxAuto = value; + } + + public double Step { get; protected set; } + + public virtual void AxisChange() + { + double minVal = _rangeMin; + double maxVal = _rangeMax; + + if (IsInfinity(minVal) || IsNaN(minVal) || minVal.Equals(MaxValue)) + minVal = 0.0; + if (IsInfinity(maxVal) || IsNaN(maxVal) || maxVal.Equals(MaxValue)) + maxVal = 0.0; + + double range = maxVal - minVal; + + if (_minAuto) + { + _min = minVal; + 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.Equals(_min) && _maxAuto && _minAuto) + { + if (Math.Abs(_max) > 1e-100) + { + _max *= (_min < 0 ? 0.95 : 1.05); + _min *= (_min < 0 ? 1.05 : 0.95); + } + else + { + _max = 1.0; + _min = -1.0; + } + } + + if (_max <= _min) + { + if (_maxAuto) + _max = _min + 1.0; + else if (_minAuto) + _min = _max - 1.0; + } + } + + public double CalcStepSize(double range, double targetSteps) + { + double tempStep = range / targetSteps; + double mag = Math.Floor(Math.Log10(tempStep)); + double magPow = Math.Pow(10.0, mag); + double magMsd = ((int)(tempStep / magPow + .5)); + if (magMsd > 5.0) + magMsd = 10.0; + else if (magMsd > 2.0) + magMsd = 5.0; + else if (magMsd > 1.0) + magMsd = 2.0; + + return magMsd * magPow; + } + } + + public class UILinearScale : UIScale + { + private static double ZeroLever = 0.25; + + public override void AxisChange() + { + base.AxisChange(); + + if (_max - _min < 1.0e-30) + { + if (_maxAuto) _max = _max + 0.2 * (_max == 0 ? 1.0 : Math.Abs(_max)); + if (_minAuto) _min = _min - 0.2 * (_min == 0 ? 1.0 : Math.Abs(_min)); + } + + if (_minAuto && _min > 0 && _min / (_max - _min) < ZeroLever) _min = 0; + if (_maxAuto && _max < 0 && Math.Abs(_max / (_max - _min)) < ZeroLever) _max = 0; + + Step = CalcStepSize(_max - _min, TargetSteps); + + if (_minAuto) _min = _min - MyMod(_min, Step); + if (_maxAuto) _max = MyMod(_max, Step) == 0.0 ? _max : _max + Step - MyMod(_max, Step); + SetScaleMag(); + } + + private void SetScaleMag() + { + if (_magAuto) + { + double minMag = Math.Floor(Math.Log10(Math.Abs(_min))); + double maxMag = Math.Floor(Math.Log10(Math.Abs(_max))); + double mag = Math.Max(maxMag, minMag); + if (Math.Abs(mag) <= 3) mag = 0; + _mag = (int)(Math.Floor(mag / 3.0) * 3.0); + } + + if (_formatAuto) + { + int numDec = 0 - (int)(Math.Floor(Math.Log10(Step)) - _mag); + if (numDec < 0) numDec = 0; + _format = "f" + numDec.ToString(CultureInfo.InvariantCulture); + } + } + + private double MyMod(double x, double y) + { + if (y == 0) return 0; + double temp = x / y; + return y * (temp - Math.Floor(temp)); + } + + public override double[] CalcLabels() + { + int nTics = CalcNumTics(); + double startVal = CalcBaseTic(); + double[] result = new double[nTics]; + for (int i = 0; i < nTics; i++) + { + result[i] = CalcMajorTicValue(startVal, i); + } + + return result; + } + + private double CalcMajorTicValue(double baseVal, double tic) + { + return baseVal + Step * tic; + } + + private double CalcBaseTic() + { + return Math.Ceiling(_min / Step - 0.00000001) * Step; + } + + private int CalcNumTics() + { + int nTics = (int)((_max - _min) / Step + 0.01) + 1; + + if (nTics < 1) nTics = 1; + else if (nTics > 1000) nTics = 1000; + + return nTics; + } + } + + public class UIDateScale : UIScale + { + public override void AxisChange() + { + base.AxisChange(); + + if (_max - _min < 1.0e-20) + { + if (_maxAuto) _max = _max + 0.2 * (_max == 0 ? 1.0 : Math.Abs(_max)); + if (_minAuto) _min = _min - 0.2 * (_min == 0 ? 1.0 : Math.Abs(_min)); + } + + DateTimeInt64 max = new DateTimeInt64(_max); + DateTimeInt64 min = new DateTimeInt64(_min); + Step = CalcDateStepSize(max, min, TargetSteps); + + if (_minAuto) _min = CalcEvenStepDate(_min, -1); + if (_maxAuto) _max = CalcEvenStepDate(_max, 1); + _mag = 0; + } + + public override double[] CalcLabels() + { + int nTics = CalcNumTics(); + double startVal = CalcBaseTic(); + double[] result = new double[nTics]; + for (int i = 0; i < nTics; i++) + { + result[i] = CalcMajorTicValue(startVal, i); + } + + return result; + } + + private double CalcEvenStepDate(double date, int direction) + { + DateTimeInt64 dtLong = new DateTimeInt64(date); + int year = dtLong.Year; + int month = dtLong.Month; + int day = dtLong.Day; + int hour = dtLong.Hour; + int minute = dtLong.Minute; + int second = dtLong.Second; + int millisecond = dtLong.Millisecond; + + if (direction < 0) direction = 0; + switch (_scaleLevel) + { + default: + if (direction == 1 && month == 1 && day == 1 && hour == 0 && minute == 0 && second == 0) + return date; + else + return new DateTimeInt64(new DateTime(year + direction, 1, 1, 0, 0, 0)).DoubleValue; + + case UIDateScaleLevel.Month: + if (direction == 1 && day == 1 && hour == 0 && minute == 0 && second == 0) + return date; + else + return new DateTimeInt64(new DateTime(year, month + direction, 1, 0, 0, 0)).DoubleValue; + + case UIDateScaleLevel.Day: + if (direction == 1 && hour == 0 && minute == 0 && second == 0) + return date; + else + return new DateTimeInt64(new DateTime(year, month, day + direction, 0, 0, 0)).DoubleValue; + + case UIDateScaleLevel.Hour: + if (direction == 1 && minute == 0 && second == 0) + return date; + else + return new DateTimeInt64(new DateTime(year, month, day, hour + direction, 0, 0)).DoubleValue; + + case UIDateScaleLevel.Minute: + if (direction == 1 && second == 0) + return date; + else + return new DateTimeInt64(new DateTime(year, month, day, hour, minute + direction, 0)).DoubleValue; + + case UIDateScaleLevel.Second: + return new DateTimeInt64(new DateTime(year, month, day, hour, minute, second + direction)).DoubleValue; + + case UIDateScaleLevel.Millisecond: + return new DateTimeInt64(new DateTime(year, month, day, hour, minute, second, millisecond + direction)).DoubleValue; + } + } + + private UIDateScaleLevel _scaleLevel; + + private double CalcDateStepSize(DateTimeInt64 max, DateTimeInt64 min, double targetSteps) + { + double range = _max - _min; + double tempStep = range / targetSteps; + TimeSpan span = max.DateTime - min.DateTime; + + if (span.TotalDays > 1825) // 5 years + { + _scaleLevel = UIDateScaleLevel.Year; + 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"; + tempStep = Math.Ceiling(tempStep / 365.0); + } + else if (span.TotalDays > 300) // 10 months + { + _scaleLevel = UIDateScaleLevel.Month; + 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"; + tempStep = Math.Ceiling(tempStep); + } + else if (span.TotalDays > 3) // 3 days + { + _scaleLevel = UIDateScaleLevel.Day; + 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"; + tempStep = Math.Ceiling(tempStep * 24.0); + + if (tempStep > 12.0) tempStep = 24.0; + else if (tempStep > 6.0) tempStep = 12.0; + else if (tempStep > 2.0) tempStep = 6.0; + else if (tempStep > 1.0) tempStep = 2.0; + else tempStep = 1.0; + } + else if (span.TotalHours > 3) // 3 hours + { + _scaleLevel = UIDateScaleLevel.Hour; + 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"; + 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; + else if (tempStep > 5.0) tempStep = 15.0; + else if (tempStep > 1.0) tempStep = 5.0; + else tempStep = 1.0; + } + else if (span.TotalMinutes > 3) // 3 Minutes + { + _scaleLevel = UIDateScaleLevel.Minute; + 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"; + + tempStep = Math.Ceiling(tempStep * 86400.0); + // make sure the second step size is 1, 5, 15, or 30 seconds + if (tempStep > 15.0) tempStep = 30.0; + else if (tempStep > 5.0) tempStep = 15.0; + else if (tempStep > 1.0) tempStep = 5.0; + else tempStep = 1.0; + } + else + { + _scaleLevel = UIDateScaleLevel.Millisecond; + if (_formatAuto) _format = "ss.fff"; + tempStep = CalcStepSize(span.TotalMilliseconds, targetSteps); + } + + return tempStep; + } + + private double CalcMajorTicValue(double baseVal, double tic) + { + DateTimeInt64 dtLong = new DateTimeInt64(baseVal); + switch (_scaleLevel) + { + default: dtLong.AddYears((int)(tic * Step)); break; + case UIDateScaleLevel.Month: dtLong.AddMonths((int)(tic * Step)); break; + case UIDateScaleLevel.Day: dtLong.AddDays(tic * Step); break; + case UIDateScaleLevel.Hour: dtLong.AddHours(tic * Step); break; + case UIDateScaleLevel.Minute: dtLong.AddMinutes(tic * Step); break; + case UIDateScaleLevel.Second: dtLong.AddSeconds(tic * Step); break; + case UIDateScaleLevel.Millisecond: dtLong.AddMilliseconds(tic * Step); break; + } + + return dtLong.DoubleValue; + } + + private int CalcNumTics() + { + int nTics; + DateTimeInt64 max = new DateTimeInt64(_max); + DateTimeInt64 min = new DateTimeInt64(_min); + + switch (_scaleLevel) + { + default: nTics = (int)((max.Year - min.Year) / Step + 1.001); break; + case UIDateScaleLevel.Month: nTics = (int)((max.Month - min.Month + 12.0 * (max.Year - min.Year)) / Step + 1.001); break; + case UIDateScaleLevel.Day: nTics = (int)((_max - _min) / Step + 1.001); break; + case UIDateScaleLevel.Hour: nTics = (int)((_max - _min) / (Step / HoursPerDay) + 1.001); break; + case UIDateScaleLevel.Minute: nTics = (int)((_max - _min) / (Step / MinutesPerDay) + 1.001); break; + case UIDateScaleLevel.Second: nTics = (int)((_max - _min) / (Step / SecondsPerDay) + 1.001); break; + case UIDateScaleLevel.Millisecond: nTics = (int)((_max - _min) / (Step / MillisecondsPerDay) + 1.001); break; + } + + if (nTics < 1) + nTics = 1; + else if (nTics > 1000) + nTics = 1000; + + return nTics; + } + + private double CalcBaseTic() + { + DateTimeInt64 dtLong = new DateTimeInt64(_min); + int year = dtLong.Year; + int month = dtLong.Month; + int day = dtLong.Day; + int hour = dtLong.Hour; + int minute = dtLong.Minute; + int second = dtLong.Second; + int millisecond = dtLong.Millisecond; + + switch (_scaleLevel) + { + default: month = 1; day = 1; hour = 0; minute = 0; second = 0; millisecond = 0; break; + case UIDateScaleLevel.Month: day = 1; hour = 0; minute = 0; second = 0; millisecond = 0; break; + case UIDateScaleLevel.Day: hour = 0; minute = 0; second = 0; millisecond = 0; break; + case UIDateScaleLevel.Hour: minute = 0; second = 0; millisecond = 0; break; + case UIDateScaleLevel.Minute: second = 0; millisecond = 0; break; + case UIDateScaleLevel.Second: millisecond = 0; break; + case UIDateScaleLevel.Millisecond: break; + } + + DateTimeInt64 xlDateNew = new DateTimeInt64(year, month, day, hour, minute, second, millisecond); + double xlDate = xlDateNew.DoubleValue; + if (xlDate < _min) + { + switch (_scaleLevel) + { + default: year++; break; + case UIDateScaleLevel.Month: month++; break; + case UIDateScaleLevel.Day: day++; break; + case UIDateScaleLevel.Hour: hour++; break; + case UIDateScaleLevel.Minute: minute++; break; + case UIDateScaleLevel.Second: second++; break; + case UIDateScaleLevel.Millisecond: millisecond++; break; + } + + xlDateNew = new DateTimeInt64(year, month, day, hour, minute, second, millisecond); + } + + return xlDateNew.DoubleValue; + } + + private const double HoursPerDay = 24.0; + private const double MinutesPerDay = 1440.0; + private const double SecondsPerDay = 86400.0; + private const double MillisecondsPerDay = 86400000.0; + } + + public enum UIDateScaleLevel + { + Year, + Month, + Day, + Hour, + Minute, + Second, + Millisecond + } +} \ No newline at end of file diff --git a/SunnyUI/SunnyUI.csproj b/SunnyUI/SunnyUI.csproj index 38482e77..7408aba0 100644 --- a/SunnyUI/SunnyUI.csproj +++ b/SunnyUI/SunnyUI.csproj @@ -71,6 +71,7 @@ + Component diff --git a/SunnyUI/Units/UDateTimeInt64.cs b/SunnyUI/Units/UDateTimeInt64.cs index 39acff6e..971c960e 100644 --- a/SunnyUI/Units/UDateTimeInt64.cs +++ b/SunnyUI/Units/UDateTimeInt64.cs @@ -17,12 +17,18 @@ * 创建日期: 2020-09-23 * * 2020-09-23: V2.2.8 增加文件说明 + * 2020-10-10: V2.2.8 增加与Double互转 ******************************************************************************/ using System; namespace Sunny.UI { + /// + /// 日期与长整形和浮点型互转 + /// Value:长整形,为1970年1月1日0时起的毫秒数 + /// DoubleValue:双精度浮点数,1970年1月1日0时起的天数 + /// public struct DateTimeInt64 : IComparable, IEquatable, IEquatable, IEquatable { public long Value { get; set; } @@ -30,6 +36,7 @@ namespace Sunny.UI public const long Jan1st1970Ticks = 621355968000000000; //Jan1st1970.Ticks; public const long Dec31th9999Ticks = 3155378975999990000; //DateTime.MaxValue.Ticks; public const string DefaultFormatString = DateTimeEx.DateTimeFormatEx; + public const double MillisecondsPerDay = 86400000.0; /// /// 返回当前时间的毫秒数, 这个毫秒其实就是自1970年1月1日0时起的毫秒数 @@ -39,6 +46,12 @@ namespace Sunny.UI return (DateTime.UtcNow.Ticks - Jan1st1970Ticks) / 10000; } + public double DoubleValue + { + get { return Value * 1.0 / MillisecondsPerDay; } + set { Value = (long)(value * MillisecondsPerDay); } + } + /// /// 返回指定时间的毫秒数, 这个毫秒其实就是自1970年1月1日0时起的毫秒数 /// @@ -69,6 +82,12 @@ namespace Sunny.UI Value = ticks; } + public DateTimeInt64(double doubleTicks) + { + doubleTicks = MakeValidDate((long)(doubleTicks * MillisecondsPerDay)); + Value = (long)doubleTicks; + } + public DateTimeInt64(DateTime dateTime) { Value = DateTimeToTicks(dateTime); @@ -209,6 +228,16 @@ namespace Sunny.UI return dt.DateTime; } + public static implicit operator double(DateTimeInt64 dtValue) + { + return dtValue.DoubleValue; + } + + public static implicit operator DateTimeInt64(double ticks) + { + return new DateTimeInt64(ticks); + } + public static bool operator ==(DateTimeInt64 dtValue1, DateTimeInt64 dtValue2) { return dtValue1.Value == dtValue2.Value; @@ -339,5 +368,13 @@ namespace Sunny.UI public string TimeString => DateTime.TimeString(); public string HourMinuteString => DateTime.ToString("HH:mm"); + + public int Year => DateTime.Year; + public int Month => DateTime.Month; + public int Day => DateTime.Day; + public int Hour => DateTime.Hour; + public int Minute => DateTime.Minute; + public int Second => DateTime.Second; + public int Millisecond => DateTime.Millisecond; } } \ No newline at end of file