Хороший алгоритм меток для графиков с минимальным количеством тиков

Мне нужно вручную вычислить метки Ticklabels и Tickrange для диаграмм.

Я знаю "стандартный" алгоритм хороших тиков (см. http://books.google.de/books?id=fvA7zLEFWZgC&pg=PA61&lpg=PA61&redir_esc=y#v=onepage&q&f=false ) и Я также знаю эту реализацию Java .

Проблема в том, что с этим алгоритмом тики "слишком умны". Это означает, что алгоритм решает, сколько тиков должно отображаться. Мое требование: что всегда есть 5 тиков, но они, конечно, должны быть "красивыми". Наивный подход заключался бы в том, чтобы получить максимальное значение, разделить на 5 и умножить на число тиков. Значения здесь - конечно - не оптимальные, и тики довольно уродливы.

Кто-нибудь знает решение проблемы или имеет подсказку для формального описания алгоритма?

27
задан thodorisbais 27 October 2016 в 13:52
поделиться

7 ответов

Вы должны быть в состоянии использовать реализацию Java с небольшими исправлениями.

Изменить максимальные значения на 5.

Измените метод расчета следующим образом:

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
    }

Отказ от ответственности: обратите внимание, что я не проверял это, поэтому вам, возможно, придется настроить его, чтобы он выглядел хорошо. Мое предлагаемое решение добавляет дополнительное пространство в верхней части графика, чтобы всегда было место для 5 тиков. Это может выглядеть уродливо в некоторых случаях.

4
ответ дан 28 November 2019 в 04:13
поделиться

Здесь лучше организован код C #.

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
ответ дан 28 November 2019 в 04:13
поделиться

Вот версия Kotlin!

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()
    }
}
1
ответ дан 28 November 2019 в 04:13
поделиться

Это версия Swift:

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)
    }
}
1
ответ дан 28 November 2019 в 04:13
поделиться

Мне нужно было преобразовать этот алгоритм в C #, так что вот он ...

public static class NiceScale {

    public static void Calculate(double min, double max, int maxTicks, out double range, out double tickSpacing, out double niceMin, out double niceMax) {
        range = niceNum(max - min, false);
        tickSpacing = niceNum(range / (maxTicks - 1), true);
        niceMin = Math.Floor(min / tickSpacing) * tickSpacing;
        niceMax = Math.Ceiling(max / tickSpacing) * tickSpacing;
    }

    private static double niceNum(double range, bool round) {
        double pow = Math.Pow(10, Math.Floor(Math.Log10(range)));
        double fraction = range / pow;

        double niceFraction;
        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;
    }

}
2
ответ дан 28 November 2019 в 04:13
поделиться

Здесь то же самое в 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

Использование:

YFRNiceScale* niceScale = [[YFRNiceScale alloc] initWithMin:0 andMax:500];
NSLog(@"Nice: %@", niceScale);
2
ответ дан 28 November 2019 в 04:13
поделиться

Поскольку каждый и его собака публикуют перевод на другие популярные языки, вот моя версия языка программирования Nimrod . Я также добавил обработку случаев, когда количество тиков меньше двух:

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 `

Поскольку каждый и его собака публикуют перевод на другие популярные языки, вот моя версия языка программирования Nimrod . Я также добавил обработку случаев, когда количество тиков меньше двух:

*(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

Это версия с комментариями. Полный текст можно прочитать на GitHub , интегрированном в мой проект.

1
ответ дан 28 November 2019 в 04:13
поделиться
Другие вопросы по тегам:

Похожие вопросы: