So I am revising XAML and I wanted to write a graph control. The first problem I encountered is how to pick nice numbers for your graph axis, the algorithm is not obvious and I had to google before I found a popular answer on Stack Overflow.
The original given code was quite classy when it needn't be given that we are not holding state. I think a functional implementation is more appropriate and so I have rewritten the C# code a class with static members only. I have abolished the class member variables and instead I am returning all the facts (short for artefacts) found in the function via an object array. I have been influenced by Python of late with its capability to return tuples but I have avoided the Tuple<> generic class because I didn't want to have to specify all the types ( I appreciate there is a small performance hot with the boxing).
using System;
namespace GraphIntervalTest
{
class Program
{
static void Main(string[] args)
{
object[] facts = NiceScaleStatic.CalculateNiceScale(1.2813, 1.331, 8);
System.Console.Out.WriteLine("Tick Spacing:\t" + facts[0]);
System.Console.Out.WriteLine("Nice Minimum:\t" + facts[1]);
System.Console.Out.WriteLine("Nice Maximum:\t" + facts[2]);
System.Console.Out.WriteLine("Tick Count:\t" + facts[3]);
System.Console.Out.WriteLine("Min:\t" + facts[4]);
System.Console.Out.WriteLine("Max:\t" + facts[5]);
System.Console.Out.WriteLine("MaxTicks:\t" + facts[6]);
}
}
public class NiceScaleStatic
{
public static object[] CalculateNiceScale(double min, double max, long maxTicks = 10)
{
double tickSpacing;
double range;
double niceMin;
double niceMax;
long tickCount;
range = niceNum(max - min, false);
tickSpacing = niceNum(range / (maxTicks - 1), true);
niceMin = Math.Floor(min / tickSpacing) * tickSpacing;
niceMax = Math.Ceiling(max / tickSpacing) * tickSpacing;
tickCount = (long)((niceMax - niceMin) / tickSpacing);
return new object[] { tickSpacing, niceMin, niceMax, tickCount, min, max, maxTicks };
}
static double niceNum(double range, bool round)
{
double exponent; /** exponent of range */
double fraction; /** fractional part of range */
double niceFraction; /** nice, rounded fraction */
exponent = Math.Floor(Math.Log10(range));
fraction = range / Math.Pow(10, exponent);
if (round)
{
if (fraction < 1.5)
niceFraction = 1;
else if (fraction < 3)
niceFraction = 2;
else if (fraction < 7)
niceFraction = 5;
else
niceFraction = 10;
}
else
{
if (fraction <= 1)
niceFraction = 1;
else if (fraction <= 2)
niceFraction = 2;
else if (fraction <= 5)
niceFraction = 5;
else
niceFraction = 10;
}
return niceFraction * Math.Pow(10, exponent);
}
}
}