22.10.2011

aktualisierte Version für CorePlot 0.9, Xcode 4

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. Als Typ wird dabei eine Cocoa Application gewählt.

Als Name wird bei dem Beispiel CorePlotLinienDia 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 CorePlotLinienDiaAppDelegate.h kann entfernt und du folgenden Quellcode ersetzt 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 <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 Liniendiagramm später angezeigt wird. Der Graph mit den Linien 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 CorePlotLinienDiaAppDelegate.m müssen auch noch einige Zeilen gelöscht und ergänzt werden. Danach sollte die Datei folgendermaßen aussehen:

#import "CorePlotLinienDiaAppDelegate.h"

@implementation CorePlotLinienDiaAppDelegate

- (void) awakeFromNib
{

}

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

@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];
}

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
{
    return nil;
}

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 = [[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 steps. (12 Points 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(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
CPTScatterPlot *linePlot = [[[CPTScatterPlot alloc] init] autorelease];
linePlot.identifier = @"LinienDiagramm";

CPTMutableLineStyle *lineStyle = [[linePlot.dataLineStyle mutableCopy] autorelease];
lineStyle.lineWidth = 3.f;
lineStyle.lineColor = [CPTColor greenColor];
linePlot.dataLineStyle = lineStyle;

linePlot.dataSource = self;
[graph addPlot: linePlot];

Abschließend wird ein CPTScatterPlot 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 Grün gesetzt.

Die DataSource für den CPTScatterPlot 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ändig.

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

In der Methode werden zwei Fälle unterschieden. Einmal wird nach dem X-Wert (CPTScatterPlotFieldX) gefragt und einmal nach dem Y-Wert (CPTScatterPlotFieldY). 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 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 Liniendiagramm zu sehen sein: