При работе с приложениями, интенсивно использующими данные, разработчик часто должен делать больше, чем просто показывать списки записей данных в табличном представлении. Библиотека CorePlot позволит вам добавить потрясающие визуализации данных в ваши приложения. Узнайте, как в этой серии Tuts + Premium!
Также доступно в этой серии:
- Работа с CorePlot: настройка проекта
- Работа с CorePlot: основы построения
- Работа с CorePlot: стилизация и добавление графиков
- Работа с CorePlot: создание гистограммы
- Работа с CorePlot: создание круговой диаграммы
Где мы остановились
В прошлый раз мы узнали, как создавать гистограмму, как настраивать цвета гистограммы и как добавлять собственные метки к нашей оси, если мы хотим отображать собственный текст вместо просто чисел.
Что мы расскажем сегодня
На этот раз мы рассмотрим, как абстрагировать логику из нашего контроллера в более повторно используемый объект источника данных. Как только мы освоим это, мы рассмотрим, как отображать те же данные, что и гистограмма, за исключением этого времени, когда они отображаются в виде круговой диаграммы!
Шаг 1: Настройка
Прежде чем мы начнем абстрагировать логику данных, мы собираемся создать наши базовые классы для круговой диаграммы. Как и в прошлый раз, нам нужно создать новый контроллер представления для графа. Давайте назовем это «STPieGraphViewController».
Обратите внимание, что нам не нужно создавать представление на этот раз, потому что мы сможем использовать ‘STGraphView’. Прежде чем приступить к настройке, давайте перейдем в STStudentListViewController.h и импортируем STPieGraphViewController.h. Нам также необходимо соответствовать протоколу STPieGraphViewControllerDelegate (который мы создадим позже):
1
|
@interface STStudentListViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, AddStudentViewControllerDelegate, UIActionSheetDelegate, STLineGraphViewControllerDelegate, STBarGraphViewControllerDelegate, STPieGraphViewControllerDelegate>
|
Переключитесь на файл .m. Нам нужно добавить кнопку в лист действий. Найдите graphButtonWasSelected: метод. Мы собираемся отредактировать текст второй кнопки и добавить третью:
1
2
3
4
5
6
|
— (void)graphButtonWasSelected:(id)sender
{
UIActionSheet *graphSelectionActionSheet = [[[UIActionSheet alloc] initWithTitle:@»Choose a graph» delegate:self cancelButtonTitle:@»Cancel» destructiveButtonTitle:nil otherButtonTitles:@»Enrolment over time», @»Subject totals — Bar», @»Subject totals — Pie», nil] autorelease];
[graphSelectionActionSheet showInView:[[UIApplication sharedApplication] keyWindow]];
}
|
Теперь перейдите в actionSheet: clickedButtonAtIndex: метод и добавьте предложение для buttonIndex == 2:
01
02
03
04
05
06
07
08
09
10
11
12
|
else if (buttonIndex == 2)
{
STPieGraphViewController *graphVC = [[STPieGraphViewController alloc] init];
[graphVC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[graphVC setModalPresentationStyle:UIModalPresentationFullScreen];
[graphVC setDelegate:self];
[graphVC setManagedObjectContext:[self managedObjectContext]];
[self presentModalViewController:graphVC animated:YES];
[graphVC release];
}
|
Как и в прошлый раз, он будет показывать некоторые предупреждения, потому что STPieGraphViewController не имеет делегата или свойства managedObjectContext.
Итак, перейдите в STPieGraphViewController.h. Импортируйте CorePlot-CocoaTouch.h и добавьте следующие свойства и объявления протокола:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
@protocol STPieGraphViewControllerDelegate
@required
— (void)doneButtonWasTapped:(id)sender;
@end
@interface STPieGraphViewController : UIViewController <CPTPieChartDelegate>
@property (nonatomic, strong) CPTGraph *graph;
@property (nonatomic, assign) id<STPieGraphViewControllerDelegate> delegate;
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
@end
|
Важно отметить, что на этот раз мы не соблюдаем CPTPieChartDataSource. Это потому, что мы будем абстрагировать логику графических данных из STBarGraphViewController в отдельный класс dataSource. Прежде чем мы сделаем это, давайте закончим настройку. В файле .m импортируйте STGraphView.h, синтезируйте свойства и реализуйте метод dealloc.
Наконец, настройте методы loadView и viewDidLoad, как показано ниже:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
— (void)loadView
{
[super loadView];
[self setTitle:@»Enrolment by subject»];
[self setView:[[[STGraphView alloc] initWithFrame:self.view.frame] autorelease]];
CPTTheme *defaultTheme = [CPTTheme themeNamed:kCPTPlainWhiteTheme];
[self setGraph:(CPTGraph *)[defaultTheme newGraph]];
}
— (void)viewDidLoad
{
[super viewDidLoad];
STGraphView *graphView = (STGraphView *)[self view];
[[graphView chartHostingView] setHostedGraph:[self graph]];
//Allow user to go back
UINavigationItem *navigationItem = [[[UINavigationItem alloc] initWithTitle:self.title] autorelease];
[navigationItem setHidesBackButton:YES];
UINavigationBar *navigationBar = [[[UINavigationBar alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 44.0f)] autorelease];
[navigationBar pushNavigationItem:navigationItem animated:NO];
[self.view addSubview:navigationBar];
[navigationItem setRightBarButtonItem:[[[UIBarButtonItem alloc] initWithTitle:@»Done»
style:UIBarButtonItemStyleDone
target:[self delegate]
action:@selector(doneButtonWasTapped:)] autorelease] animated:NO];
}
|
Выше уже должно быть знакомо. Итак, давайте рассмотрим абстрагирование графической логики в отдельный класс.
Шаг 2: Разделение графической логики
Мы уже написали логику для получения данных об общем количестве студентов по всем предметам; мы не хотим писать это снова, к счастью, нам не нужно. Все протоколы источников данных для графиков наследуются от ‘CPTPlotDataSource’, и именно этот протокол содержит методы numberOfRecordsForPlot: и numberForPlot: fieldEnum: recordIndex.
Создайте класс с именем «STAbstractSubjectDataSource.h» (унаследованный от NSObject) в новой группе «DataSource» в графической группе. Для файла заголовка импортируйте ‘CorePlot-CocoaTouch.h’ и поместите следующие свойства и объявления методов:
01
02
03
04
05
06
07
08
09
10
11
|
@interface STAbstractSubjectEnrollementDataSource : NSObject <CPTPlotDataSource>
@property (nonatomic, strong) NSManagedObjectContext *managedObjectContext;
— (id)initWithManagedObjectContext:(NSManagedObjectContext *)aManagedObjectContext;
— (float)getTotalSubjects;
— (float)getMaxEnrolled;
— (NSArray *)getSubjectTitlesAsArray;
@end
|
Мы подписываемся на протокол «CPTPlotDataSource». Мы создаем пользовательский метод init, который проходит через managedObjectContext, чтобы объект мог получить доступ к хранилищу данных. Наконец, есть три вспомогательных метода, которые могут помочь с получением информации о предметах и зачислении. Это те же самые методы, которые в настоящее время существуют в STBarGraphViewController. Мы собираемся переместить их в метод источника данных.
Помимо метода init, в файле .m нет нового кода, который вы раньше не видели. Это всего лишь вопрос перемещения всего существующего кода из STBarGraphViewController в объект dataSource. Методы, которые вы должны переместить:
- (поплавок) getTotalSubjects
- (С плавающей точкой) getMaxEnrolled
- (NSArray *) getSubjectTitlesAsArray
- (NSUInteger) numberOfRecordsForPlot: (CPTPlot *) график
- (NSNumber *) numberForPlot: (CPTPlot *) поле графика: (NSUInteger) fieldEnum recordIndex: (NSUInteger) индекс
Также убедитесь, что вы добавили в пользовательский метод init:
01
02
03
04
05
06
07
08
09
10
|
— (id)initWithManagedObjectContext:(NSManagedObjectContext *)aManagedObjectContext
{
self = [super init];
if (self)
{
[self setManagedObjectContext:aManagedObjectContext];
}
return self;
}
|
Теперь у нас есть объект источника данных, который может предоставить базовые данные для круговой диаграммы и гистограммы. Однако абстрактный источник данных не дает нам всего, что нам нужно, barFillForBarPlot: recordIndex не может быть реализован, поскольку он является частью CPTBarPlotDataSource. Нам нужно расширить наш абстрактный класс до чего-то определенного для гистограмм.
Создайте новый объект в группе «Источник данных» под названием «STBarGraphSubjectEnrollementDataSource», который расширяет наш абстрактный класс. В заголовке подпишитесь на ‘CPTBarPlotDataSource:
1
2
|
— (id)initWithManagedObjectContext:(NSManagedObjectContext *)aManagedObjectContext
@interface STBarGraphSubjectEnrollementDataSource : STAbstractSubjectEnrollementDataSource <CPTBarPlotDataSource>
|
А в файле .m реализуйте метод barFillForBarPlot:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
#pragma mark — CPTBarPlotDataSource Methods
-(CPTFill *)barFillForBarPlot:(CPTBarPlot *)barPlot recordIndex:(NSUInteger)index
{
CPTColor *areaColor = nil;
switch (index)
{
case 0:
areaColor = [CPTColor redColor];
break;
case 1:
areaColor = [CPTColor blueColor];
break;
case 2:
areaColor = [CPTColor orangeColor];
break;
case 3:
areaColor = [CPTColor greenColor];
break;
default:
areaColor = [CPTColor purpleColor];
break;
}
CPTFill *barFill = [CPTFill fillWithColor:areaColor];
return barFill;
}
|
Теперь вернитесь в файл заголовка STBarGraphViewControllers и импортируйте новый источник данных гистограммы. Теперь вы можете удалить подписку CPTBarPlotDataSource. Перейдите в файл .m и удалите все методы, кроме loadView, viewDidLoad и dealloc. Нам они больше не нужны.
Нам нужно сохранить указатель на источник данных и затем освободить указатель, когда представление будет сделано с ним. В закрытом интерфейсе объявите свойство и затем синтезируйте:
01
02
03
04
05
06
07
08
09
10
11
12
|
@interface STBarGraphViewController ()
@property (nonatomic, retain) STBarGraphSubjectEnrollementDataSource *barGraphDataSource;
@end
@implementation STBarGraphViewController
@synthesize delegate;
@synthesize managedObjectContext;
@synthesize graph;
@synthesize barGraphDataSource;
|
Убедитесь, что вы освобождаете его и в методе dealloc. Создайте новый экземпляр и установите его как наше свойство в методе loadView:
1
|
[self setBarGraphDataSource:[[[STBarGraphSubjectEnrollementDataSource alloc] initWithManagedObjectContext:[self managedObjectContext]] autorelease]];
|
Теперь в методе viewDidLoad нам нужно использовать вспомогательные методы нашего источника данных для расчета пространства графика и пользовательских меток:
1
|
CPTXYPlotSpace *studentPlotSpace = (CPTXYPlotSpace *)[graph defaultPlotSpace];
|
1
|
NSArray *subjectsArray = [[self barGraphDataSource] getSubjectTitlesAsArray];
|
Сохранить, собрать и запустить. Все должно работать так же, как и раньше. Если это произойдет, мы можем начать создавать нашу круговую диаграмму!
Шаг 3: Создание круговой диаграммы
Мы захотим создать конкретный источник данных для круговой диаграммы, как мы это делали для гистограммы, на случай, если нам понадобится реализовать какие-либо методы источников данных, специфичные для круговой диаграммы. Создайте класс с именем «STPieGraphSubjectEnrollementDataSource», который наследуется от «STAbstractSubjectEnrollementDataSource». В файле заголовка подпишитесь на протокол ‘CPTPieChartDataSource’. Мы не будем реализовывать какие-либо конкретные методы источника данных для круговой диаграммы, мы вернемся к этому позже.
Теперь, когда у нас есть источники данных, создать круговую диаграмму просто! Перейдите в STBPieGraphViewController.m и импортируйте объект источника данных круговой диаграммы. Объявите его как свойство в файле .m, как мы делали в прошлый раз:
01
02
03
04
05
06
07
08
09
10
11
12
|
@interface STPieGraphViewController ()
@property (nonatomic, strong) STPieGraphSubjectEnrollementDataSource *pieChartDataSource;
@end
@implementation STPieGraphViewController
@synthesize managedObjectContext;
@synthesize delegate;
@synthesize graph;
@synthesize pieChartDataSource;
|
Теперь создайте и установите его в loadView:
1
|
[self setPieChartDataSource:[[[STPieGraphSubjectEnrollementDataSource alloc] initWithManagedObjectContext:[self managedObjectContext]] autorelease]];
|
Наконец, в методе viewDidLoad нам нужно создать нашу круговую диаграмму, добавить ее в наш GraphView и удалить стандартную ось:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
STGraphView *graphView = (STGraphView *)[self view];
[[graphView chartHostingView] setHostedGraph:[self graph]];
CPTPieChart *pieChart = [[CPTPieChart alloc] initWithFrame:[graph bounds]];
[pieChart setPieRadius:100.00];
[pieChart setIdentifier:@»Subject»];
[pieChart setStartAngle:M_PI_4];
[pieChart setSliceDirection:CPTPieDirectionCounterClockwise];
[pieChart setDataSource:pieChartDataSource];
[graph addPlot:pieChart];
[graph setAxisSet:nil];
[graph setBorderLineStyle:nil];
|
Большинство из вышеперечисленного должно выглядеть знакомо. Обратите внимание, что он явно не называется «графиком», потому что он не зависит от оси x или оси y, но мы по-прежнему относимся к нему практически одинаково. Есть некоторые специфические вещи, которые мы делаем здесь. Мы создаем круговой радиус и начальный угол. Мы также устанавливаем направление среза. Наконец, мы устанавливаем ‘axisSet’ графа в ноль, чтобы мы не получили линии x и y.
И это должно быть все. Постройте и запустите, чтобы увидеть свою круговую диаграмму.
Это хорошо, но это может быть связано с каким-то указанием того, что представляет каждый цвет. Хороший способ сделать это — использовать легенды. Для этого мы создаем объект CPTLegend, который добавляем к нашему графику, и реализуем метод делегата, который возвращает соответствующий заголовок для легенды.
Давайте сначала создадим объект CPTLegend. В нашем методе viewDidLoad введите следующий код, где мы создаем нашу круговую диаграмму:
1
2
3
4
5
|
CPTLegend *theLegend = [CPTLegend legendWithGraph:[self graph]];
[theLegend setNumberOfColumns:2];
[[self graph] setLegend:theLegend];
[[self graph] setLegendAnchor:CPTRectAnchorBottom];
[[self graph] setLegendDisplacement:CGPointMake(0.0, 30.0)];
|
Это создает легенду и добавляет ее к нашему графическому объекту. Количество столбцов определяет, как будут выложены заголовки легенды. Затем мы устанавливаем некоторые атрибуты на объекте графика, которые определяют, где будет размещена легенда (внизу), и некоторое смещение, чтобы она полностью отображалась на виде.
Мы все еще должны предоставить легенду с названиями все же. Есть метод, специфичный для CPTPieChartDataSource, который позволяет нам это делать. Перейти к источнику данных круговой диаграммы и реализовать следующий код:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
#pragma mark — CPTPieChartDataSource
-(NSString *)legendTitleForPieChart:(CPTPieChart *)pieChart recordIndex:(NSUInteger)index
{
NSError *error = nil;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@»STSubject» inManagedObjectContext:[self managedObjectContext]];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@»subjectID == %d», index];
[request setEntity:entity];
[request setResultType:NSDictionaryResultType];
[request setPredicate:predicate];
[request setReturnsDistinctResults:NO];
[request setPropertiesToFetch:[NSArray arrayWithObject:@»subjectName»]];
NSArray *titleStrings = [[self managedObjectContext] executeFetchRequest:request error:&error];
NSDictionary *fetchedSubject = [titleStrings objectAtIndex:0];
return [fetchedSubject objectForKey:@»subjectName»];
}
|
Этот метод просто получает индекс легенды, получает заголовок из хранилища данных в виде строки и возвращает его.
Сборка и запуск, и у вас должна быть информативная круговая диаграмма!
Заворачивать
Мы рассмотрели, как абстрагировать логику данных из контроллера в отдельный объект, которым проще управлять и расширять. Мы также рассмотрели основы создания круговой диаграммы.
Это подводит нас к концу серии. Я надеюсь, что вы нашли эти уроки полезными. CorePlot может сделать гораздо больше, но это должно дать вам прочную основу для дальнейшего развития. Удачи в добавлении графиков в ваши собственные проекты!