Algorithm for “nice” grid line intervals on a graph

前端 未结 14 1235
长发绾君心
长发绾君心 2020-11-28 01:45

I need a reasonably smart algorithm to come up with \"nice\" grid lines for a graph (chart).

For example, assume a bar chart with values of 10, 30, 72 and 60. You k

相关标签:
14条回答
  • 2020-11-28 02:14

    In R, use

    tickSize <- function(range,minCount){
        logMaxTick <- log10(range/minCount)
        exponent <- floor(logMaxTick)
        mantissa <- 10^(logMaxTick-exponent)
        af <- c(1,2,5) # allowed factors
        mantissa <- af[findInterval(mantissa,af)]
        return(mantissa*10^exponent)
    }
    

    where range argument is max-min of domain.

    0 讨论(0)
  • 2020-11-28 02:18

    Here is a javascript function I wrote to round grid intervals (max-min)/gridLinesNumber to beautiful values. It works with any numbers, see the gist with detailed commets to find out how it works and how to call it.

    var ceilAbs = function(num, to, bias) {
      if (to == undefined) to = [-2, -5, -10]
      if (bias == undefined) bias = 0
      var numAbs = Math.abs(num) - bias
      var exp = Math.floor( Math.log10(numAbs) )
    
        if (typeof to == 'number') {
            return Math.sign(num) * to * Math.ceil(numAbs/to) + bias
        }
    
      var mults = to.filter(function(value) {return value > 0})
      to = to.filter(function(value) {return value < 0}).map(Math.abs)
      var m = Math.abs(numAbs) * Math.pow(10, -exp)
      var mRounded = Infinity
    
      for (var i=0; i<mults.length; i++) {
        var candidate = mults[i] * Math.ceil(m / mults[i])
        if (candidate < mRounded)
          mRounded = candidate
      }
      for (var i=0; i<to.length; i++) {
        if (to[i] >= m && to[i] < mRounded)
          mRounded = to[i]
      }
      return Math.sign(num) * mRounded * Math.pow(10, exp) + bias
    }
    

    Calling ceilAbs(number, [0.5]) for different numbers will round numbers like that:

    301573431.1193228 -> 350000000
    14127.786597236991 -> 15000
    -63105746.17236853 -> -65000000
    -718854.2201183736 -> -750000
    -700660.340487957 -> -750000
    0.055717507097870114 -> 0.06
    0.0008068701205775142 -> 0.00085
    -8.66660070605576 -> -9
    -400.09256079792976 -> -450
    0.0011740548815578223 -> 0.0015
    -5.3003294346854085e-8 -> -6e-8
    -0.00005815960629843176 -> -0.00006
    -742465964.5184875 -> -750000000
    -81289225.90985894 -> -85000000
    0.000901771713513881 -> 0.00095
    -652726598.5496342 -> -700000000
    -0.6498901364393532 -> -0.65
    0.9978325804695487 -> 1
    5409.4078950583935 -> 5500
    26906671.095639467 -> 30000000
    

    Check out the fiddle to experiment with the code. Code in the answer, the gist and the fiddle is slightly different I'm using the one given in the answer.

    0 讨论(0)
  • 2020-11-28 02:20

    Here's another implementation in JavaScript:

    var calcStepSize = function(range, targetSteps)
    {
      // calculate an initial guess at step size
      var tempStep = range / targetSteps;
    
      // get the magnitude of the step size
      var mag = Math.floor(Math.log(tempStep) / Math.LN10);
      var magPow = Math.pow(10, mag);
    
      // calculate most significant digit of the new step size
      var magMsd = Math.round(tempStep / magPow + 0.5);
    
      // promote the MSD to either 1, 2, or 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;
    };
    
    0 讨论(0)
  • 2020-11-28 02:24

    Taken from Mark above, a slightly more complete Util class in c#. That also calculates a suitable first and last tick.

    public  class AxisAssists
    {
        public double Tick { get; private set; }
    
        public AxisAssists(double aTick)
        {
            Tick = aTick;
        }
        public AxisAssists(double range, int mostticks)
        {
            var minimum = range / mostticks;
            var magnitude = Math.Pow(10.0, (Math.Floor(Math.Log(minimum) / Math.Log(10))));
            var residual = minimum / magnitude;
            if (residual > 5)
            {
                Tick = 10 * magnitude;
            }
            else if (residual > 2)
            {
                Tick = 5 * magnitude;
            }
            else if (residual > 1)
            {
                Tick = 2 * magnitude;
            }
            else
            {
                Tick = magnitude;
            }
        }
    
        public double GetClosestTickBelow(double v)
        {
            return Tick* Math.Floor(v / Tick);
        }
        public double GetClosestTickAbove(double v)
        {
            return Tick * Math.Ceiling(v / Tick);
        }
    }
    

    With ability to create an instance, but if you just want calculate and throw it away:

        double tickX = new AxisAssists(aMaxX - aMinX, 8).Tick;
    
    0 讨论(0)
  • 2020-11-28 02:25

    Using a lot of inspiration from answers already availible here, here's my implementation in C. Note that there's some extendibility built into the ndex array.

    float findNiceDelta(float maxvalue, int count)
    {
        float step = maxvalue/count,
             order = powf(10, floorf(log10(step))),
             delta = (int)(step/order + 0.5);
    
        static float ndex[] = {1, 1.5, 2, 2.5, 5, 10};
        static int ndexLenght = sizeof(ndex)/sizeof(float);
        for(int i = ndexLenght - 2; i > 0; --i)
            if(delta > ndex[i]) return ndex[i + 1] * order;
        return delta*order;
    }
    
    0 讨论(0)
  • 2020-11-28 02:26

    CPAN provides an implementation here (see source link)

    See also Tickmark algorithm for a graph axis

    FYI, with your sample data:

    • Maple: Min=8, Max=74, Labels=10,20,..,60,70, Ticks=10,12,14,..70,72
    • MATLAB: Min=10, Max=80, Labels=10,20,,..,60,80
    0 讨论(0)
提交回复
热议问题