+ UIScale:增加坐标轴刻度计算类
This commit is contained in:
parent
ef3896c50b
commit
26ce6ff7af
Binary file not shown.
BIN
Bin/SunnyUI.dll
BIN
Bin/SunnyUI.dll
Binary file not shown.
BIN
Bin/SunnyUI.pdb
BIN
Bin/SunnyUI.pdb
Binary file not shown.
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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:
|
||||
|
@ -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()
|
||||
{
|
||||
//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());
|
||||
}
|
||||
}
|
||||
if (LineOption.XAxisType == UIAxisType.DateTime)
|
||||
XScale = new UIDateScale();
|
||||
else
|
||||
XScale = new UILinearScale();
|
||||
|
||||
YScale = new UILinearScale();
|
||||
|
||||
//Y轴
|
||||
{
|
||||
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;
|
||||
if (!LineOption.YAxis.MaxAuto) max = LineOption.YAxis.Max;
|
||||
if (!LineOption.YAxis.MinAuto) min = LineOption.YAxis.Min;
|
||||
|
||||
if ((max - min).IsZero())
|
||||
{
|
||||
max = 100;
|
||||
min = 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();
|
||||
}
|
||||
|
||||
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,56 +147,38 @@ 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);
|
||||
|
||||
SizeF sf = g.MeasureString(label, SubFont);
|
||||
g.DrawString(label, SubFont, ChartStyle.ForeColor, x - sf.Width / 2.0f, DrawOrigin.Y + LineOption.XAxis.AxisTick.Length);
|
||||
}
|
||||
|
||||
if (x.Equals(DrawOrigin.X)) continue;
|
||||
if (x.Equals(DrawOrigin.X + DrawSize.Width)) continue;
|
||||
|
||||
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);
|
||||
g.DrawLine(pn, x, DrawOrigin.Y, x, LineOption.Grid.Top);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
g.DrawLine(ChartStyle.ForeColor, start, DrawOrigin.Y, start, LineOption.Grid.Top);
|
||||
}
|
||||
|
||||
start += DrawBarWidth;
|
||||
}
|
||||
}
|
||||
|
||||
//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;
|
||||
}
|
||||
|
||||
SizeF sfname = g.MeasureString(LineOption.XAxis.Name, SubFont);
|
||||
g.DrawString(LineOption.XAxis.Name, SubFont, ChartStyle.ForeColor,
|
||||
@ -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++)
|
||||
float[] ylabels = YScale.CalcYPixels(YLabels, DrawOrigin.Y, DrawSize.Height);
|
||||
float wmax = 0;
|
||||
for (int i = 0; i < ylabels.Length; i++)
|
||||
{
|
||||
g.DrawLine(ChartStyle.ForeColor, DrawOrigin.X, start, DrawOrigin.X - LineOption.YAxis.AxisTick.Length, start);
|
||||
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;
|
||||
|
||||
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;
|
||||
g.DrawLine(pn, DrawOrigin.X, y, Width - LineOption.Grid.Right, y);
|
||||
}
|
||||
}
|
||||
|
||||
//Y Label
|
||||
if (LineOption.YAxis.AxisLabel.Show)
|
||||
{
|
||||
float start = DrawOrigin.Y;
|
||||
float DrawBarHeight = DrawSize.Height * 1.0f / (YAxisEnd - YAxisStart);
|
||||
int idx = 0;
|
||||
float wmax = 0;
|
||||
for (int i = YAxisStart; i <= YAxisEnd; 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;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
if (YScale == null) return;
|
||||
|
||||
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);
|
||||
|
||||
if (LineOption.GreaterWarningArea == null && LineOption.LessWarningArea == null)
|
||||
{
|
||||
using (Pen pen = new Pen(color, series.Width))
|
||||
{
|
||||
g.SetHighQuality();
|
||||
@ -319,16 +243,23 @@ namespace Sunny.UI
|
||||
g.DrawLines(pen, series.Points.ToArray());
|
||||
g.SetDefaultQuality();
|
||||
}
|
||||
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Bitmap bmp = new Bitmap(Width, Height);
|
||||
Bitmap bmpGreater;
|
||||
Bitmap bmpLess;
|
||||
|
||||
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))
|
||||
{
|
||||
Graphics graphics = bmp.Graphics();
|
||||
@ -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));
|
||||
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))
|
||||
{
|
||||
|
@ -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);
|
||||
|
@ -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
560
SunnyUI/Charts/UIScale.cs
Normal 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
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user