245 lines
7 KiB
C#
245 lines
7 KiB
C#
namespace JD.Sequencers.Times
|
|
{
|
|
/// <summary>
|
|
/// Helper class to help calculate ideal Minor and Major intervals (number labels and ticks)
|
|
/// inside a range on the screen.
|
|
/// </summary>
|
|
public class Intervals
|
|
{
|
|
#region Variables
|
|
|
|
private static readonly int[] m_idealMultiples = new [] { 1, 2, 5 };
|
|
|
|
private int m_pixelWidth;
|
|
private double m_unitWidth;
|
|
|
|
private int m_minorThreshold = 5;
|
|
private int m_majorThreshold = 30;
|
|
|
|
private double m_minor;
|
|
private double m_major;
|
|
|
|
private bool m_dirty = true;
|
|
|
|
#endregion
|
|
|
|
#region Constructor
|
|
|
|
/// <summary>
|
|
/// Default Constructor.
|
|
/// </summary>
|
|
public Intervals()
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="pixelWidth">The width in pixels in which to fit the UnitWidth range.</param>
|
|
/// <param name="unitWidth">The width in units that are to be fitted within PixelWidth.</param>
|
|
public Intervals(int pixelWidth, double unitWidth)
|
|
{
|
|
PixelWidth = pixelWidth;
|
|
UnitWidth = unitWidth;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructor.
|
|
/// </summary>
|
|
/// <param name="pixelWidth">The width in pixels in which to fit the UnitWidth range.</param>
|
|
/// <param name="unitWidth">The width in units that are to be fitted within PixelWidth.</param>
|
|
/// <param name="minorThreshold">The minimum number of pixels between minor ticks.</param>
|
|
/// <param name="majorThreshold">The minimum number of pixels reserved around a major interval.</param>
|
|
public Intervals(int pixelWidth, double unitWidth, int minorThreshold, int majorThreshold)
|
|
: this(pixelWidth, unitWidth)
|
|
{
|
|
MinorThreshold = minorThreshold;
|
|
MajorThreshold = majorThreshold;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
/// <summary>
|
|
/// Gets or sets the width in pixels in which to fit the UnitWidth range.
|
|
/// </summary>
|
|
public int PixelWidth
|
|
{
|
|
get
|
|
{
|
|
return m_pixelWidth;
|
|
}
|
|
set
|
|
{
|
|
if (m_pixelWidth != value)
|
|
{
|
|
m_pixelWidth = System.Math.Max(0, value);
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the width in units that are to be fitted within PixelWidth.
|
|
/// </summary>
|
|
public double UnitWidth
|
|
{
|
|
get
|
|
{
|
|
return m_unitWidth;
|
|
}
|
|
set
|
|
{
|
|
if (m_unitWidth != value)
|
|
{
|
|
m_unitWidth = System.Math.Max(0, value);
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the minimum number of pixels between minor ticks. The default is 5 px.
|
|
/// </summary>
|
|
public int MinorThreshold
|
|
{
|
|
get
|
|
{
|
|
return m_minorThreshold;
|
|
}
|
|
set
|
|
{
|
|
if (m_minorThreshold != value)
|
|
{
|
|
m_minorThreshold = value;
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets or sets the minimum number of pixels reserved around a major interval. The default is 30 px.
|
|
/// </summary>
|
|
public int MajorThreshold
|
|
{
|
|
get
|
|
{
|
|
return m_majorThreshold;
|
|
}
|
|
set
|
|
{
|
|
if (m_majorThreshold != value)
|
|
{
|
|
m_majorThreshold = value;
|
|
m_dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current Minor interval (in units).
|
|
/// </summary>
|
|
public double Minor
|
|
{
|
|
get
|
|
{
|
|
CalculateIntervals();
|
|
return m_minor;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current Major interval (in units).
|
|
/// </summary>
|
|
public double Major
|
|
{
|
|
get
|
|
{
|
|
CalculateIntervals();
|
|
return m_major;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Gets the closest ideal interval according to the current scale.
|
|
/// </summary>
|
|
/// <param name="unitWidth"></param>
|
|
/// <param name="pixelWidth"></param>
|
|
/// <param name="pixelThreshold">The minimum amount of pixels between intervals.</param>
|
|
/// <returns></returns>
|
|
public virtual double GetIdealInterval(double unitWidth, double pixelWidth, double pixelThreshold)
|
|
{
|
|
if (unitWidth == 0 || pixelWidth == 0 || pixelThreshold == 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
double unitThreshold = (pixelThreshold * unitWidth) / pixelWidth;
|
|
int exponent = (int)System.Math.Floor(System.Math.Log10(unitThreshold));
|
|
|
|
do
|
|
{
|
|
double v = System.Math.Pow(10, exponent);
|
|
|
|
foreach (int idealMultiple in m_idealMultiples)
|
|
{
|
|
double v2 = v * idealMultiple;
|
|
double pixelV = (v2 * pixelWidth) / unitWidth;
|
|
if (pixelV >= pixelThreshold)
|
|
{
|
|
return v2;
|
|
}
|
|
}
|
|
|
|
exponent++;
|
|
} while (true);
|
|
}
|
|
|
|
private void CalculateIntervals()
|
|
{
|
|
if (!m_dirty)
|
|
{
|
|
return;
|
|
}
|
|
m_dirty = false;
|
|
|
|
System.Diagnostics.Debug.Assert(PixelWidth >= 0 && UnitWidth >= 0);
|
|
if (PixelWidth == 0 || UnitWidth == 0)
|
|
{
|
|
m_minor = 0;
|
|
m_major = 0;
|
|
}
|
|
else
|
|
{
|
|
m_minor = GetIdealInterval(UnitWidth, PixelWidth, MinorThreshold);
|
|
m_major = GetIdealInterval(UnitWidth, PixelWidth, MajorThreshold);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the next ideal interval.
|
|
/// </summary>
|
|
/// <param name="unit"></param>
|
|
/// <param name="spacing">The maximum unit space between each interval.</param>
|
|
/// <returns></returns>
|
|
public static double GetNextInterval(double unit, double spacing)
|
|
{
|
|
int times = (int)System.Math.Floor(unit / spacing);
|
|
double rest = unit - times * spacing;
|
|
if (rest == 0)
|
|
{
|
|
return unit; // The unit is dead on an interval.
|
|
}
|
|
|
|
double result = unit + spacing - rest;
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|