Arbeta med CorePlot Skapa en stapeldiagram

När du arbetar med dataintensiva applikationer måste en utvecklare ofta göra mer än bara visa listor med dataposter i en tabellvy. CorePlot-biblioteket låter dig lägga till fantastiska datavisualiseringar till dina applikationer. Ta reda på hur i denna Tuts + Premium-serie!


Finns även i serien:

  1. Arbeta med CorePlot: Project Setup
  2. Arbetar med CorePlot: Plot Fundamentals
  3. Arbetar med CorePlot: Styling och Add Plots
  4. Arbeta med CorePlot: Skapa en stapeldiagram
  5. Arbeta med CorePlot: Skapa en cirkeldiagram

Var vi lämnade

Förra gången gick vi över hur vi anpassade utseendet och stilen på en raddiagram (eller scatter plot) med hjälp av klasser som CPTMutableTextStyle och CPTMutableLineStyle. Vi lärde oss hur vi anpassar X- och Y-axelinkrementen och antalet stilarter med hjälp av CPTXYAxisSet och CPTXYAxis-klasserna. Vi tittade också på hur man lägger till flera tomter i ditt diagram och modifierar datakällans metoder för att ge rätt data för rätt tomter med hjälp av plot-identifierare.


Vad vi täcker idag

Idag arbetar vi med en helt ny graf. Vi kommer att skapa ett stapeldiagram som visar det totala antalet studenter i varje ämne med varje stapel som representerar ett ämne. Vi ska också titta på hur du anpassar utseendet på grafen. Låt oss börja!


Steg 1: Ställa in

Först måste vi lägga till relevanta klasser i vårt projekt. Låt oss skapa en ViewController kallad 'STBarGraphViewController' och en 'STGraphView'. (Se till att du lägger dem i relevanta grupper)


Lägg märke till namnet på "STGraphView" istället för "STBarGraphView". Framåt kommer vi att använda den här vyn för att visa baren och cirkeldiagrammen.

Innan vi börjar arbeta med någon kod måste vi lägga till en knapp i handlingsbladet i vår elevlista. Öppna först STStudentListViewController.h och importera STBarGraphViewController. Lägg till STBarGraphViewControllerDelegate till listan över registrerade protokoll (protokollet finns faktiskt inte, men vi kommer att skapa det senare):

 @interface STStudentListViewController: UIViewController 

Hoppa därefter till .m-filen och leta reda på "graphButtonWasSelected:" -metoden. Lägg till 'Inskrivning per ämne' till listan med 'otherButtonTitles:':

 UIActionSheet * graphSelectionActionSheet = [[[UIActionSheet alloc] initWithTitle: @ "Välj en graf" delegat: self cancelButtonTitle: @ "Avbryt" destructiveButtonTitle: nil otherButtonTitles: @ "Inskrivning över tiden", @ "Inskrivning per ämne", noll] autorelease] ;

Hitta nu "actionSheet: clickedButtonAtIndex:" -metoden och ändra det för att fungera med knappenIndex == 1:

 om (buttonIndex == 0) STLineGraphViewController * graphVC = [[STLineGraphViewController alloc] init]; [graphVC setModalTransitionStyle: UIModalTransitionStyleFlipHorizontal]; [graphVC setModalPresentationStyle: UIModalPresentationFullScreen]; [graphVC setDelegate: self]; [graphVC setManagedObjectContext: [self managedObjectContext]]; [self presentModalViewController: graphVC animated: YES]; [graphVC release];  annars om (buttonIndex == 1) STBarGraphViewController * graphVC = [[STBarGraphViewController alloc] init]; [graphVC setModalTransitionStyle: UIModalTransitionStyleFlipHorizontal]; [graphVC setModalPresentationStyle: UIModalPresentationFullScreen]; [graphVC setDelegate: self]; [graphVC setManagedObjectContext: [self managedObjectContext]]; [self presentModalViewController: graphVC animated: YES]; [graphVC release]; 

Återigen kommer detta att visa några varningar eftersom vi inte har implementerat egenskaperna för delegerade eller managedObjectContext på STBarGraphViewController än.

Hoppa nu till STBarGraphViewController.h. Importera CorePlot-CocoaTouch.h och lägg till följande fastighetsdeklaration:

 @protocol STBarGraphViewControllerDelegate @required - (void) doneButtonWasTapped: (id) avsändare; @slutet

Lägg nu till följande egenskaper:

 @property (nonatomic, strong) CPTGraph * graph; @property (nonatomic, assign) id delegera; @property (nonatomic, strong) NSManagedObjectContext * managedObjectContext;

Slutligen registrera dig att den här klassen följer dessa protokoll:

 @interface STBarGraphViewController: UIViewController 

Observera att denna klass, förutom att överensstämma med olika protokoll, är samma som STLineViewController. Helst skulle du ha en basklass som skulle ha dessa egenskaper som du skulle subklassera för att minska kodrepetitionen. Denna handledning fokuserar bara på kärnplott, så vi fokuserar bara på hur bäst det fungerar med CorePlot-ramen. Om du har kunskap och tid, kan du skapa basklass och använda arv, men vi kommer att hålla det enkelt i provkoden här.

Hoppa därefter till .m-filen, syntetisera egenskaperna och släpp dem i deallokmetoden.

Låt oss nu länka visningsklassen som regulatorens vy. Importera 'STBarGraphView.h' och lägg till följande metod:

 - (void) loadView [super loadView]; [self setTitle: @ "Inskrivning per ämne"]; [self setView: [[[STGraphView alloc] initWithFrame: self.view.frame] autorelease]]; CPTTheme * defaultTheme = [CPTTheme themeNamed: kCPTPlainWhiteTheme]; [self setGraph: (CPTGraph *) [defaultTheme newGraph]]; 

Nu är vi redo att arbeta med vyn. Öppna upp STGraphView.h, importera kärnplaneringsramen (CorePlot-CocoaTouch.h) och lägg till följande fastighetsdeklaration:

 @property (nonatomic, strong) CPTGraphHostingView * chartHostingView;

Hoppa in i .m, syntetisera egenskapen och släpp egenskapen i deallokmetoden. Skapa sedan CPTGraphHostingView i metoden 'initWithFrame:':

 - (id) initWithFrame: (CGRect) ram self = [super initWithFrame: frame]; om (själv) [self setChartHostingView: [[[CPTGraphHostingView alloc] initWithFrame: CGRectZero] autorelease]]; [chartHostingView setBackgroundColor: [UIColor purpleColor]]; [self addSubview: chartHostingView];  återvänd själv 

Slutligen skapa "layoutSubviews" -metoden med följande kod:

 - (void) layoutSubviews [super layoutSubviews]; float chartHeight = self.frame.size.height; float chartWidth = self.frame.size.width; [[self chartHostingView] setFrame: CGRectMake (0, 0, chartWidth, chartHeight)]; [[self chartHostingView] setCenter: [self center]]; 

Du märker att koden som används för att skapa denna vy är exakt densamma som STLineGraphView. Vi kan använda denna grundläggande vy för att arbeta med alla grafvyer framåt.

Hoppa tillbaka till "STBarGraphViewController.m" -vyn och leta efter "viewDidLoad" -metoden. Först vill vi skapa en CPTBarPlot och lägga till den i vårt diagram:

 [[graphView chartHostingView] setHostedGraph: [self graph]]; CPTBarPlot * subjectBarPlot = [[CPTBarPlot-allokering] initWithFrame: [graph bounds]]; [subjectBarPlot setIdentifier: @ "subjectEnrollement"]; [subjectBarPlot setDelegate: self]; [subjectBarPlot setDataSource: self]; [[självgraf] addPlot: subjectBarPlot];

Till sist lägger vi till en navigeringsfält med en färdig knapp, så användaren kan komma tillbaka:

 UINavigationItem * navigationItem = [[[UINavigationItem alloc] initWithTitle: self.title] autorelease]; [navigationItem setHidesBackButton: JA]; 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: @ "Klar" stil: UIBarButtonItemStyleDone mål: [self delegate] action: @selector (doneButtonWasTapped :)] autorelease] animated: NO];

Låt oss nu börja arbeta med datakällans metoder:


Steg 2: Ge stapeldata

Processen kommer att likna hur vi skapade linjediagrammet, men vi kommer att göra saker lite mer dynamiska. Vi kommer att skapa anpassade metoder som ger ett lämpligt tomtutrymme. Vi lägger också till några extra datakälla metoder för att ge en specifik färg för varje stapel samt x-axel titlar.

Innan vi tittar på att tillhandahålla grafdata måste vi ställa in axeln och synliga intervall. För att göra detta behöver vi två metoder som beräknar maximala x och maximala y-värden. Förklara följande metoder överst i .m-filen:

 [[graphView chartHostingView] setHostedGraph: [self graph]]; @interface STBarGraphViewController () - (float) getTotalSubjects; - (float) getMaxEnrolled; @slutet

Genomföra dem enligt nedan:

 #pragma mark - Private Methods - (float) getTotalSubjects NSError * error = nil; NSFetchRequest * fetchRequest = [[[NSFetchRequest alloker] init] autorelease]; NSEntityDescription * entity = [NSEntityDescription entityForName: @ "STSubject" inManagedObjectContext: managedObjectContext]; [fetchRequest setEntity: entity]; returnera [managedObjectContext countForFetchRequest: fetchRequest error: & error];  - (float) getMaxEnrolled float maxEnrolled = 0; NSError * error = nil; för (int i = 0; i < [self getTotalSubjects]; i++)  NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"STStudent" inManagedObjectContext:managedObjectContext]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"subjectID == %d", i]; [fetchRequest setEntity:entity]; [fetchRequest setPredicate:predicate]; float subjectMax = [managedObjectContext countForFetchRequest:fetchRequest error:&error]; NSLog(@"enrolled for Subject %d is %f", i, subjectMax); [fetchRequest release]; if (subjectMax > maxEnrolled) maxEnrolled = subjectMax;  returnera maxEnrolled; 

'getTotalSubjects' får helt enkelt en räkning av alla ämnen i kärndatabutiken. 'getMaxEnrolled' loopar genom alla ämnen och letar efter högsta antal studenter i varje och returnerar det högsta värdet.

Med de här metoderna kan vi gå tillbaka till vår "viewDidLoad" -metod och lägga till följande kod nedan, där vi lägger till bardiagrammet i vårt diagram:

 CPTXYPlotSpace * studentPlotSpace = (CPTXYPlotSpace *) [graph defaultPlotSpace]; [studentPlotSpace setXRange: [CPTPlotRange plotRangeWithLocation: CPTDecimalFromInt (0) längd: CPTDecimalFromInt ([self getTotalSubjects] + 1)]]; [studentPlotSpace setYRange: [CPTPlotRange plotRangeWithLocation: CPTDecimalFromInt (0) längd: CPTDecimalFromInt ([self getMaxEnrolled] + 1)]]; [[graph plotAreaFrame] setPaddingLeft: 40.0f]; [[graph plotAreaFrame] setPaddingTop: 10.0f]; [[graph plotAreaFrame] setPaddingBottom: 120.0f]; [[graph plotAreaFrame] setPaddingRight: 0.0f]; [[graph plotAreaFrame] setBorderLineStyle: nil]; CPTMutableTextStyle * textStyle = [CPTMutableTextStyle textStyle]; [textStyle setFontSize: 12.0f]; [textStyle setColor: [CPTColor colorWithCGColor: [[UIColor grayColor] CGColor]]]; CPTXYAxisSet * axisSet = (CPTXYAxisSet *) [graph axisSet]; CPTXYAxis * xAxis = [axisSet xAxis]; [xAxis setMajorIntervalLength: CPTDecimalFromInt (1)]; [xAxis setMinorTickLineStyle: nil]; [xAxis setLabelingPolicy: CPTAxisLabelingPolicyFixedInterval]; [xAxis setLabelTextStyle: textStyle]; CPTXYAxis * yAxis = [axisSet yAxis]; [yAxis setMajorIntervalLength: CPTDecimalFromInt (1)]; [yAxis setMinorTickLineStyle: nil]; [yAxis setLabelingPolicy: CPTAxisLabelingPolicyFixedInterval];

Det mesta av ovanstående bör vara bekant från förra gången, men istället för att ställa in hårdkodade värden för vår x och y max plotintervalllängd använder vi våra nya metoder för att dynamiskt skapa värdena. Därefter ställer vi just upp en del axelformatering och ger plottramen en del fyllning så att axeln visar på lämpligt sätt.

Nu måste vi tillhandahålla grafen med data med datakällans metoder. Att tillhandahålla antalet poster är enkelt:

 #pragma mark - CPTBarPlotDataSourceMethods - (NSUInteger) numberOfRecordsForPlot: (CPTPlot *) plot return [self getTotalSubjects]; 

Nu för att ge x- och y-värdena för posterna:

 - (NSNumber *) numberForPlot: (CPTPlot *) plotfält: (NSUInteger) fältEn recordIndex: (NSUInteger) index int x = index + 1; int y = 0; NSError * fel; NSFetchRequest * fetchRequest = [[NSFetchRequest tilldela] init]; NSEntityDescription * entity = [NSEntityDescription entityForName: @ "STStudent" inManagedObjectContext: managedObjectContext]; NSPredicate * predicate = [NSPredicate predicateWithFormat: @ "subjectID ==% d", index]; [fetchRequest setEntity: entity]; [fetchRequest setPredicate: predicate]; y = [managedObjectContext countForFetchRequest: fetchRequest error: & error]; [fetchRequest release]; switch (fieldEnum) fall CPTScatterPlotFieldX: returnera [NSNumber numberWithInt: x]; ha sönder; fall CPTScatterPlotFieldY: returnera [NSNumber numberWithInt: y]; ha sönder; standard: break;  returnera nil; 

Ovanstående metod liknar väldigt hur vi tillhandahåller data till en scatterplot. Vi ändrar samtalet till datalagret så att vi får en räkning av alla studenter som är inskrivna i ett ämne. Vi ställer också in x-värdet till +1. Det här är så att linjen börjar på '1' och ger en liten bit vaddering mellan den första linjen och y-axeln.

Spara och kör projektet ... du borde se din stapeldiagram!



Steg 3: Efterbehandling

Det finns lite mer vi kan göra för att göra det enklare att titta på. Vi vill ge varje stapel en annan färg och ge namnet på ämnet som x-axeln i stället för ett nummer. Vi kan göra det första med en CPTBarPlotDataSource-metod som heter 'barFillForBarPlot: recordIndex':

 -(CPTFill *) barFillForBarPlot: (CPTBarPlot *) barPlot recordIndex: (NSUInteger) index CPTColor * areaColor = nil; switch (index) fall 0: areaColor = [CPTColor redColor]; ha sönder; fall 1: areaColor = [CPTColor blueColor]; ha sönder; fall 2: areaColor = [CPTColor orangeColor]; ha sönder; fall 3: areaColor = [CPTColor greenColor]; ha sönder; standard: areaColor = [CPTColor purpleColor]; ha sönder;  CPTFill * barFill = [CPTFill fillWithColor: areaColor]; returnera barFill; 

Detta kommer att returnera en annan färg för varje stapel i vår graf. Om du ville göra det ännu snyggare, kan du faktiskt göra det till en gradient. Det är också en del av vårt diagram som inte är helt dynamiskt, eftersom om någon lägger till ett nytt ämne kommer det att använda standardfärgen. Kanske ett sätt att komma runt detta i ett verkligt liv skulle vara att ha en färg när man skapar ett nytt ämne som lagras i datalageret


Till sist lägger vi till några anpassade x-axeltitlar. För att göra detta måste vi arbeta med x-axeln. Leta reda på var vi ställer in x-axeln och ändra koden för att göra följande:

 CPTXYAxis * xAxis = [axisSet xAxis]; [xAxis setMajorIntervalLength: CPTDecimalFromInt (1)]; [xAxis setMinorTickLineStyle: nil]; [xAxis setLabelingPolicy: CPTAxisLabelingPolicyNone]; [xAxis setLabelTextStyle: textStyle]; [xAxis setLabelRotation: M_PI / 4]; NSArray * subjectsArray = [self getSubjectTitlesAsArray]; [xAxis setAxisLabels: [NSSet setWithArray: subjectsArray]];

Det finns några ändringar i ovanstående kod. Först ändrar vi märkningspolitiken till ingen. Detta kommer att se till att CorePlot inte skriver ut etiketten själv och istället använder det vi ger det. Därefter ställer vi etikettrotationen så att den stämmer bättre med grafen. Slutligen ställer vi fast egenskapen "Axis etiketter" som tar en NSSet av NSString-värden. Vi skapar NSSet med en NSArray skapad med metoden 'getSubjectTitlesAsArray'. Den metoden existerar inte, så låt oss skapa den. Lägg till deklarationen längst upp i .m-filen och skriv sedan följande implementering:

 - (NSArray *) getSubjectTitlesAsArray NSError * error = nil; NSFetchRequest * request = [[NSFetchRequest tilldela] init]; NSSortDescriptor * sortDescriptor = [NSSortDescriptor sortDescriptorWithKey: @ "subjectID" stigande: JA]; NSEntityDescription * entity = [NSEntityDescription entityForName: @ "STSubject" inManagedObjectContext: managedObjectContext]; [förfrågan setEntity: entity]; [request setSortDescriptors: [NSArray arrayWithObject: sortDescriptor]]; [förfrågan setResultType: NSDictionaryResultType]; [request setReturnsDistinctResults: NO]; [request setPropertiesToFetch: [NSArray arrayWithObject: @ "subjectName"]]; NSArray * titleStrings = [managedObjectContext executeFetchRequest: begäran fel: & error]; NSMutableArray * labelArray = [NSMutableArray array]; CPTMutableTextStyle * textStyle = [CPTMutableTextStyle textStyle]; [textStyle setFontSize: 10]; för (int i = 0; i < [titleStrings count]; i++)  NSDictionary *dict = [titleStrings objectAtIndex:i]; CPTAxisLabel *axisLabel = [[CPTAxisLabel alloc] initWithText:[dict objectForKey:@"subjectName"] textStyle:textStyle]; [axisLabel setTickLocation:CPTDecimalFromInt(i + 1)]; [axisLabel setRotation:M_PI/4]; [axisLabel setOffset:0.1]; [labelArray addObject:axisLabel]; [axisLabel release];  return [NSArray arrayWithArray:labelArray]; 

Det finns mycket kvar i ovanstående kod. För att kunna ge en grafik anpassade etiketter måste vi skicka ett NSSet innehållande objekt av typen 'CPTAxisLabel'. Först får vi en uppsättning av alla ämnesnamn som beställts av subjectID så det kommer att vara i samma ordning som grafen. Sedan, för mängden namn som vi kommer tillbaka, slingrar vi igenom och skapar en CPTAxisLabel med ämnesnamnsträngen och en fördefinierad textstil. Den "tick plats" är vilket fält det kommer att visas för. Vi måste lägga till 1 till vårt värde eftersom vi börjar vår första stapel vid 1 istället för 0. Vi ställer sedan in en rotation och kompenseras och läggs till i en matris. Slutligen returnerar vi arrayen av axelablar.

Om vi ​​sparar och kör projektet, har vi ett färdigt, färgstarkt streckdiagram med anpassade etiketter!



Nästa gång

Vi har lärt oss en hel del om CorePlot denna session. Vi har lärt oss hur man skapar ett stapeldiagram, ändrar streckets färger och till och med lägger till egna etiketter på axeln.

Nästa gång kommer vi att täcka hur man gör ett fantastiskt cirkeldiagram som visar samma data som stapeldiagrammet. Fånga dig nästa gång!