19.10.2011

aktualisierte Version für CorePlot 0.9, Xcode 4

Tutorial: CorePlot Balkendiagramm

Im Folgenden wird erklärt, wie man mit dem Framework CorePlot ein einfaches Balkendiagramm auf dem Mac erstellt.

Als erstes muß ein neues Projekt in Xcode angelegt werden. Als Typ wird dabei eine Cocoa Application gewählt.

Als Name wird bei dem Beispiel CorePlotBalkenDia gewählt.

Das neue Projekt sollte dann ungefähr so aussehen:

Als erstes sollten folgende Frameworks hinzugefügt werden: CorePlot.Framework, QuartzCore.framework. Dafür muß im Project navigator der Ordner Frameworks selektiert werden. Nach einem Rechtsklick, kann im Popup-Menü Add files to "CorePlotBalkenDia"... ausgewählt werden.

In dem Dialog navigiert man für QuartzCore.framework zu /System/Library/Frameworks und wählt die oben aufgeführten Frameworks aus. Das CorePlot.framework liegt normalerweise unter /Library/Frameworks.

Wenn das CorePlot.framework dort nicht erscheint, muß das Binary des Frameworks an die richtige Stelle kopiert werden. Das ist hier unter Punkt 2 beschrieben.

Der Inhalt der Datei CorePlotBalkenDiaAppDelegate.h kann entfernt und du folgenden Quellcode ersetzt werden:

#import <Cocoa/Cocoa.h>
#import <Quartz/Quartz.h>
#import <CorePlot/CorePlot.h>

@interface CorePlotBalkenDiaAppDelegate : NSObject <CPTPlotDataSource>
{
    IBOutlet CPTGraphHostingView *view;
    CPTXYGraph *graph;
    NSArray *data;
}
@end

Zuerst wird die Importanweisung des benötigten Headers von CorePlot hinzugefügt. Außerdem wird ein IBOutlet vom Typ CPTGraphHostingView definiert, in dem das Balkendiagramm später angezeigt wird. Der Graph mit den Balken ist vom Typ CPTXYGraph. Das NSArray wird dazu verwendet, um die Daten zu speichern, die angezeigt werden sollen. Außerdem muß noch das Protokoll CPTPlotDataSource für die DataSource angegeben werden.

In der Datei CorePlotBalkenDiaAppDelegate.m müssen auch noch einige Zeilen gelöscht und ergänzt werden. Danach sollte die Datei folgendermaßen aussehen:

#import "CorePlotBalkenDiaAppDelegate.h"

@implementation CorePlotBalkenDiaAppDelegate
 
- (void) awakeFromNib
{

}

- (void)windowWillClose:(NSNotification *)notification 
{
	[NSApp terminate:self];
}

@end

Als erstes wird die dealloc-Methode ergänzt.

-(void)dealloc
{
    [data release];
    [graph release];
    [super dealloc];
}

Als nächstes kommt die Implementierung der DataSource-Methoden:

#pragma mark -
#pragma mark Plot Data Source Methods

-(NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
{
    return data.count;
}

-(NSNumber *)numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{

}

-(CPTFill *) barFillForBarPlot:(CPTBarPlot *)barPlot recordIndex:(NSNumber *)index; 
{
	return nil;
}

Die Methode numberOfRecordsForPlot liefert die Anzahl der Datensätze, d.h. die Anzahl der Balken, zurück. Dieser Wert kann direkt durch eine Abfrage der Elementanzahl in data bestimmt werden. Weitere Informationen zur Methode numberForPlot:field:recordIndex werden weiter unten gegeben.

Die Methode barFillForBarPlot:recordIndex wird in dem Beispiel nicht weiter verwendet und liefert deshalb nil zurück.

Die meiste Logik steckt in der Methode awakeFromNib. Hier werden die Daten angelegt und das Aussehen des Balkendiagramms wird definiert.

In der Methode awakeFromNib wird zuerst der Code ergänzt, der das Array mit den anzuzeigenden Daten enthält.

data = [[NSArray alloc]initWithObjects: 
                    [NSDecimalNumber numberWithInt:100], 
                    [NSDecimalNumber numberWithInt:130],
                    [NSDecimalNumber numberWithInt:30],
                    [NSDecimalNumber numberWithInt:40],
                    [NSDecimalNumber numberWithInt:60],
                    [NSDecimalNumber numberWithInt:80],
                    [NSDecimalNumber numberWithInt:100],
                    [NSDecimalNumber numberWithInt:120],
                    [NSDecimalNumber numberWithInt:10],
                    [NSDecimalNumber numberWithInt:15],
                    [NSDecimalNumber numberWithInt:20],
                    [NSDecimalNumber numberWithInt:100],
                    nil ];

Als nächstes kommt der Code, der den Graph erzeugt:

// Create graph and set a theme
graph = [[CPTXYGraph alloc] initWithFrame:CGRectZero];
CPTTheme *theme = [CPTTheme themeNamed:kCPTDarkGradientTheme];
[graph applyTheme:theme];
view.hostedGraph = graph;

Es wird ein Graph initialisiert und das CPTTheme festgelegt. Der Graph wird abschließend dem HostedGraph (hostedGraph) der view zugewiesen.

Als nächste wird eine Zeichenfläche definiert.

// Define the space for the bars. (12 Bars with a max height of 150)
CPTXYPlotSpace *plotSpace = (CPTXYPlotSpace *)graph.defaultPlotSpace;
plotSpace.yRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(0.0f)
     length:CPTDecimalFromFloat(150.0f)];
plotSpace.xRange = [CPTPlotRange plotRangeWithLocation:CPTDecimalFromFloat(0.0f) 
     length:CPTDecimalFromFloat(12.0f)];

Hier wird zuerst die Höhe y mit 150 angegeben. Für die Breite x wird der Wert 12 gewählt, weil 12 Balken angezeigt werden sollen.

//  Bar plot
CPTBarPlot *barPlot = [CPTBarPlot tubularBarPlotWithColor:[CPTColor yellowColor] horizontalBars:NO];
barPlot.dataSource = self;
barPlot.baseValue = CPTDecimalFromFloat(0.0f);
barPlot.barOffset = CPTDecimalFromFloat(0.5f); 
barPlot.barWidth = CPTDecimalFromFloat(0.5f); 
barPlot.identifier = @"BlueBarPlot";
[graph addPlot:barPlot toPlotSpace:plotSpace];

Abschließend wird ein CPTBarPlot mit der Farbe gelb definiert, bei dem die Balken vertikal angezeigt werden.

Die DataSource für den BarPlot wird auf self gesetzt, so daß die oben angelegten DataSource-Methoden aufgerufen werden, wenn der BarPlot nach den anzuzeigenden Daten fragt.

Als Basevalue wird 0 gewählt. Der Offset sollte 0.5f sein, damit alle Balken angezeigt werden und der erste Balken nicht links aus dem Bild verschwindet. Als Breite für einen Balken wird 0.5 gewählt. Der Identifier wird angegeben damit ein Balken eindeutig identifiziert werden kann.

Zum Schluß wird der BarPlot und der PlotSpace dem Graphen zugewiesen.

In der Methode numberForPlot:field:recordIndex werden die Werte aus dem Array ausgelesen, wenn die Ansicht (view) die Werte von ihrer DataSource abfragt. Dies funktioniert folgendermaßen:

-(NSNumber *)numberForPlot:(CPPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
  switch ( fieldEnum ) {
    case CPTBarPlotFieldBarLocation:
        return (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:index];
    default:
        return [data objectAtIndex:index];
  }
  return nil;
}

Wenn nach der Position des Balkens gefragt wird, dann liefert die Methode einfach den Index zurück. Falls die Länge eines Balkens bestimmt werden soll, wird der entsprechende Wert aus den Array ausgelesen und zurück geliefert.

Die Implementierung ist damit abgeschlossen. Jetzt muß noch die Ansicht (view) eingerichtet werden. Dazu wird die Datei MainMenu.xib ausgewählt. Daraufhin öffnet sich der in Xcode 4 integrierte InterfaceBuilder. Falls die rechte Spalte beim ersten Öffnen nicht angezeigt wird, kann sie über das Menü View->Utilities->Object Library eingeblendet werden.

Theoretisch ist eine möglich eine CPTLayerHostingView direkt in die NSView des NSWindow einzubauen. Da aber ein kleiner Abstand zwischen View und Rahmen sein soll, wird zuerst eine CustomView hinzugefügt. Dazu wird im Suchfeld der Object Library der Wert NSView eingetragen, woraufhin eine CustomView angezeigt wird. Diese zieht man per Drag&Drop in das Window.

Anschließend paßt man die Größe der CustomView an und wählt auf der rechten Seite den Identity Inspector aus. Dort wird unter Custom Class für das Feld Class die Klasse CPTLayerHostingView eingetragen.

Was jetzt noch fehlt, ist die Verbindung zwischen View und Controller. Diese wird durch ein crtl-mousedrag vom Core Plot Balken Dia App Delegate auf die CPTLayerHostingView hinzugefügt. Jetzt ist auch das Outlet im Controller mit der Ansicht (View) verbunden.

Nach dem Kompilieren und Starten, sollte folgendes Balkendiagramm zu sehen sein: