SunnyUI/SunnyUI/Charts/UIScale.cs
2022-01-05 21:57:47 +08:00

620 lines
22 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/******************************************************************************
* SunnyUI 开源控件库、工具类库、扩展类库、多页面开发框架。
* CopyRight (C) 2012-2022 ShenYongHua(沈永华).
* QQ群56829229 QQ17612584 EMailSunnyUI@QQ.Com
*
* Blog: https://www.cnblogs.com/yhuse
* Gitee: https://gitee.com/yhuse/SunnyUI
* GitHub: https://github.com/yhuse/SunnyUI
*
* SunnyUI.dll can be used for free under the GPL-3.0 license.
* If you use this code, please keep this note.
* 如果您使用此代码,请保留此说明。
******************************************************************************
* 文件名称: UIScale.cs
* 文件说明: 坐标轴刻度计算类
* 当前版本: V3.1
* 创建日期: 2020-10-01
*
* 2020-10-01: V2.2.8 完成曲线图表坐标轴刻度计算类
******************************************************************************/
using System;
using System.Globalization;
namespace Sunny.UI
{
public abstract class UIScale
{
protected double _rangeMin;
protected double _rangeMax;
protected bool _minAuto = true;
protected bool _maxAuto = true;
protected double _min, _max;
protected string _format;
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 double CalcXPos(double value, int origin, int width)
{
return (float)(_min + (value - origin) * (_max - _min) * 1.0f / width);
}
public float CalcYPixel(double value, int origin, int height)
{
return origin - (float)((value - _min) * 1.0f * height / (_max - _min));
}
public double CalcYPos(double value, int origin, int height)
{
return (float)(_min + (origin - value) * (_max - _min) * 1.0f / height);
}
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++)
{
if (labels[i].IsInfinity() || labels[i].IsNan())
result[i] = float.NaN;
else
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++)
{
if (labels[i].IsInfinity() || labels[i].IsNan())
result[i] = float.NaN;
else
result[i] = CalcYPixel(labels[i], origin, height);
}
return result;
}
public int Mag
{
get => _mag;
set { _mag = value; MagAuto = false; }
}
public bool MagAuto { get; set; } = true;
public double MinGrace { get; set; } = 0.1;
public double MaxGrace { get; set; } = 0.1;
public bool FormatAuto { get; set; } = true;
public string Format
{
get => _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 (double.IsInfinity(minVal) || double.IsNaN(minVal) || minVal.Equals(double.MinValue))
minVal = 0.0;
if (double.IsInfinity(maxVal) || double.IsNaN(maxVal) || maxVal.Equals(double.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 readonly double ZeroLever = 0.25;
public override void AxisChange()
{
base.AxisChange();
if (_max - _min < 1.0e-30)
{
if (_maxAuto) _max += 0.2 * (_max == 0 ? 1.0 : Math.Abs(_max));
if (_minAuto) _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 -= 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 += 0.2 * (_max == 0 ? 1.0 : Math.Abs(_max));
if (_minAuto) _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
{
DateTime dt = new DateTime(year, 1, 1, 0, 0, 0);
return new DateTimeInt64(dt.AddYears(direction)).DoubleValue;
}
case UIDateScaleLevel.Month:
if (direction == 1 && day == 1 && hour == 0 && minute == 0 && second == 0)
{
return date;
}
else
{
DateTime dt = new DateTime(year, month, 1, 0, 0, 0);
return new DateTimeInt64(dt.AddMonths(direction)).DoubleValue;
}
case UIDateScaleLevel.Day:
if (direction == 1 && hour == 0 && minute == 0 && second == 0)
{
return date;
}
else
{
DateTime dt = new DateTime(year, month, day, 0, 0, 0);
return new DateTimeInt64(dt.AddDays(direction)).DoubleValue;
}
case UIDateScaleLevel.Hour:
if (direction == 1 && minute == 0 && second == 0)
{
return date;
}
else
{
DateTime dt = new DateTime(year, month, day, hour, 0, 0);
return new DateTimeInt64(dt.AddHours(direction)).DoubleValue;
}
case UIDateScaleLevel.Minute:
if (direction == 1 && second == 0)
{
return date;
}
else
{
DateTime dt = new DateTime(year, month, day, hour, minute, 0);
return new DateTimeInt64(dt.AddMinutes(direction)).DoubleValue;
}
case UIDateScaleLevel.Second:
{
DateTime dt = new DateTime(year, month, day, hour, minute, second);
return new DateTimeInt64(dt.AddSeconds(direction)).DoubleValue;
}
case UIDateScaleLevel.Millisecond:
{
DateTime dt = new DateTime(year, month, day, hour, minute, second, millisecond);
return new DateTimeInt64(dt.AddMilliseconds(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);
*/
switch (_scaleLevel)
{
default: xlDateNew.AddYears(1); break;
case UIDateScaleLevel.Month: xlDateNew.AddMonths(1); break;
case UIDateScaleLevel.Day: xlDateNew.AddDays(1); break;
case UIDateScaleLevel.Hour: xlDateNew.AddHours(1); break;
case UIDateScaleLevel.Minute: xlDateNew.AddMinutes(1); break;
case UIDateScaleLevel.Second: xlDateNew.AddSeconds(1); break;
case UIDateScaleLevel.Millisecond: xlDateNew.AddMilliseconds(1); break;
}
}
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
}
}