Выбор привлекательной линейной шкалы для оси Y графика - PullRequest
68 голосов
/ 29 ноября 2008

Я пишу немного кода для отображения гистограммы (или линии) в нашем программном обеспечении. Все идет хорошо. То, что меня озадачило, - это обозначение оси Y.

Вызывающий может сказать мне, насколько точно они хотят, чтобы шкала Y была помечена, но я, кажется, застрял на том, что именно нужно пометить их «привлекательным» способом. Я не могу описать «привлекательный», и, вероятно, вы не можете, но мы знаем это, когда видим это, верно?

Так что, если точки данных:

   15, 234, 140, 65, 90

И пользователь запрашивает 10 меток на оси Y, и мы получаем немного бумаги с карандашом:

  0, 25, 50, 75, 100, 125, 150, 175, 200, 225, 250

Таким образом, там 10 (не включая 0), последний простирается чуть выше самого высокого значения (234 <250), и это «хороший» шаг 25 каждый. Если бы они попросили 8 ярлыков, прирост 30 выглядел бы неплохо: </p>

  0, 30, 60, 90, 120, 150, 180, 210, 240

Девять было бы сложно. Может быть, просто использовали 8 или 10 и позвоните достаточно близко, все будет в порядке. А что делать, если некоторые из точек отрицательны?

Я вижу, что Excel прекрасно справляется с этой проблемой.

Кто-нибудь знает алгоритм общего назначения (даже грубая сила в порядке) для решения этой проблемы? Мне не нужно делать это быстро, но это должно выглядеть красиво.

Ответы [ 12 ]

0 голосов
/ 21 декабря 2012

На основе алгоритма @ Gamecat я создал следующий класс помощников

public struct Interval
{
    public readonly double Min, Max, TickRange;

    public static Interval Find(double min, double max, int tickCount, double padding = 0.05)
    {
        double range = max - min;
        max += range*padding;
        min -= range*padding;

        var attempts = new List<Interval>();
        for (int i = tickCount; i > tickCount / 2; --i)
            attempts.Add(new Interval(min, max, i));

        return attempts.MinBy(a => a.Max - a.Min);
    }

    private Interval(double min, double max, int tickCount)
    {
        var candidates = (min <= 0 && max >= 0 && tickCount <= 8) ? new[] {2, 2.5, 3, 4, 5, 7.5, 10} : new[] {2, 2.5, 5, 10};

        double unroundedTickSize = (max - min) / (tickCount - 1);
        double x = Math.Ceiling(Math.Log10(unroundedTickSize) - 1);
        double pow10X = Math.Pow(10, x);
        TickRange = RoundUp(unroundedTickSize/pow10X, candidates) * pow10X;
        Min = TickRange * Math.Floor(min / TickRange);
        Max = TickRange * Math.Ceiling(max / TickRange);
    }

    // 1 < scaled <= 10
    private static double RoundUp(double scaled, IEnumerable<double> candidates)
    {
        return candidates.First(candidate => scaled <= candidate);
    }
}
0 голосов
/ 12 июня 2009

Спасибо за вопрос и ответ, очень полезно. Gamecat, мне интересно, как вы определяете, к чему должен быть округлен диапазон тиков.

диапазон тиков = 21,9. Это должно быть 25.0

Чтобы алгоритмически сделать это, нужно было бы добавить логику в алгоритм выше, чтобы сделать эту шкалу хорошо для больших чисел? Например, при 10 тиках, если диапазон равен 3346, диапазон тиков оценивается до 334,6, а округление до ближайших 10 даст 340, когда 350, вероятно, лучше.

Что ты думаешь?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...