30.01.2011

für CorePlot 0.2.2, Xcode 3.2

Tutorial: CorePlot Liniendiagramm

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

Als erstes muß ein neues Projekt in Xcode angelegt werden. Im Gegensatz zu dem Tutorial Erstellen eines Balkendiagramm, in dem eine Quartz Composer Application verwendet wurde, wird diesmal eine Cocoa Application als Projekt-Template verwendet.

Als Name wird bei dem Beispiel CorePlotLinienDiagramm gewählt. Das neue Projekt sollte dann ungefähr so aussehen:

Als erstes sollte das CorePlot.Framework und das QuartzCore.framework hinzugefügt werden. Dafür muß unter Groups&Files der Ordner Frameworks selektiert werden. Nach einem Rechtsklick, kann im Popup-Menü Add->Existing Framework ausgewählt werden. In dem Auswahldialog wird das CorePlot.framework und das QuartzCore.framework selektiert und dann mit Add hinzufügt. (Zum Selektieren des zweiten Frameworks einfach cmd gedrückt halten.)

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

In der Datei CorePlotLiniendiagrammAppDelegate.h müssen die folgenden zwei Zeilen entfernt werden:

NSWindow *window;
@property (assign) IBOutlet NSWindow *window;

Danach wird die Datei so angepaßt, daß sie folgendermaßen aussieht:

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

@interface CorePlotLiniendiagrammAppDelegate : NSObject <CPPlotDataSource> {
    IBOutlet CPLayerHostingView *view;
    CPXYGraph *graph;
    NSArray *data;
}

@end

Ganz oben in der Datei muß die Import-Anweisung für CorePlot ergänzt werden. Außerdem wird ein IBOutlet vom Typ CPLayerHostingView definiert, in dem das Liniendiagramm später angezeigt wird. Der Graph mit den Linien ist vom Typ CPXYGraph. Das NSArray wird dazu verwendet, um die Daten zu speichern, die angezeigt werden sollen. Außerdem muß noch das Protokoll CPPlotDataSource für die DataSource angegeben werden.

In der Datei CorePlotLiniendiagrammAppDelegate.m kann noch die Zeile @synthesize window gelöscht werden.

Nach dem Löschen, sollte die Datei folgendermaßen aussehen:

#import "CorePlotLiniendiagrammAppDelegate.h"

@implementation CorePlotLiniendiagrammAppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
	// Insert code here to initialize your application
}

@end

Als erstes wird die dealloc-Methode ergänzt, damit nach dem Schließen der Applikation der Speicher wieder aufgeräumt wird.

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

Außerdem wird die Methode awakeFromNib benötigt. In ihr wird weiter unten der Quellcode ergänzt, der das Aussehen des Diagramms bestimmt.

- (void) awakeFromNib
{
...
}

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

#pragma mark -
#pragma mark Plot Data Source Methods

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

-(NSNumber *)numberForPlot:(CPPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)index
{
   return [[data objectAtIndex:index] objectForKey:[NSNumber numberWithInt:fieldEnum]];
}

Die Methode numberOfRecordsForPlot liefert die Anzahl der Datensätze, d.h. die Anzahl der Punkte im Liniendiagramm, 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 meiste Logik steckt in der Methode awakeFromNib. Hier werden die Daten angelegt und das Aussehen des Liniendiagramms 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 = [[CPXYGraph alloc] initWithFrame:CGRectZero];
CPTheme *theme = [CPTheme themeNamed:kCPDarkGradientTheme];
[graph applyTheme:theme];
view.hostedLayer = graph;

Es wird ein Graph initialisiert und das CPTheme festgelegt. Der Graph wird abschließend dem HostedLayer (hostedLayer) der view zugewiesen.

Als nächste wird eine Zeichenfläche definiert.

// Define the space for the bars. (12 Points with a max height of 150)
CPXYPlotSpace *plotSpace = (CPXYPlotSpace *)graph.defaultPlotSpace;
plotSpace.yRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0.0f)
                                               length:CPDecimalFromFloat(150.0f)];
plotSpace.xRange = [CPPlotRange plotRangeWithLocation:CPDecimalFromFloat(0.0f)
                                               length:CPDecimalFromFloat(11.0f)];

Hier wird zuerst die Höhe y mit 150 angegeben. Für die Breite x wird der Wert 11 gewählt, weil 12 Punkte angezeigt werden sollen. Der erste Punkt wird bei der x-Koordinate 0 gesetzt und der letzte Punkt bei der x-Koordinate 11.

// ScatterPlot
CPScatterPlot *linePlot = [[[CPScatterPlot alloc] init] autorelease];
linePlot.identifier = @"LinienDiagramm";
linePlot.dataLineStyle.lineWidth = 3.f;
linePlot.dataLineStyle.lineColor = [CPColor orangeColor];
linePlot.dataSource = self;
[graph addPlot: linePlot];

Abschließend wird ein CPScatterPlot allokiert und initialisiert. Als Identifier wird der Wert LinienDiagramm zugewiesen, um das Diagramm eindeutig identifizieren zu können. Für die Breite einer Linie wird 3 (lineWidth) angegeben. Über die Variable lineColor wird die Farbe Orange gesetzt.

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

Zum Schluß wird der ScatterPlot dem Graphen zugewiesen. Damit ist die Methode awakeFromNib vollständing.

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 CPScatterPlotFieldX:
            return (NSDecimalNumber *)[NSDecimalNumber numberWithUnsignedInteger:index];
        case CPScatterPlotFieldY:
            return [data objectAtIndex:index];
    }
    return nil;
}

In der Methode werden zwei Fälle unterschieden. Einmal wird nach dem X-Wert (CPScatterPlotFieldX) gefragt und einmal nach dem Y-Wert (CPScatterPlotFieldY). Bei dem X-Wert wird einfach der index zurückgeliefert. Wird der Y-Wert angefragt, dann wird der entsprechende Wert aus dem NSArray data ausgelesen.

Die Implementierung ist damit abgeschlossen. Jetzt muß noch die Ansicht (view) eingerichtet werden. Dazu wird die Datei MainMenu.xib im Resources-Folder ausgewählt. Nach einem Doppelklick auf die Datei öffnet sich der InterfaceBuilder.

Wenn die Library nicht schon geöffnet ist, dann sollte man sie mit cmd-shift-L öffnen. Dort Classes anklicken und unten im Suchfeld CPLayerHostingView eintippen.

Diese Ansicht (View) wird selektiert und mit gedrückter Mousetaste in das Fenster (Window) gezogen und auf die gewünschte Größe angepaßt.

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

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