Лучший алгоритм для вычисления круглых значений из диапазона - PullRequest
4 голосов
/ 05 июля 2011

Я рисую график, где на горизонтальной оси есть время, а на вертикальной оси - цена.

Цена может варьироваться от 0,23487 до 0,8746 или от 20,47 до 45,48 или от 1,4578 до 1,6859 или от 9000 до 12000 ... Вы понимаете, что любой диапазон может быть там. Также точность чисел может отличаться (но обычно это 2 десятичных знака или 4 десятичных знака).

Теперь по вертикальной оси мне нужно показать цены, но не все из них, только некоторые значимые уровни. Мне нужно показать как можно больше значимых уровней, но эти уровни не должны быть ближе друг к другу, чем 30 пикселей (.

Так что, если у меня есть график с данными, цены которого колеблются от 1.4567 до 1.6789, а высота графика составляет 500, я могу показать максимум 16 значимых уровней. Диапазон видимых цен 1.6789-1.4567 = 0.2222. 0,2222 / 16 = 0,0138, чтобы я мог показать уровни 1.4716, 1.4854 и т. Д. Но я хочу округлить эти уровни до некоторого значимого числа, например 1.4600, 1.4700, 1.4800 ... или 1.4580, 1.4590, 1.4600 ... или 1.4580, 1.4585 ... и т. Д. Поэтому я хочу всегда показывать как можно больше уровней значимости в зависимости от того, сколько места у меня есть, но всегда показываю только уровни при некоторых значимых значениях (я не говорю, что округленные значения также значимы как 20,25), которые составляют 1, 2, 2,5, 5 и 10 или их множители (10, 20, 25 ... или 100, 200, 250 ... ) или их подразделения (0,1, 0,2, 0,25 ... или 0,0001, 0,0002, 0,00025 ...)

У меня это работает на самом деле, но мне совсем не нравится мой алгоритм, он слишком длинный и не элегантный. Я надеюсь, что кто-то может предложить более элегантный и общий способ. Я ищу алгоритм, я могу реализовать не необходимый код. Ниже мой текущий алогит в объективе-c. Спасибо.

-(float) getPriceLineDenominator
{
    NSArray *possVal = [NSArray arrayWithObjects:
                        [NSNumber numberWithFloat:1.0],
                        [NSNumber numberWithFloat:2.0],
                        [NSNumber numberWithFloat:2.5],
                        [NSNumber numberWithFloat:5.0],
                        [NSNumber numberWithFloat:10.0],
                        [NSNumber numberWithFloat:20.0],
                        [NSNumber numberWithFloat:25.0],
                        [NSNumber numberWithFloat:50.0],
                        [NSNumber numberWithFloat:100.0],
                        nil];

    float diff = highestPrice-lowestPrice;//range of shown values

    double multiplier = 1;
    if(diff<10)
    {
        while (diff<10) 
        {
            multiplier/=10;
            diff = diff*10;
        }
    }
    else
    {
        while (diff>100) 
        {
            multiplier*=10;
            diff = diff/10;
        }
    }

    float result = 0;
    for(NSNumber *n in possVal)
    {
        float f = [n floatValue]*multiplier;
        float x = [self priceY:highestPrice];
        float y = [self priceY:highestPrice-f];
        if((y-x)>=30)//30 is minimum distance between price levels shown
        {
            result = f;
            break;
        }
    }
    return result;
}

Ответы [ 2 ]

3 голосов
/ 05 июля 2011

Вы можете использовать логарифмы для определения размера каждого поддиапазона.

Допустим, вы знаете минимальные и максимальные значения в ваших данных.Вы также знаете, сколько уровней вы хотите.

Разница между максимумом и минимумом, деленная на количество уровней, (немного) меньше размера каждого поддиапазона

double diff = highestPrice - lowestPrice;     // range of shown values
double range = diff / levels;                 // size of range
double logrange = log10(range);               // log10
int lograngeint = (int)logrange;              // integer part
double lograngerest = logrange - lograngeint; // fractional part
if (lograngerest < 0) {                       // adjust if negative
    lograngerest += 1;
    lograngeint -= 1;
}

/* now you can increase lograngerest to the boundaries you like */
if (lograngerest < log10(2)) lograngerest = log10(2);
else if (lograngerest < log10(2.5)) lograngerest = log10(2.5);
else if (lograngerest < log10(5)) lograngerest = log10(5);
else lograngerest = /* log10(10) */ 1;

/* and the size of each range is therefore */
return pow(10, lograngeint + lograngerest);

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

1 голос
/ 05 июля 2011

Как вы говорите, доступная высота определяет максимальное количество делений.Для аргументации давайте избегать магических чисел и скажем, что у вас есть height пикселей и минимальный интервал closest:

int maxDivs = height / closest;

Разделите диапазон на это множество делений.Скорее всего, вы получите некрасивое значение, но оно обеспечивает отправную точку:

double minTickSpacing = diff/maxDivs;

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

double multiplier = pow(10, -floor(log10(minTickSpacing)));

Выберите следующий интервал из вашего диапазона {2, 2.5, 5, 10} - я просто собираюсь сделать это сконстанты и if - else для простоты:

double scaledSpacing = multiplier * minTickSpacing;
if ( scaledSpacing < 2 ) result = 2;
else if ( scaledSpacing < 2.5 ) result = 2.5;
else if ( scaledSpacing < 5 ) result = 5;
else result = 10;

return result/multiplier;

или что-то в этом роде.Полностью не проверено, поэтому вам нужно проверить знаки и диапазоны и тому подобное.И обязательно будут некоторые интересные крайние случаи.Но я думаю это должно быть в правильном поле ...

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