Nice Label Algorithm for Charts with minimum ticks

后端 未结 16 1923
清酒与你
清酒与你 2020-11-29 17:07

I need to calculate the Ticklabels and the Tickrange for charts manually.

I know the \"standard\" algorithm for nice ticks (see http://books.google.de/books?id=fvA7

相关标签:
16条回答
  • 2020-11-29 17:35

    I have converted above java code to Python as per my requirement.

     import math
    
      class NiceScale:
        def __init__(self, minv,maxv):
            self.maxTicks = 6
            self.tickSpacing = 0
            self.lst = 10
            self.niceMin = 0
            self.niceMax = 0
            self.minPoint = minv
            self.maxPoint = maxv
            self.calculate()
    
        def calculate(self):
            self.lst = self.niceNum(self.maxPoint - self.minPoint, False)
            self.tickSpacing = self.niceNum(self.lst / (self.maxTicks - 1), True)
            self.niceMin = math.floor(self.minPoint / self.tickSpacing) * self.tickSpacing
            self.niceMax = math.ceil(self.maxPoint / self.tickSpacing) * self.tickSpacing
    
        def niceNum(self, lst, rround):
            self.lst = lst
            exponent = 0 # exponent of range */
            fraction = 0 # fractional part of range */
            niceFraction = 0 # nice, rounded fraction */
    
            exponent = math.floor(math.log10(self.lst));
            fraction = self.lst / math.pow(10, exponent);
    
            if (self.lst):
                if (fraction < 1.5):
                    niceFraction = 1
                elif (fraction < 3):
                    niceFraction = 2
                elif (fraction < 7):
                    niceFraction = 5;
                else:
                    niceFraction = 10;
            else :
                if (fraction <= 1):
                    niceFraction = 1
                elif (fraction <= 2):
                    niceFraction = 2
                elif (fraction <= 5):
                    niceFraction = 5
                else:
                    niceFraction = 10
    
            return niceFraction * math.pow(10, exponent)
    
        def setMinMaxPoints(self, minPoint, maxPoint):
              self.minPoint = minPoint
              self.maxPoint = maxPoint
              self.calculate()
    
        def setMaxTicks(self, maxTicks):
            self.maxTicks = maxTicks;
            self.calculate()
    
    a=NiceScale(14024, 17756)
    print "a.lst ", a.lst
    print "a.maxPoint ", a.maxPoint
    print "a.maxTicks ", a.maxTicks
    print "a.minPoint ", a.minPoint
    print "a.niceMax ", a.niceMax
    print "a.niceMin ", a.niceMin
    print "a.tickSpacing ", a.tickSpacing
    
    0 讨论(0)
  • 2020-11-29 17:35

    This is the Swift version:

    class NiceScale {
        private var minPoint: Double
        private var maxPoint: Double
        private var maxTicks = 10
        private(set) var tickSpacing: Double = 0
        private(set) var range: Double = 0
        private(set) var niceMin: Double = 0
        private(set) var niceMax: Double = 0
    
        init(min: Double, max: Double) {
            minPoint = min
            maxPoint = max
            calculate()
        }
    
        func setMinMaxPoints(min: Double, max: Double) {
            minPoint = min
            maxPoint = max
            calculate()
        }
    
        private func calculate() {
            range = niceNum(maxPoint - minPoint, round: false)
            tickSpacing = niceNum(range / Double((maxTicks - 1)), round: true)
            niceMin = floor(minPoint / tickSpacing) * tickSpacing
            niceMax = floor(maxPoint / tickSpacing) * tickSpacing
        }
    
        private func niceNum(range: Double, round: Bool) -> Double {
            let exponent = floor(log10(range))
            let fraction = range / pow(10, exponent)
            let niceFraction: Double
    
            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 * pow(10, exponent)
        }
    }
    
    0 讨论(0)
  • 2020-11-29 17:35

    Here's the Kotlin version!

    import java.lang.Math.*
    
    /**
     * Instantiates a new instance of the NiceScale class.
     *
     * @param min Double The minimum data point.
     * @param max Double The maximum data point.
     */
    class NiceScale(private var minPoint: Double, private var maxPoint: Double) {
    
        private var maxTicks = 15.0
        private var range: Double = 0.0
        var niceMin: Double = 0.0
        var niceMax: Double = 0.0
        var tickSpacing: Double = 0.0
    
        init {
            calculate()
        }
    
        /**
         * Calculate and update values for tick spacing and nice
         * minimum and maximum data points on the axis.
         */
        private fun calculate() {
            range = niceNum(maxPoint - minPoint, false)
            tickSpacing = niceNum(range / (maxTicks - 1), true)
            niceMin = floor(minPoint / tickSpacing) * tickSpacing
            niceMax = ceil(maxPoint / tickSpacing) * tickSpacing
        }
    
        /**
         * Returns a "nice" number approximately equal to range. Rounds
         * the number if round = true. Takes the ceiling if round = false.
         *
         * @param range Double The data range.
         * @param round Boolean Whether to round the result.
         * @return Double A "nice" number to be used for the data range.
         */
        private fun niceNum(range: Double, round: Boolean): Double {
            /** Exponent of range  */
            val exponent: Double = floor(log10(range))
            /** Fractional part of range  */
            val fraction: Double
            /** Nice, rounded fraction  */
            val niceFraction: Double
    
            fraction = range / pow(10.0, exponent)
    
            niceFraction = if (round) {
                when {
                    fraction < 1.5 -> 1.0
                    fraction < 3 -> 2.0
                    fraction < 7 -> 5.0
                    else -> 10.0
                }
            } else {
                when {
                    fraction <= 1 -> 1.0
                    fraction <= 2 -> 2.0
                    fraction <= 5 -> 5.0
                    else -> 10.0
                }
            }
    
            return niceFraction * pow(10.0, exponent)
        }
    
        /**
         * Sets the minimum and maximum data points.
         *
         * @param minPoint Double The minimum data point.
         * @param maxPoint Double The maximum data point.
         */
        fun setMinMaxPoints(minPoint: Double, maxPoint: Double) {
            this.minPoint = minPoint
            this.maxPoint = maxPoint
            calculate()
        }
    
        /**
         * Sets maximum number of tick marks we're comfortable with.
         *
         * @param maxTicks Double The maximum number of tick marks.
         */
        fun setMaxTicks(maxTicks: Double) {
            this.maxTicks = maxTicks
            calculate()
        }
    }
    
    0 讨论(0)
  • 2020-11-29 17:36

    I am the author of "Algorithm for Optimal Scaling on a Chart Axis". It used to be hosted on trollop.org, but I have recently moved domains/blogging engines. Anyhow, I'll post the contents here for easier access.

    I've been working on an Android charting application for an assignment and ran into a bit of an issue when it came to presenting the chart in a nicely scaled format. I spent a some time trying to create this algorithm on my own and came awfully close, but in the end I found a pseudo-code example in a book called "Graphics Gems, Volume 1" by Andrew S. Glassner. An excellent description of the problem is given in the chapter on "Nice Numbers for Graph Labels":

    When creating a graph by computer, it is desirable to label the x and y axes with "nice" numbers: simple decimal numbers. For example, if the data range is 105 to 543, we'd probably want to plot the range from 100 to 600 and put tick marks every 100 units. Or if the data range is 2.04 to 2.16, we'd probably plot a range from 2.00 to 2.20 with a tick spacing of 0.05. Humans are good at choosing such "nice" numbers, but simplistic algorithms are not. The naïve label-selection algorithm takes the data range and divides it into n equal intervals, but this usually results in ugly tick labels. We here describe a simple method for generating nice graph labels.

    The primary observation is that the "nicest" numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers. We will use only such numbers for the tick spacing, and place tick marks at multiples of the tick spacing...

    I used the pseudo-code example in this book to create the following class in Java:

    public class NiceScale {
    
      private double minPoint;
      private double maxPoint;
      private double maxTicks = 10;
      private double tickSpacing;
      private double range;
      private double niceMin;
      private double niceMax;
    
      /**
       * Instantiates a new instance of the NiceScale class.
       *
       * @param min the minimum data point on the axis
       * @param max the maximum data point on the axis
       */
      public NiceScale(double min, double max) {
        this.minPoint = min;
        this.maxPoint = max;
        calculate();
      }
    
      /**
       * Calculate and update values for tick spacing and nice
       * minimum and maximum data points on the axis.
       */
      private void calculate() {
        this.range = niceNum(maxPoint - minPoint, false);
        this.tickSpacing = niceNum(range / (maxTicks - 1), true);
        this.niceMin =
          Math.floor(minPoint / tickSpacing) * tickSpacing;
        this.niceMax =
          Math.ceil(maxPoint / tickSpacing) * tickSpacing;
      }
    
      /**
       * Returns a "nice" number approximately equal to range Rounds
       * the number if round = true Takes the ceiling if round = false.
       *
       * @param range the data range
       * @param round whether to round the result
       * @return a "nice" number to be used for the data range
       */
      private double niceNum(double range, boolean 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);
      }
    
      /**
       * Sets the minimum and maximum data points for the axis.
       *
       * @param minPoint the minimum data point on the axis
       * @param maxPoint the maximum data point on the axis
       */
      public void setMinMaxPoints(double minPoint, double maxPoint) {
        this.minPoint = minPoint;
        this.maxPoint = maxPoint;
        calculate();
      }
    
      /**
       * Sets maximum number of tick marks we're comfortable with
       *
       * @param maxTicks the maximum number of tick marks for the axis
       */
      public void setMaxTicks(double maxTicks) {
        this.maxTicks = maxTicks;
        calculate();
      }
    }
    

    We can then make use of the above code like this:

    NiceScale numScale = new NiceScale(-0.085, 0.173);
    
    System.out.println("Tick Spacing:\t" + numScale.getTickSpacing());
    System.out.println("Nice Minimum:\t" + numScale.getNiceMin());
    System.out.println("Nice Maximum:\t" + numScale.getNiceMax());
    

    Which will then output nicely formatted numbers for use in whatever application for which you need to create pretty scales. =D

    Tick Spacing: 0.05
    Nice Minimum: -0.1
    Nice Maximum: 0.2
    
    0 讨论(0)
  • 2020-11-29 17:36

    Since everybody and his dog is posting a translation to other popular languages, here is my version for the Nimrod programming language. I also added handling of cases where the amount of ticks is less than two:

    import math, strutils
    
    const
      defaultMaxTicks = 10
    
    type NiceScale = object
      minPoint: float
      maxPoint: float
      maxTicks: int
      tickSpacing: float
      niceMin: float
      niceMax: float
    
    proc ff(x: float): string =
      result = x.formatFloat(ffDecimal, 3)
    
    proc `$`*(x: NiceScale): string =
      result = "Input minPoint: " & x.minPoint.ff &
        "\nInput maxPoint: " & x.maxPoint.ff &
        "\nInput maxTicks: " & $x.maxTicks &
        "\nOutput niceMin: " & x.niceMin.ff &
        "\nOutput niceMax: " & x.niceMax.ff &
        "\nOutput tickSpacing: " & x.tickSpacing.ff &
        "\n"
    
    proc calculate*(x: var NiceScale)
    
    proc init*(x: var NiceScale; minPoint, maxPoint: float;
        maxTicks = defaultMaxTicks) =
      x.minPoint = minPoint
      x.maxPoint = maxPoint
      x.maxTicks = maxTicks
      x.calculate
    
    proc initScale*(minPoint, maxPoint: float;
        maxTicks = defaultMaxTicks): NiceScale =
      result.init(minPoint, maxPoint, maxTicks)
    
    proc niceNum(scaleRange: float; doRound: bool): float =
      var
        exponent: float ## Exponent of scaleRange.
        fraction: float ## Fractional part of scaleRange.
        niceFraction: float ## Nice, rounded fraction.
    
      exponent = floor(log10(scaleRange));
      fraction = scaleRange / pow(10, exponent);
    
      if doRound:
        if fraction < 1.5:
          niceFraction = 1
        elif fraction < 3:
          niceFraction = 2
        elif fraction < 7:
          niceFraction = 5
        else:
          niceFraction = 10
      else:
        if fraction <= 1:
          niceFraction = 1
        elif fraction <= 2:
          niceFraction = 2
        elif fraction <= 5:
          niceFraction = 5
        else:
          niceFraction = 10
    
      return niceFraction * pow(10, exponent)
    
    proc calculate*(x: var NiceScale) =
      assert x.maxPoint > x.minPoint, "Wrong input range!"
      assert x.maxTicks >= 0, "Sorry, can't have imaginary ticks!"
      let scaleRange = niceNum(x.maxPoint - x.minPoint, false)
      if x.maxTicks < 2:
        x.niceMin = floor(x.minPoint)
        x.niceMax = ceil(x.maxPoint)
        x.tickSpacing = (x.niceMax - x.niceMin) /
          (if x.maxTicks == 1: 2.0 else: 1.0)
      else:
        x.tickSpacing = niceNum(scaleRange / (float(x.maxTicks - 1)), true)
        x.niceMin = floor(x.minPoint / x.tickSpacing) * x.tickSpacing
        x.niceMax = ceil(x.maxPoint / x.tickSpacing) * x.tickSpacing
    
    when isMainModule:
      var s = initScale(57.2, 103.3)
      echo s
    

    This is the comment stripped version. Full one can be read at GitHub integrated into my project.

    0 讨论(0)
  • 2020-11-29 17:37

    Here'a better organized C# code.

    public class NiceScale
    {
    
        public double NiceMin { get; set; }
        public double NiceMax { get; set; }
        public double TickSpacing { get; private set; }
    
        private double _minPoint;
        private double _maxPoint;
        private double _maxTicks = 5;
        private double _range;
    
        /**
         * Instantiates a new instance of the NiceScale class.
         *
         * @param min the minimum data point on the axis
         * @param max the maximum data point on the axis
         */
        public NiceScale(double min, double max)
        {
            _minPoint = min;
            _maxPoint = max;
            Calculate();
        }
    
        /**
         * Calculate and update values for tick spacing and nice
         * minimum and maximum data points on the axis.
         */
        private void Calculate()
        {
            _range = NiceNum(_maxPoint - _minPoint, false);
            TickSpacing = NiceNum(_range / (_maxTicks - 1), true);
            NiceMin = Math.Floor(_minPoint / TickSpacing) * TickSpacing;
            NiceMax = Math.Ceiling(_maxPoint / TickSpacing) * TickSpacing;
        }
    
        /**
         * Returns a "nice" number approximately equal to range Rounds
         * the number if round = true Takes the ceiling if round = false.
         *
         * @param range the data range
         * @param round whether to round the result
         * @return a "nice" number to be used for the data range
         */
        private 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);
        }
    
        /**
         * Sets the minimum and maximum data points for the axis.
         *
         * @param minPoint the minimum data point on the axis
         * @param maxPoint the maximum data point on the axis
         */
        public void SetMinMaxPoints(double minPoint, double maxPoint)
        {
            _minPoint = minPoint;
            _maxPoint = maxPoint;
            Calculate();
        }
    
        /**
         * Sets maximum number of tick marks we're comfortable with
         *
         * @param maxTicks the maximum number of tick marks for the axis
         */
        public void SetMaxTicks(double maxTicks)
        {
            _maxTicks = maxTicks;
            Calculate();
        }
    }
    
    0 讨论(0)
提交回复
热议问题