Nice Label Algorithm for Charts with minimum ticks

后端 未结 16 1904
清酒与你
清酒与你 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:21

    Here is a javascript version:

    var minPoint;
    var maxPoint;
    var maxTicks = 10;
    var tickSpacing;
    var range;
    var niceMin;
    var niceMax;
    
    /**
     * Instantiates a new instance of the NiceScale class.
     *
     *  min the minimum data point on the axis
     *  max the maximum data point on the axis
     */
    function niceScale( min, max) {
        minPoint = min;
        maxPoint = max;
        calculate();
        return {
            tickSpacing: tickSpacing,
            niceMinimum: niceMin,
            niceMaximum: niceMax
        };
    }
    
    
    
    /**
     * Calculate and update values for tick spacing and nice
     * minimum and maximum data points on the axis.
     */
    function calculate() {
        range = niceNum(maxPoint - minPoint, false);
        tickSpacing = niceNum(range / (maxTicks - 1), true);
        niceMin =
          Math.floor(minPoint / tickSpacing) * tickSpacing;
        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.
     *
     *  localRange the data range
     *  round whether to round the result
     *  a "nice" number to be used for the data range
     */
    function niceNum( localRange,  round) {
        var exponent; /** exponent of localRange */
        var fraction; /** fractional part of localRange */
        var niceFraction; /** nice, rounded fraction */
    
        exponent = Math.floor(Math.log10(localRange));
        fraction = localRange / 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.
     *
     *  minPoint the minimum data point on the axis
     *  maxPoint the maximum data point on the axis
     */
    function setMinMaxPoints( localMinPoint,  localMaxPoint) {
        minPoint = localMinPoint;
        maxPoint = localMaxoint;
        calculate();
    }
    
    /**
     * Sets maximum number of tick marks we're comfortable with
     *
     *  maxTicks the maximum number of tick marks for the axis
     */
    function setMaxTicks(localMaxTicks) {
        maxTicks = localMaxTicks;
        calculate();
    }
    

    Enjoy!

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

    Here it is in TypeScript!

    /**
     * Calculate and update values for tick spacing and nice
     * minimum and maximum data points on the axis.
     */
    function calculateTicks(maxTicks: number, minPoint: number, maxPoint: number): [number, number, number] {
        let range = niceNum(maxPoint - minPoint, false);
        let tickSpacing = niceNum(range / (maxTicks - 1), true);
        let niceMin = Math.floor(minPoint / tickSpacing) * tickSpacing;
        let niceMax = Math.ceil(maxPoint / tickSpacing) * tickSpacing;
        let tickCount = range / tickSpacing;
        return [tickCount, niceMin, niceMax];
    }
    
    /**
     * 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
     */
    function niceNum(range: number, round: boolean): number {
        let exponent: number;
        /** exponent of range */
        let fraction: number;
        /** fractional part of range */
        let niceFraction: number;
        /** 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);
    }
    
    0 讨论(0)
  • 2020-11-29 17:25

    Dart / Flutter Version:

    import 'dart:math';
    
    void main() {
      double min = 3, max = 28;
    
      var scale = NiceScale(min, max, 5);
    
      print("Range: $min-$max; Max Point: ${scale.niceMax}; Min Point: ${scale.niceMin}; Steps: ${scale.tickSpacing};");
    }
    
    class NiceScale {
      double _niceMin, _niceMax;
      double _tickSpacing;
    
      double get tickSpacing { return _tickSpacing; }
      double get niceMin{ return _niceMin; }
      double get niceMax{ return _niceMax; }
    
      double _minPoint, _maxPoint;
      double _maxTicks;
      double _range;
    
      NiceScale(double minP, double maxP, double maxTicks){
        this._minPoint = minP;
        this._maxPoint = maxP;
        this._maxTicks = maxTicks;
        _calculate();
      }
    
      void _calculate(){
        _range = _niceNum(_maxPoint - _minPoint, false);
        _tickSpacing = _niceNum(_range / (_maxTicks - 1), true);
        _niceMin = _calcMin();
        _niceMax = _calcMax();
      }
    
      double _calcMin() {
        int floored = (_minPoint / _tickSpacing).floor();
        return floored * _tickSpacing;
      }
    
      double _calcMax() {
        int ceiled = (_maxPoint / _tickSpacing).ceil();
        return ceiled * _tickSpacing;
      }
    
      double _niceNum(double range, bool round){
        double exponent; /** exponent of range */
        double fraction; /** fractional part of range */
        double niceFraction; /** nice, rounded fraction */
    
        exponent = (log(range)/ln10).floor().toDouble();
        fraction = range / 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 * pow(10, exponent);
      }
    }
    
    0 讨论(0)
  • 2020-11-29 17:30

    Here is the same thing in Objective C

    YFRNiceScale.h

    #import <Foundation/Foundation.h>
    
    @interface YFRNiceScale : NSObject
    
    @property (nonatomic, readonly) CGFloat minPoint;
    @property (nonatomic, readonly) CGFloat maxPoint;
    @property (nonatomic, readonly) CGFloat maxTicks;
    @property (nonatomic, readonly) CGFloat tickSpacing;
    @property (nonatomic, readonly) CGFloat range;
    @property (nonatomic, readonly) CGFloat niceRange;
    @property (nonatomic, readonly) CGFloat niceMin;
    @property (nonatomic, readonly) CGFloat niceMax;
    
    
    - (id) initWithMin: (CGFloat) min andMax: (CGFloat) max;
    - (id) initWithNSMin: (NSDecimalNumber*) min andNSMax: (NSDecimalNumber*) max;
    
    @end
    

    YFRNiceScale.m

    #import "YFRNiceScale.h"
    
    @implementation YFRNiceScale
    
    @synthesize minPoint = _minPoint;
    @synthesize maxPoint = _maxPoint;
    @synthesize maxTicks = _maxTicks;
    @synthesize tickSpacing = _tickSpacing;
    @synthesize range = _range;
    @synthesize niceRange = _niceRange;
    @synthesize niceMin = _niceMin;
    @synthesize niceMax = _niceMax;
    
    - (id)init {
        self = [super init];
        if (self) {
    
        }
        return self;
    }
    
    - (id) initWithMin: (CGFloat) min andMax: (CGFloat) max {
    
        if (self) {
            _maxTicks = 10;
            _minPoint = min;
            _maxPoint = max;
            [self calculate];
        }
        return [self init];
    }
    
    - (id) initWithNSMin: (NSDecimalNumber*) min andNSMax: (NSDecimalNumber*) max {
    
        if (self) {
            _maxTicks = 10;
            _minPoint = [min doubleValue];
            _maxPoint = [max doubleValue];
            [self calculate];
        }
        return [self init];
    }
    
    
    /**
     * Calculate and update values for tick spacing and nice minimum and maximum
     * data points on the axis.
     */
    
    - (void) calculate {
        _range = [self niceNumRange: (_maxPoint-_minPoint) roundResult:NO];
        _tickSpacing = [self niceNumRange: (_range / (_maxTicks - 1)) roundResult:YES];
        _niceMin = floor(_minPoint / _tickSpacing) * _tickSpacing;
        _niceMax = ceil(_maxPoint / _tickSpacing) * _tickSpacing;
    
        _niceRange = _niceMax - _niceMin;
    }
    
    
    /**
     * 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
     */
    - (CGFloat) niceNumRange:(CGFloat) aRange roundResult:(BOOL) round {
        CGFloat exponent;
        CGFloat fraction;
        CGFloat niceFraction;
    
        exponent = floor(log10(aRange));
        fraction = aRange / 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 = 2;
            } else {
                niceFraction = 10;
            }
        }
    
        return niceFraction * pow(10, exponent);
    }
    
    - (NSString*) description {
        return [NSString stringWithFormat:@"NiceScale [minPoint=%.2f, maxPoint=%.2f, maxTicks=%.2f, tickSpacing=%.2f, range=%.2f, niceMin=%.2f, niceMax=%.2f]", _minPoint, _maxPoint, _maxTicks, _tickSpacing, _range, _niceMin, _niceMax ];
    }
    
    @end
    

    Usage:

    YFRNiceScale* niceScale = [[YFRNiceScale alloc] initWithMin:0 andMax:500];
    NSLog(@"Nice: %@", niceScale);
    
    0 讨论(0)
  • 2020-11-29 17:31

    Here's the C++ version. As a bonus you get a function that returns the minimum number of decimal points to display the tick labels on the axis.

    The header file:

    class NiceScale 
    {   public:
    
        float minPoint;
        float maxPoint;
        float maxTicks;
        float tickSpacing;
        float range;
        float niceMin;
        float niceMax;
    
        public:
        NiceScale()
        {   maxTicks = 10;
        }
    
        /**
        * 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
        */
        NiceScale(float min, float max) 
        {   minPoint = min;
            maxPoint = max;
            calculate();
        }
    
        /**
        * Calculate and update values for tick spacing and nice
        * minimum and maximum data points on the axis.
        */
        void calculate() ;
    
        /**
        * 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
        */
        float niceNum(float range, boolean round) ;
    
        /**
        * 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
        */
        void setMinMaxPoints(float minPoint, float maxPoint) ;
    
        /**
        * Sets maximum number of tick marks we're comfortable with
        *
        * @param maxTicks the maximum number of tick marks for the axis
        */
        void setMaxTicks(float maxTicks) ;
        int decimals(void);
    };
    

    And the CPP file:

    /**
    * Calculate and update values for tick spacing and nice
    * minimum and maximum data points on the axis.
    */
    void NiceScale::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 the data range
    * @param round whether to round the result
    * @return a "nice" number to be used for the data range
    */
    float NiceScale::niceNum(float range, boolean round) 
    {   float exponent; /** exponent of range */
        float fraction; /** fractional part of range */
        float niceFraction; /** nice, rounded fraction */
    
        exponent = floor(log10(range));
        fraction = range / pow(10.f, 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 * 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
    */
    void NiceScale::setMinMaxPoints(float minPoint, float 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
    */
    void NiceScale::setMaxTicks(float maxTicks) 
    {
        this->maxTicks = maxTicks;
        calculate();
    }
    
    // minimum number of decimals in tick labels
    // use in sprintf statement:
    // sprintf(buf, "%.*f", decimals(), tickValue);
    int NiceScale::decimals(void)
    {
        float logTickX = log10(tickSpacing);
        if(logTickX >= 0)
            return 0;
        return (int)(abs(floor(logTickX)));
    }
    
    0 讨论(0)
  • 2020-11-29 17:34

    You should be able to use the Java implementation with minor corrections.

    Change maxticks to 5.

    Change the calculate mehod to this:

    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 = this.niceMin + tickSpacing * (maxticks - 1); // Always display maxticks
        }
    

    Disclaimer: Note that I haven't tested this, so you may have to tweak it to make it look good. My suggested solution adds extra space at the top of the chart to always make room for 5 ticks. This may look ugly in some cases.

    0 讨论(0)
提交回复
热议问题