Возможно, что в этом посте будет мало чего-то нового для большинства читателей, но этот пост будет полезен новичкам.
Итак, я расскажу об отрисовке графиков в мобильном приложении.
Задача
Нам потребовалось сделать отображение графиков нагрузки на разные элементы
- При необходимости скроллится по горизонтали
- Автоматически менять маштаб (в зависимости от максимальной величины)
- Подбирать аннотации по вертикали
- Уметь получить аннотации по горизонтали из массива и расставить под указанными точками
- При необходимости заполнять цветом площадь под графиком
Если интересно, то добро пожаловать под кат.
Реализация
Сначала все шло, как по маслу. Быстро сделал отрисовку линии и внес эту линию в 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 и пробовать умножить их на два, для наилучшего результата.
На этом трудности кончились, дописал протоколы и с чистой совестью пошел праздновать первомай.
Результат
Скачать исходный код и прочитать описание можно по адресу graphview.unnamedd.com
Автор: itruf