Отрисовка графиков в мобильном приложении

в 7:44, , рубрики: iOS, xcode, разработка под iOS, метки: ,

Возможно, что в этом посте будет мало чего-то нового для большинства читателей, но этот пост будет полезен новичкам.

image

Итак, я расскажу об отрисовке графиков в мобильном приложении.

Задача

Нам потребовалось сделать отображение графиков нагрузки на разные элементы VPS (RAM, CPU, HDD, Траффик). После раздумий было решено, что график должен:

  • При необходимости скроллится по горизонтали
  • Автоматически менять маштаб (в зависимости от максимальной величины)
  • Подбирать аннотации по вертикали
  • Уметь получить аннотации по горизонтали из массива и расставить под указанными точками
  • При необходимости заполнять цветом площадь под графиком

Если интересно, то добро пожаловать под кат.

Реализация

Сначала все шло, как по маслу. Быстро сделал отрисовку линии и внес эту линию в scroll view, сделал перевод из величины графика в пиксели, но тут начались проблемы. Первая из них — потребовалось округлять максимальную величину. Вроде, все просто, но встретились подводные камни, а именно — округление дробных чисел из-за того, что иногда максимальная величина иногда была меньше 0.5, то простое «round()» выводило ноль.

Решение нашлось примерно за час на основе этого:

float rounded = round(0.0556*1000)/1000
//rounded = 0.06

Мое решение (Тут не просто округление до десятых, а еще и в большую сторону):

- (float) roundNumber:(float)numberToRound {

    if (numberToRound == 0) {
        return numberToRound;
    }

    float multiplier = 10;
    float result = 0;

    while (result == 0) {
        result = (round(numberToRound*multiplier))/multiplier;
        if (result > 0) {
            if (result < numberToRound) {
                result = (round((numberToRound+5/(multiplier*10))*multiplier))/multiplier;
            }
        }
        multiplier = multiplier * 10;
    }
    return result;

}

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

Красивые числа было решено получать следующим образом: красивым считается число, которое, либо кратно степени 5, либо степени 10 и некоторые исключения.

А вот и решение в виде кода:

- (float) foundIntervalForMaximumValue:(float) maximumValueA withValueToPoints:(float)valueToPoints {
    float numberOfLines = 0;
    float currentPart = 0.000001;
    float result = 0;
    BOOL shouldDouble = NO;
    while (result == 0) {
        numberOfLines = maximumValue / currentPart;
        
        float numberOfLinesVisible = round((self.frame.size.height - 2*spaceForAnnotations - 30)/(currentPart*valueToPoints) + 0.5);
        
        if (numberOfLinesVisible > 0 && numberOfLinesVisible <= numberOfLinesWant) {
            result = currentPart;
        }
        
        NSLog(@"number of lines visible: %f with part: %f", numberOfLinesVisible, currentPart);
        
        if (shouldDouble) {
            currentPart = currentPart * 2;
            shouldDouble = NO;
        } else {
            currentPart = currentPart * 5;
            shouldDouble = YES;
        }
        if (currentPart > 1) {
            currentPart = roundf(currentPart);
        }
    }
    if (numberOfLines <= 2) {
        NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
        formatter.numberStyle = NSNumberFormatterDecimalStyle;
        NSString *valueStr = [formatter stringFromNumber:[NSNumber numberWithDouble:result]];
        if (valueStr.length > 0) {
            if ([[valueStr substringToIndex:1] intValue] == 5 || [[valueStr substringWithRange:NSMakeRange(valueStr.length-1, 1)] intValue] == 5) {
                result = result / 2;
                if (result > 1) {
                    result = round(result);
                }
            }
        }
        [formatter release];
    }
    return result;
    
}

NSNumberFormatter в конце используется для того, чтобы находить числа, которые оканчиваются на 5 и пробовать умножить их на два, для наилучшего результата.

На этом трудности кончились, дописал протоколы и с чистой совестью пошел праздновать первомай.

Результат

image
Скачать исходный код и прочитать описание можно по адресу graphview.unnamedd.com

Автор: itruf


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js