Алгоритм отметки для оси графика - PullRequest
23 голосов
/ 26 октября 2008

Я ищу алгоритм, который размещает отметки на оси, учитывая отображаемый диапазон, ширину для отображения и функцию для измерения ширины строки для отметки.

Например, учитывая, что мне нужно отображать от 1e-6 до 5e-6 и ширину для отображения в пикселях, алгоритм будет определять, что я должен ставить отметки (например) на 1e-6, 2e-6, 3e-6, 4e-6 и 5e-6. Учитывая меньшую ширину, он может решить, что оптимальное размещение будет только в четных позициях, то есть 2e-6 и 4e-6 (так как установка большего количества отметок может привести к их перекрытию).

Умный алгоритм будет отдавать предпочтение меткам с кратностью 10, 5 и 2. Кроме того, умный алгоритм будет симметричным относительно нуля.

Ответы [ 5 ]

6 голосов
/ 19 апреля 2018

Поскольку мне не нравилось ни одно из найденных мной решений, я реализовал собственное. Он на C #, но его легко перевести на любой другой язык.

Он в основном выбирает из списка возможных шагов наименьший, который отображает все значения, , не оставляя значения точно по краю , позволяет вам легко выбирать, какие возможные шаги вы хотите использовать (без необходимости редактировать уродливые if-else if блоки) и поддерживает любой диапазон значений. Я использовал C # Tuple, чтобы вернуть три значения просто для быстрой и простой демонстрации.

private static Tuple<decimal, decimal, decimal> GetScaleDetails(decimal min, decimal max)
{
    // Minimal increment to avoid round extreme values to be on the edge of the chart
    decimal epsilon = (max - min) / 1e6m;
    max += epsilon;
    min -= epsilon;
    decimal range = max - min;

    // Target number of values to be displayed on the Y axis (it may be less)
    int stepCount = 20;
    // First approximation
    decimal roughStep = range / (stepCount - 1);

    // Set best step for the range
    decimal[] goodNormalizedSteps = { 1, 1.5m, 2, 2.5m, 5, 7.5m, 10 }; // keep the 10 at the end
    // Or use these if you prefer:  { 1, 2, 5, 10 };

    // Normalize rough step to find the normalized one that fits best
    decimal stepPower = (decimal)Math.Pow(10, -Math.Floor(Math.Log10((double)Math.Abs(roughStep))));
    var normalizedStep = roughStep * stepPower;
    var goodNormalizedStep = goodNormalizedSteps.First(n => n >= normalizedStep);
    decimal step = goodNormalizedStep / stepPower;

    // Determine the scale limits based on the chosen step.
    decimal scaleMax = Math.Ceiling(max / step) * step;
    decimal scaleMin = Math.Floor(min / step) * step;

    return new Tuple<decimal, decimal, decimal>(scaleMin, scaleMax, step);
}

static void Main()
{
    // Dummy code to show a usage example.
    var minimumValue = data.Min();
    var maximumValue = data.Max();
    var results = GetScaleDetails(minimumValue, maximumValue);
    chart.YAxis.MinValue = results.Item1;
    chart.YAxis.MaxValue = results.Item2;
    chart.YAxis.Step = results.Item3;
}
2 голосов
/ 27 октября 2008

Возьмите самый длинный из сегментов около нуля (или весь график, если ноль не находится в диапазоне) - например, если у вас есть что-то в диапазоне [-5, 1], возьмите [-5,0] .

Приблизительно выясните, как долго будет длиться этот сегмент в тиках. Это просто деление длины на ширину галочки. Итак, предположим, что метод говорит, что мы можем поставить 11 тиков от -5 до 0. Это наша верхняя граница. Для более короткой стороны мы просто отразим результат на более длинной стороне.

Теперь попробуйте ввести как можно больше (до 11) тиков, чтобы маркер для каждого тика имел вид i * 10 * 10 ^ n, i * 5 * 10 ^ n, i * 2 * 10 ^ n, где n - целое число, а i - индекс тика. Теперь это проблема оптимизации - мы хотим максимизировать количество тиков, которые мы можем вставить, и в то же время минимизировать расстояние между последним тиком и концом результата. Поэтому присвойте балл, чтобы получить как можно больше тиков, меньше нашей верхней границы, и назначьте балл, чтобы последний тик был близок к n - вам придется экспериментировать здесь.

В приведенном выше примере попробуйте n = 1. Мы получаем 1 тик (при i = 0). n = 2 дает нам 1 тик, и мы дальше от нижней границы, поэтому мы знаем, что должны идти другим путем. n = 0 дает нам 6 тиков в каждой целочисленной точке. n = -1 дает нам 12 тиков (0, -0,5, ..., -5,0). n = -2 дает нам 24 тика и так далее. Алгоритм оценки даст каждому из них балл - чем выше, тем лучше метод.

Сделайте это снова для i * 5 * 10 ^ n и i * 2 * 10 ^ n, и возьмите тот, у которого лучший результат.

(в качестве примера алгоритма подсчета скажем, что счет - это расстояние до последнего такта, умноженное на максимальное количество тактов минус необходимое количество. Это, вероятно, будет плохо, но послужит хорошей отправной точкой).

0 голосов
/ 04 марта 2017

Этот простой алгоритм дает интервал, кратный 1, 2 или 5 раз степени 10. И диапазон оси делится по крайней мере на 5 интервалов. Пример кода на языке Java:

protected double calculateInterval(double range) {
    double x = Math.pow(10.0, Math.floor(Math.log10(range)));
    if (range / x >= 5)
        return x;
    else if (range / (x / 2.0) >= 5)
        return x / 2.0;
    else
        return x / 5.0;
}

Это альтернатива, для минимальных 10 интервалов:

protected double calculateInterval(double range) {
    double x = Math.pow(10.0, Math.floor(Math.log10(range)));
    if (range / (x / 2.0) >= 10)
        return x / 2.0;
    else if (range / (x / 5.0) >= 10)
        return x / 5.0;
    else
        return x / 10.0;
}
0 голосов
/ 27 октября 2008

какой у вас язык разработки? У меня есть элемент управления графиком в C ++, который легко решает эту проблему, используя комбинацию логарифма, celings и т. Д. Если хотите, можете объяснить код для вас.

0 голосов
/ 27 октября 2008

Я использую библиотеку графов jQuery flot . Это открытый исходный код и довольно хорошо выполняет генерацию осей / тиков. Я бы посоветовал взглянуть на его код и оттуда извлечь некоторые идеи.

...