Статьи

Создание графика с помощью Quartz 2D: часть 4

В этой серии статей я обсуждаю создание диаграмм и графиков, используя не что иное, как Quartz 2D, API рендеринга графики, созданный Apple, который является частью Core Graphics. Возможно, вы захотите набрать скорость с части 1 , часть 2 и часть 3 .

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

В этой части серии, давайте посмотрим, как включить этот вид интерактивности. Мы оставим текст для рисования на графике для заключительной части серии.

Включение интерактивности

Прежде всего, давайте немного реорганизовать существующий код. Мы собираемся переключаться между гистограммой и линейным графиком. Может показаться удобным создать два разных проекта для двух разных графиков, и я думаю, что некоторые читатели уже сделали это. Для меня удобнее хранить весь код в одном проекте. Я просто перенесу код для рисования гистограммы в отдельный метод, расположенный прямо над drawRect :

 - (void)drawBarGraphWithContext:(CGContextRef)ctx { // Draw the bars float maxBarHeight = kGraphHeight - kBarTop - kOffsetY; for (int i = 0; i < sizeof(data); i++) { float barX = kOffsetX + kStepX + i * kStepX - kBarWidth / 2; float barY = kBarTop + maxBarHeight - maxBarHeight * data[i]; float barHeight = maxBarHeight * data[i]; CGRect barRect = CGRectMake(barX, barY, kBarWidth, barHeight); [self drawBar:barRect context:ctx]; } } 

Теперь замените в drawRect весь код, который вошел в новый метод, одной строкой. Это должно быть прямо рядом с линией, которая вызывает метод для рисования линейного графика. Комментируя одну из этих строк, мы сможем легко переключаться между различными типами графиков.

 [self drawBarGraphWithContext:context]; 

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

Обнаружение баров постучал

Подход довольно прост. При рисовании полос мы создаем прямоугольники и заполняем их градиентами. Если нам удастся сохранить эти прямоугольники и сохранить их рядом, мы сможем проверить, находятся ли координаты касания внутри одного из них.

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

 #define kNumberOfBars 12 

Добавьте следующую строку кода в GraphView.m вне методов:

 CGRect touchAreas[kNumberOfBars]; 

Также измените условие цикла for в drawBarGraphWithContext :

 for (int i = 0; i < kNumberOfBars; i++) 

Наконец, в самом конце этого цикла, сохраните прямоугольник для бара в новый массив:

 touchAreas[i] = barRect; 

Теперь, когда рисование графика завершено, мы будем иметь в своем распоряжении массив прямоугольников, которые использовались для рисования баров. Следующий шаг — перехватить прикосновения пользователя и определить их местоположение. Для этого нам нужно добавить следующий метод:

 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; NSLog(@"Touch x:%f, y:%f", point.x, point.y); } 

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

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

 for (int i = 0; i < kNumberOfBars; i++) { if (CGRectContainsPoint(touchAreas[i], point)) { NSLog(@"Tapped a bar with index %d, value %f", i, data[i]); break; } } 

Теперь, если вы нажмете внутри одной из полос, вы увидите логирование, похожее на следующее, с другой стороны, если вы нажмете где-то за пределами полос, вы должны увидеть только сенсорное сообщение:

 MyGraph[1265:b303] Touch x:212.000000, y:126.000000 MyGraph[1265:b303] Tapped a bar with index 2, value 0.900000 

Очевидно, что вместо регистрации сообщения о касании панели вы можете отобразить представление с меткой и установить для текста этой метки значение панели или сделать что-то еще, соответствующее вашему приложению. Поскольку это не будет зависеть от Quartz 2D, я оставляю вам детальную реализацию.

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

Рисование линии указателя

Первым подходом, который я попробовал, было просто добавить код для рисования вертикальной линии в конец метода drawRect и запустить его после касания графика. Чтобы сделать линию видимой, я должен был запросить перерисовку всего графика после каждого касания. Здесь я заметил, что график стал немного вялым, перерисовывая линии, градиенты и метки снова и снова. Очевидно, что это не было приемлемым решением.

Альтернативным подходом было бы перерисовать не весь граф, а только его ограниченную область. Однако я чувствовал себя слишком ленивым, чтобы сделать это, и, наконец, нашел решение, которое я считаю простым и приятным. Идея состоит в том, чтобы поместить другое прозрачное представление поверх GraphView , назовем его PointerView , и обрабатывать прикосновения и рисовать указатель только в этом представлении, оставляя GraphView какой он есть. Давайте углубимся в то, как я это сделал.

Выберите MyGraphViewController.xib , затем перетащите View из библиотеки и GraphView его поверх GraphView . Возможно, вам придется внести некоторые коррективы здесь. Сначала убедитесь, что новое View находится на том же уровне иерархии объектов, что и GraphView . Чтобы достичь этого, перетащите новый вид поверх вида Scroll View прямо в дереве объектов. Во-вторых, установите координаты x и y вида на 0 . На следующем снимке экрана показано, каким должен быть конечный результат ваших манипуляций.

Quartz 2D Part 4 Figure 1

фигура 1

Затем добавьте в ваш проект новый класс Objective-C и сделайте его подклассом UIView . Я назвал это PointerView . Вернитесь к MyGraphViewController.xib , выберите недавно добавленный View и измените его класс на PointerView . Вы также можете изменить его имя в дереве объектов на представление Pointer View .

Пока мы здесь, давайте изменим цвет фона PointerView на прозрачный. Для этого нажмите на элемент управления для выбора цвета фона в свойствах PointerView и перетащите ползунок Opacity влево, насколько это возможно:

Quartz 2D Part 4 Figure 2

фигура 2

Если вы сейчас запускаете приложение и не забываете переключаться в режим линейного графика, комментируя / раскомментируя соответствующие строки кода, оно должно работать и выглядеть точно так же, как и раньше. В PointerView.m раскомментируйте метод drawRect и добавьте метод для обработки касаний:

 - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; } 

Ваша домашняя работа — это логика для определения ближайшей точки данных и проведения через нее линии указателя. Здесь мы просто нарисуем вертикальную линию в точке касания графика. Объявите пару переменных в PointerView.h :

 #import <UIKit/UIKit.h> @interface PointerView : UIView { float pointerX; BOOL drawPointer; } @end 

Первый будет хранить координату x последнего касания, а второй — флаг, который сообщит, что можно нарисовать линию указателя. Теперь мы можем завершить метод touchesBegan , добавив в него следующие строки:

 pointerX = point.x; drawPointer = YES; [self setNeedsDisplay]; 

Последняя строка отмечает PointerView для перерисовки. Теперь у нас есть вся информация, необходимая для рисования указателя, поэтому вот код рисования:

 - (void)drawRect:(CGRect)rect { if (drawPointer) { CGContextRef context = UIGraphicsGetCurrentContext(); CGRect frame = self.frame; CGContextSetLineWidth(context, 2.0); CGContextSetStrokeColorWithColor(context, [[UIColor colorWithRed:0.4 green:0.8 blue:0.4 alpha:1.0] CGColor]); CGContextMoveToPoint(context, pointerX, 0); CGContextAddLineToPoint(context, pointerX, frame.size.height); CGContextStrokePath(context); } } 

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

Quartz 2D Part 4 Figure 3

Рисунок 3

Обратите внимание, что производительность не пострадает, потому что после прикосновения мы будем только перерисовывать линию указателя. Единственный важный элемент, который отсутствует в наших графиках, — это некоторая текстовая информация. Мы собираемся научиться рисовать этот текст с помощью Quartz 2D в следующей и последней части этой серии. Будьте на связи.

Кварц 2D Индекс

Серия Александра Колесникова «Создание графика с использованием Quartz 2D» была разбита на 5 частей. Вы можете сослаться на серию, используя Quartz 2D Tag, и получить доступ к отдельным статьям, используя ссылки ниже.