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