+ UIScale:增加坐标轴刻度计算类

This commit is contained in:
Sunny 2020-10-10 22:57:59 +08:00
parent ef3896c50b
commit 26ce6ff7af
13 changed files with 876 additions and 356 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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;
}

View File

@ -42,6 +42,67 @@ namespace Sunny.UI
CalcData();
}
/// <summary>
/// 计算刻度
/// 起始值必须小于结束值
/// </summary>
/// <param name="start">起始值</param>
/// <param name="end">结束值</param>
/// <param name="expect_num">期望刻度数量,实际数接近此数</param>
/// <param name="degree_start">刻度起始值,须乘以间隔使用</param>
/// <param name="degree_end">刻度结束值,须乘以间隔使用</param>
/// <param name="degree_gap">刻度间隔</param>
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;

View File

@ -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;

View File

@ -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:

View File

@ -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))
{

View File

@ -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);

View File

@ -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
{
/// <summary>
/// 计算刻度
/// 起始值必须小于结束值
/// </summary>
/// <param name="start">起始值</param>
/// <param name="end">结束值</param>
/// <param name="expect_num">期望刻度数量,实际数接近此数</param>
/// <param name="degree_start">刻度起始值,须乘以间隔使用</param>
/// <param name="degree_end">刻度结束值,须乘以间隔使用</param>
/// <param name="degree_gap">刻度间隔</param>
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;
}
/// <summary>
/// 计算刻度
/// 起始值必须小于结束值
/// </summary>
/// <param name="start">起始值</param>
/// <param name="end">结束值</param>
/// <param name="expect_num">期望刻度数量,实际数接近此数</param>
/// <param name="degree_start">刻度起始值,须乘以间隔使用</param>
/// <param name="degree_end">刻度结束值,须乘以间隔使用</param>
/// <param name="degree_gap">刻度间隔</param>
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;
}
/// <summary>
/// 计算刻度
/// 起始值必须小于结束值
/// </summary>
/// <param name="start">起始值</param>
/// <param name="end">结束值</param>
/// <param name="expect_num">期望刻度数量,实际数接近此数</param>
/// <returns>刻度列表</returns>
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;
}
}
}

560
SunnyUI/Charts/UIScale.cs Normal file
View File

@ -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
}
}

View File

@ -71,6 +71,7 @@
<Compile Include="Charts\UILineChartOption.cs" />
<Compile Include="Charts\UIOption.cs" />
<Compile Include="Charts\UIPieChartOption.cs" />
<Compile Include="Charts\UIScale.cs" />
<Compile Include="Controls\Color\UIColorBar.cs">
<SubType>Component</SubType>
</Compile>

View File

@ -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
{
/// <summary>
/// 日期与长整形和浮点型互转
/// Value长整形为1970年1月1日0时起的毫秒数
/// DoubleValue双精度浮点数1970年1月1日0时起的天数
/// </summary>
public struct DateTimeInt64 : IComparable, IEquatable<DateTimeInt64>, IEquatable<long>, IEquatable<DateTime>
{
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;
/// <summary>
/// 返回当前时间的毫秒数, 这个毫秒其实就是自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); }
}
/// <summary>
/// 返回指定时间的毫秒数, 这个毫秒其实就是自1970年1月1日0时起的毫秒数
/// </summary>
@ -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;
}
}