1#import "CPTGraph.h"
2#import "CPTExceptions.h"
3#import "CPTLegend.h"
4#import "CPTPlot.h"
5#import "CPTPlotArea.h"
6#import "CPTPlotAreaFrame.h"
7#import "CPTMutableTextStyle.h"
8#import "CPTPlotSpace.h"
9#import "CPTFill.h"
10#import "CPTAxisSet.h"
11#import "CPTAxis.h"
12#import "CPTTheme.h"
13#import "CPTLayerAnnotation.h"
14#import "CPTTextLayer.h"
15#import "NSCoderExtensions.h"
16
17/**	@defgroup graphAnimation Graphs
18 *	@brief Graph properties that can be animated using Core Animation.
19 *	@if MacOnly
20 *	@since Custom layer property animation is supported on MacOS 10.6 and later.
21 *	@endif
22 *	@ingroup animation
23 **/
24
25NSString * const CPTGraphNeedsRedrawNotification = @"CPTGraphNeedsRedrawNotification";
26
27/**	@cond */
28@interface CPTGraph()
29
30@property (nonatomic, readwrite, retain) NSMutableArray *plots;
31@property (nonatomic, readwrite, retain) NSMutableArray *plotSpaces;
32@property (nonatomic, readwrite, retain) CPTLayerAnnotation *titleAnnotation;
33@property (nonatomic, readwrite, retain) CPTLayerAnnotation *legendAnnotation;
34
35-(void)plotSpaceMappingDidChange:(NSNotification *)notif;
36-(CGPoint)contentAnchorForLegend;
37
38@end
39/**	@endcond */
40
41#pragma mark -
42
43/**	@brief An abstract graph class.
44 *	@todo More documentation needed
45 **/
46@implementation CPTGraph
47
48/**	@property axisSet
49 *	@brief The axis set.
50 **/
51@dynamic axisSet;
52
53/**	@property plotAreaFrame
54 *	@brief The plot area frame.
55 **/
56@synthesize plotAreaFrame;
57
58/**	@property plots
59 *	@brief An array of all plots associated with the graph.
60 **/
61@synthesize plots;
62
63/**	@property plotSpaces
64 *	@brief An array of all plot spaces associated with the graph.
65 **/
66@synthesize plotSpaces;
67
68/**	@property defaultPlotSpace
69 *	@brief The default plot space.
70 **/
71@dynamic defaultPlotSpace;
72
73/** @property topDownLayerOrder
74 *	@brief An array of graph layers to be drawn in an order other than the default.
75 *	@see CPTPlotArea#topDownLayerOrder
76 **/
77@dynamic topDownLayerOrder;
78
79/**	@property title
80 *	@brief The title string.
81 *  Default is nil.
82 **/
83@synthesize title;
84
85/**	@property titleTextStyle
86 *	@brief The text style of the title.
87 **/
88@synthesize titleTextStyle;
89
90/**	@property titlePlotAreaFrameAnchor
91 *	@brief The location of the title with respect to the plot area frame.
92 *  Default is top center.
93 **/
94@synthesize titlePlotAreaFrameAnchor;
95
96/**	@property titleDisplacement
97 *	@brief A vector giving the displacement of the title from the edge location.
98 *	@ingroup graphAnimation
99 **/
100@synthesize titleDisplacement;
101
102/**	@property legend
103 *	@brief The graph legend.
104 *	Setting this property will automatically anchor the legend to the graph and position it
105 *	using the legendAnchor and legendDisplacement properties. This is a convenience property
106 *	only—the legend may be inserted in the layer tree and positioned like any other CPTLayer
107 *	if more flexibility is needed.
108 **/
109@dynamic legend;
110
111/**	@property legendAnchor
112 *	@brief The location of the legend with respect to the graph frame.
113 *  Default is bottom center.
114 **/
115@synthesize legendAnchor;
116
117/**	@property legendDisplacement
118 *	@brief A vector giving the displacement of the legend from the edge location.
119 *	@ingroup graphAnimation
120 **/
121@synthesize legendDisplacement;
122
123@synthesize titleAnnotation;
124@synthesize legendAnnotation;
125
126#pragma mark -
127#pragma mark Init/Dealloc
128
129-(id)initWithFrame:(CGRect)newFrame
130{
131	if ( (self = [super initWithFrame:newFrame]) ) {
132		plots = [[NSMutableArray alloc] init];
133
134        // Margins
135        self.paddingLeft = 20.0;
136        self.paddingTop = 20.0;
137        self.paddingRight = 20.0;
138        self.paddingBottom = 20.0;
139
140        // Plot area
141        CPTPlotAreaFrame *newArea = [(CPTPlotAreaFrame *)[CPTPlotAreaFrame alloc] initWithFrame:self.bounds];
142        self.plotAreaFrame = newArea;
143        [newArea release];
144
145        // Plot spaces
146		plotSpaces = [[NSMutableArray alloc] init];
147        CPTPlotSpace *newPlotSpace = [self newPlotSpace];
148        [self addPlotSpace:newPlotSpace];
149        [newPlotSpace release];
150
151        // Axis set
152		CPTAxisSet *newAxisSet = [self newAxisSet];
153		self.axisSet = newAxisSet;
154		[newAxisSet release];
155
156        // Title
157        title = nil;
158        titlePlotAreaFrameAnchor = CPTRectAnchorTop;
159        titleTextStyle = [[CPTTextStyle textStyle] retain];
160        titleDisplacement = CGPointZero;
161		titleAnnotation = nil;
162
163		// Legend
164		legend = nil;
165		legendAnnotation = nil;
166		legendAnchor = CPTRectAnchorBottom;
167		legendDisplacement = CGPointZero;
168
169		self.needsDisplayOnBoundsChange = YES;
170	}
171	return self;
172}
173
174-(id)initWithLayer:(id)layer
175{
176	if ( (self = [super initWithLayer:layer]) ) {
177		CPTGraph *theLayer = (CPTGraph *)layer;
178
179		plotAreaFrame = [theLayer->plotAreaFrame retain];
180		plots = [theLayer->plots retain];
181		plotSpaces = [theLayer->plotSpaces retain];
182		title = [theLayer->title retain];
183		titlePlotAreaFrameAnchor = theLayer->titlePlotAreaFrameAnchor;
184		titleTextStyle = [theLayer->titleTextStyle retain];
185		titleDisplacement = theLayer->titleDisplacement;
186		titleAnnotation = [theLayer->titleAnnotation retain];
187		legend = [theLayer->legend retain];
188		legendAnnotation = [theLayer->legendAnnotation retain];
189		legendAnchor = theLayer->legendAnchor;
190		legendDisplacement = theLayer->legendDisplacement;
191	}
192	return self;
193}
194
195-(void)dealloc
196{
197	[[NSNotificationCenter defaultCenter] removeObserver:self];
198
199	[plotAreaFrame release];
200	[plots release];
201	[plotSpaces release];
202    [title release];
203    [titleTextStyle release];
204    [titleAnnotation release];
205	[legend release];
206	[legendAnnotation release];
207
208	[super dealloc];
209}
210
211#pragma mark -
212#pragma mark NSCoding methods
213
214-(void)encodeWithCoder:(NSCoder *)coder
215{
216	[super encodeWithCoder:coder];
217
218	[coder encodeObject:self.plotAreaFrame forKey:@"CPTGraph.plotAreaFrame"];
219	[coder encodeObject:self.plots forKey:@"CPTGraph.plots"];
220	[coder encodeObject:self.plotSpaces forKey:@"CPTGraph.plotSpaces"];
221	[coder encodeObject:self.title forKey:@"CPTGraph.title"];
222	[coder encodeObject:self.titleTextStyle forKey:@"CPTGraph.titleTextStyle"];
223	[coder encodeInteger:self.titlePlotAreaFrameAnchor forKey:@"CPTGraph.titlePlotAreaFrameAnchor"];
224	[coder encodeCPTPoint:self.titleDisplacement forKey:@"CPTGraph.titleDisplacement"];
225	[coder encodeObject:self.titleAnnotation forKey:@"CPTGraph.titleAnnotation"];
226	[coder encodeObject:self.legend forKey:@"CPTGraph.legend"];
227	[coder encodeObject:self.legendAnnotation forKey:@"CPTGraph.legendAnnotation"];
228	[coder encodeInteger:self.legendAnchor forKey:@"CPTGraph.legendAnchor"];
229	[coder encodeCPTPoint:self.legendDisplacement forKey:@"CPTGraph.legendDisplacement"];
230}
231
232-(id)initWithCoder:(NSCoder *)coder
233{
234    if ( (self = [super initWithCoder:coder]) ) {
235		plotAreaFrame = [[coder decodeObjectForKey:@"CPTGraph.plotAreaFrame"] retain];
236		plots = [[coder decodeObjectForKey:@"CPTGraph.plots"] mutableCopy];
237		plotSpaces = [[coder decodeObjectForKey:@"CPTGraph.plotSpaces"] mutableCopy];
238		title = [[coder decodeObjectForKey:@"CPTGraph.title"] copy];
239		titleTextStyle = [[coder decodeObjectForKey:@"CPTGraph.titleTextStyle"] copy];
240		titlePlotAreaFrameAnchor = [coder decodeIntegerForKey:@"CPTGraph.titlePlotAreaFrameAnchor"];
241		titleDisplacement = [coder decodeCPTPointForKey:@"CPTGraph.titleDisplacement"];
242		titleAnnotation = [[coder decodeObjectForKey:@"CPTGraph.titleAnnotation"] retain];
243		legend = [[coder decodeObjectForKey:@"CPTGraph.legend"] retain];
244		legendAnnotation = [[coder decodeObjectForKey:@"CPTGraph.legendAnnotation"] retain];
245		legendAnchor = [coder decodeIntegerForKey:@"CPTGraph.legendAnchor"];
246		legendDisplacement = [coder decodeCPTPointForKey:@"CPTGraph.legendDisplacement"];
247	}
248    return self;
249}
250
251#pragma mark -
252#pragma mark Drawing
253
254-(void)layoutAndRenderInContext:(CGContextRef)context
255{
256    [self reloadDataIfNeeded];
257    [self.axisSet.axes makeObjectsPerformSelector:@selector(relabel)];
258	[super layoutAndRenderInContext:context];
259}
260
261#pragma mark -
262#pragma mark Animation
263
264+(BOOL)needsDisplayForKey:(NSString *)aKey
265{
266	static NSArray *keys = nil;
267
268	if ( !keys ) {
269		keys = [[NSArray alloc] initWithObjects:
270				@"titleDisplacement",
271				@"legendDisplacement",
272				nil];
273	}
274
275	if ( [keys containsObject:aKey] ) {
276		return YES;
277	}
278	else {
279		return [super needsDisplayForKey:aKey];
280	}
281}
282
283#pragma mark -
284#pragma mark Retrieving Plots
285
286/**	@brief Makes all plots reload their data.
287 **/
288-(void)reloadData
289{
290    [self.plots makeObjectsPerformSelector:@selector(reloadData)];
291}
292
293/**	@brief Makes all plots reload their data if their data cache is out of date.
294 **/
295-(void)reloadDataIfNeeded
296{
297    [self.plots makeObjectsPerformSelector:@selector(reloadDataIfNeeded)];
298}
299
300/**	@brief All plots associated with the graph.
301 *	@return An array of all plots associated with the graph.
302 **/
303-(NSArray *)allPlots
304{
305	return [NSArray arrayWithArray:self.plots];
306}
307
308/**	@brief Gets the plot at the given index in the plot array.
309 *	@param index An index within the bounds of the plot array.
310 *	@return The plot at the given index.
311 **/
312-(CPTPlot *)plotAtIndex:(NSUInteger)index
313{
314    return [self.plots objectAtIndex:index];
315}
316
317/**	@brief Gets the plot with the given identifier from the plot array.
318 *	@param identifier A plot identifier.
319 *	@return The plot with the given identifier or nil if it was not found.
320 **/
321-(CPTPlot *)plotWithIdentifier:(id <NSCopying>)identifier
322{
323	for (CPTPlot *plot in self.plots) {
324        if ( [[plot identifier] isEqual:identifier] ) return plot;
325	}
326    return nil;
327}
328
329#pragma mark -
330#pragma mark Organizing Plots
331
332/**	@brief Add a plot to the default plot space.
333 *	@param plot The plot.
334 **/
335-(void)addPlot:(CPTPlot *)plot
336{
337	[self addPlot:plot toPlotSpace:self.defaultPlotSpace];
338}
339
340/**	@brief Add a plot to the given plot space.
341 *	@param plot The plot.
342 *	@param space The plot space.
343 **/
344-(void)addPlot:(CPTPlot *)plot toPlotSpace:(CPTPlotSpace *)space
345{
346	if ( plot ) {
347		[self.plots addObject:plot];
348		plot.plotSpace = space;
349        plot.graph = self;
350		[self.plotAreaFrame.plotGroup addPlot:plot];
351	}
352}
353
354/**	@brief Remove a plot from the graph.
355 *	@param plot The plot to remove.
356 **/
357-(void)removePlot:(CPTPlot *)plot
358{
359    if ( [self.plots containsObject:plot] ) {
360        plot.plotSpace = nil;
361        plot.graph = nil;
362		[self.plotAreaFrame.plotGroup removePlot:plot];
363        [self.plots removeObject:plot];
364    }
365    else {
366        [NSException raise:CPTException format:@"Tried to remove CPTPlot which did not exist."];
367    }
368}
369
370/**	@brief Add a plot to the default plot space at the given index in the plot array.
371 *	@param plot The plot.
372 *	@param index An index within the bounds of the plot array.
373 **/
374-(void)insertPlot:(CPTPlot* )plot atIndex:(NSUInteger)index
375{
376	[self insertPlot:plot atIndex:index intoPlotSpace:self.defaultPlotSpace];
377}
378
379/**	@brief Add a plot to the given plot space at the given index in the plot array.
380 *	@param plot The plot.
381 *	@param index An index within the bounds of the plot array.
382 *	@param space The plot space.
383 **/
384-(void)insertPlot:(CPTPlot* )plot atIndex:(NSUInteger)index intoPlotSpace:(CPTPlotSpace *)space
385{
386	if (plot) {
387		[self.plots insertObject:plot atIndex:index];
388		plot.plotSpace = space;
389        plot.graph = self;
390		[self.plotAreaFrame.plotGroup addPlot:plot];
391	}
392}
393
394/**	@brief Remove a plot from the graph.
395 *	@param identifier The identifier of the plot to remove.
396 **/
397-(void)removePlotWithIdentifier:(id <NSCopying>)identifier
398{
399	CPTPlot* plotToRemove = [self plotWithIdentifier:identifier];
400	if (plotToRemove) {
401		plotToRemove.plotSpace = nil;
402        plotToRemove.graph = nil;
403		[self.plotAreaFrame.plotGroup removePlot:plotToRemove];
404		[self.plots removeObjectIdenticalTo:plotToRemove];
405	}
406}
407
408#pragma mark -
409#pragma mark Retrieving Plot Spaces
410
411-(CPTPlotSpace *)defaultPlotSpace {
412    return ( self.plotSpaces.count > 0 ? [self.plotSpaces objectAtIndex:0] : nil );
413}
414
415/**	@brief All plot spaces associated with the graph.
416 *	@return An array of all plot spaces associated with the graph.
417 **/
418-(NSArray *)allPlotSpaces
419{
420	return [NSArray arrayWithArray:self.plotSpaces];
421}
422
423/**	@brief Gets the plot space at the given index in the plot space array.
424 *	@param index An index within the bounds of the plot space array.
425 *	@return The plot space at the given index.
426 **/
427-(CPTPlotSpace *)plotSpaceAtIndex:(NSUInteger)index
428{
429	return ( self.plotSpaces.count > index ? [self.plotSpaces objectAtIndex:index] : nil );
430}
431
432/**	@brief Gets the plot space with the given identifier from the plot space array.
433 *	@param identifier A plot space identifier.
434 *	@return The plot space with the given identifier or nil if it was not found.
435 **/
436-(CPTPlotSpace *)plotSpaceWithIdentifier:(id <NSCopying>)identifier
437{
438	for (CPTPlotSpace *plotSpace in self.plotSpaces) {
439        if ( [[plotSpace identifier] isEqual:identifier] ) return plotSpace;
440	}
441    return nil;
442}
443
444#pragma mark -
445#pragma mark Set Plot Area
446
447-(void)setPlotAreaFrame:(CPTPlotAreaFrame *)newArea
448{
449    if ( plotAreaFrame != newArea ) {
450    	plotAreaFrame.graph = nil;
451    	[plotAreaFrame removeFromSuperlayer];
452        [plotAreaFrame release];
453        plotAreaFrame = [newArea retain];
454        [self addSublayer:newArea];
455        plotAreaFrame.graph = self;
456		for ( CPTPlotSpace *space in self.plotSpaces ) {
457            space.graph = self;
458        }
459    }
460}
461
462#pragma mark -
463#pragma mark Organizing Plot Spaces
464
465/**	@brief Add a plot space to the graph.
466 *	@param space The plot space.
467 **/
468-(void)addPlotSpace:(CPTPlotSpace *)space
469{
470	[self.plotSpaces addObject:space];
471    space.graph = self;
472    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(plotSpaceMappingDidChange:) name:CPTPlotSpaceCoordinateMappingDidChangeNotification object:space];
473}
474
475/**	@brief Remove a plot space from the graph.
476 *	@param plotSpace The plot space.
477 **/
478-(void)removePlotSpace:(CPTPlotSpace *)plotSpace
479{
480	if ( [self.plotSpaces containsObject:plotSpace] ) {
481        [[NSNotificationCenter defaultCenter] removeObserver:self name:CPTPlotSpaceCoordinateMappingDidChangeNotification object:plotSpace];
482
483        // Remove space
484		plotSpace.graph = nil;
485		[self.plotSpaces removeObject:plotSpace];
486
487        // Update axes that referenced space
488        for ( CPTAxis *axis in self.axisSet.axes ) {
489            if ( axis.plotSpace == plotSpace ) axis.plotSpace = nil;
490        }
491    }
492    else {
493        [NSException raise:CPTException format:@"Tried to remove CPTPlotSpace which did not exist."];
494    }
495}
496
497#pragma mark -
498#pragma mark Coordinate Changes in Plot Spaces
499
500-(void)plotSpaceMappingDidChange:(NSNotification *)notif
501{
502	CPTPlotSpace *plotSpace = notif.object;
503	BOOL backgroundBandsNeedRedraw = NO;
504
505	for ( CPTAxis *axis in self.axisSet.axes ) {
506		if ( axis.plotSpace == plotSpace ) {
507			[axis setNeedsRelabel];
508			backgroundBandsNeedRedraw |= (axis.backgroundLimitBands.count > 0);
509		}
510	}
511	for ( CPTPlot *plot in self.plots ) {
512		if ( plot.plotSpace == plotSpace ) {
513			[plot setNeedsDisplay];
514		}
515	}
516	if ( backgroundBandsNeedRedraw ) {
517		[self.plotAreaFrame.plotArea setNeedsDisplay];
518	}
519}
520
521#pragma mark -
522#pragma mark Axis Set
523
524-(CPTAxisSet *)axisSet
525{
526	return self.plotAreaFrame.axisSet;
527}
528
529-(void)setAxisSet:(CPTAxisSet *)newSet
530{
531	self.plotAreaFrame.axisSet = newSet;
532}
533
534#pragma mark -
535#pragma mark Themes
536
537/**	@brief Apply a theme to style the graph.
538 *	@param theme The theme object used to style the graph.
539 **/
540-(void)applyTheme:(CPTTheme *)theme
541{
542    [theme applyThemeToGraph:self];
543}
544
545#pragma mark -
546#pragma mark Legend
547
548-(CPTLegend *)legend
549{
550	return (CPTLegend *)self.legendAnnotation.contentLayer;
551}
552
553-(void)setLegend:(CPTLegend *)newLegend
554{
555	if ( newLegend != legend ) {
556        [legend release];
557        legend = [newLegend retain];
558		CPTLayerAnnotation *theLegendAnnotation = self.legendAnnotation;
559		if ( legend ) {
560			if ( theLegendAnnotation ) {
561				theLegendAnnotation.contentLayer = legend;
562			}
563			else {
564				CPTLayerAnnotation *newLegendAnnotation = [[CPTLayerAnnotation alloc] initWithAnchorLayer:self];
565				newLegendAnnotation.contentLayer = legend;
566				newLegendAnnotation.displacement = self.legendDisplacement;
567				newLegendAnnotation.rectAnchor = self.legendAnchor;
568				newLegendAnnotation.contentAnchorPoint = [self contentAnchorForLegend];
569				[self addAnnotation:newLegendAnnotation];
570				self.legendAnnotation = newLegendAnnotation;
571				[newLegendAnnotation release];
572			}
573		}
574		else {
575			if ( theLegendAnnotation ) {
576				[self removeAnnotation:theLegendAnnotation];
577				self.legendAnnotation = nil;
578			}
579		}
580    }
581}
582
583-(void)setLegendAnchor:(CPTRectAnchor)newLegendAnchor
584{
585	if ( newLegendAnchor != legendAnchor ) {
586		legendAnchor = newLegendAnchor;
587		CPTLayerAnnotation *theLegendAnnotation = self.legendAnnotation;
588		if ( theLegendAnnotation ) {
589			theLegendAnnotation.rectAnchor = newLegendAnchor;
590			theLegendAnnotation.contentAnchorPoint = [self contentAnchorForLegend];
591		}
592	}
593}
594
595-(void)setLegendDisplacement:(CGPoint)newLegendDisplacement
596{
597	if ( !CGPointEqualToPoint(newLegendDisplacement, legendDisplacement) ) {
598		legendDisplacement = newLegendDisplacement;
599		self.legendAnnotation.displacement = newLegendDisplacement;
600	}
601}
602
603-(CGPoint)contentAnchorForLegend
604{
605	CGPoint contentAnchor = CGPointZero;
606
607	switch ( self.legendAnchor ) {
608		case CPTRectAnchorBottomLeft:
609			contentAnchor = CGPointMake(0.0, 0.0);
610			break;
611		case CPTRectAnchorBottom:
612			contentAnchor = CGPointMake(0.5, 0.0);
613			break;
614		case CPTRectAnchorBottomRight:
615			contentAnchor = CGPointMake(1.0, 0.0);
616			break;
617		case CPTRectAnchorLeft:
618			contentAnchor = CGPointMake(0.0, 0.5);
619			break;
620		case CPTRectAnchorRight:
621			contentAnchor = CGPointMake(1.0, 0.5);
622			break;
623		case CPTRectAnchorTopLeft:
624			contentAnchor = CGPointMake(0.0, 1.0);
625			break;
626		case CPTRectAnchorTop:
627			contentAnchor = CGPointMake(0.5, 1.0);
628			break;
629		case CPTRectAnchorTopRight:
630			contentAnchor = CGPointMake(1.0, 1.0);
631			break;
632		case CPTRectAnchorCenter:
633			contentAnchor = CGPointMake(0.5, 0.5);
634			break;
635		default:
636			break;
637	}
638
639	return contentAnchor;
640}
641
642#pragma mark -
643#pragma mark Accessors
644
645-(void)setPaddingLeft:(CGFloat)newPadding
646{
647    if ( newPadding != self.paddingLeft ) {
648        [super setPaddingLeft:newPadding];
649		[self.axisSet.axes makeObjectsPerformSelector:@selector(setNeedsDisplay)];
650    }
651}
652
653-(void)setPaddingRight:(CGFloat)newPadding
654{
655    if ( newPadding != self.paddingRight ) {
656        [super setPaddingRight:newPadding];
657		[self.axisSet.axes makeObjectsPerformSelector:@selector(setNeedsDisplay)];
658    }
659}
660
661-(void)setPaddingTop:(CGFloat)newPadding
662{
663    if ( newPadding != self.paddingTop ) {
664        [super setPaddingTop:newPadding];
665		[self.axisSet.axes makeObjectsPerformSelector:@selector(setNeedsDisplay)];
666    }
667}
668
669-(void)setPaddingBottom:(CGFloat)newPadding
670{
671    if ( newPadding != self.paddingBottom ) {
672        [super setPaddingBottom:newPadding];
673		[self.axisSet.axes makeObjectsPerformSelector:@selector(setNeedsDisplay)];
674    }
675}
676
677-(NSArray *)topDownLayerOrder
678{
679	return self.plotAreaFrame.plotArea.topDownLayerOrder;
680}
681
682-(void)setTopDownLayerOrder:(NSArray *)newArray
683{
684	self.plotAreaFrame.plotArea.topDownLayerOrder = newArray;
685}
686
687-(void)setTitle:(NSString *)newTitle
688{
689	if ( newTitle != title ) {
690        [title release];
691        title = [newTitle copy];
692		CPTLayerAnnotation *theTitleAnnotation = self.titleAnnotation;
693		if ( title ) {
694			if ( theTitleAnnotation ) {
695				((CPTTextLayer *)theTitleAnnotation.contentLayer).text = title;
696			}
697			else {
698				CPTLayerAnnotation *newTitleAnnotation = [[CPTLayerAnnotation alloc] initWithAnchorLayer:self.plotAreaFrame];
699				CPTTextLayer *newTextLayer = [[CPTTextLayer alloc] initWithText:title style:self.titleTextStyle];
700				newTitleAnnotation.contentLayer = newTextLayer;
701				newTitleAnnotation.displacement = self.titleDisplacement;
702				newTitleAnnotation.rectAnchor = self.titlePlotAreaFrameAnchor;
703				[self addAnnotation:newTitleAnnotation];
704				self.titleAnnotation = newTitleAnnotation;
705				[newTextLayer release];
706				[newTitleAnnotation release];
707			}
708		}
709		else {
710			if ( theTitleAnnotation ) {
711				[self removeAnnotation:theTitleAnnotation];
712				self.titleAnnotation = nil;
713			}
714		}
715    }
716}
717
718-(void)setTitleTextStyle:(CPTMutableTextStyle *)newStyle
719{
720    if ( newStyle != titleTextStyle ) {
721        [titleTextStyle release];
722        titleTextStyle = [newStyle copy];
723		((CPTTextLayer *)self.titleAnnotation.contentLayer).textStyle = titleTextStyle;
724    }
725}
726
727-(void)setTitleDisplacement:(CGPoint)newDisplace
728{
729    if ( !CGPointEqualToPoint(newDisplace, titleDisplacement) ) {
730        titleDisplacement = newDisplace;
731        titleAnnotation.displacement = newDisplace;
732    }
733}
734
735-(void)setTitlePlotAreaFrameAnchor:(CPTRectAnchor)newAnchor
736{
737    if ( newAnchor != titlePlotAreaFrameAnchor ) {
738        titlePlotAreaFrameAnchor = newAnchor;
739        titleAnnotation.rectAnchor = titlePlotAreaFrameAnchor;
740    }
741}
742
743#pragma mark -
744#pragma mark Event Handling
745
746-(BOOL)pointingDeviceDownEvent:(id)event atPoint:(CGPoint)interactionPoint
747{
748    // Plots
749    for ( CPTPlot *plot in [self.plots reverseObjectEnumerator] ) {
750        if ( [plot pointingDeviceDownEvent:event atPoint:interactionPoint] ) return YES;
751    }
752
753    // Axes Set
754    if ( [self.axisSet pointingDeviceDownEvent:event atPoint:interactionPoint] ) return YES;
755
756    // Plot area
757    if ( [self.plotAreaFrame pointingDeviceDownEvent:event atPoint:interactionPoint] ) return YES;
758
759    // Plot spaces
760    // Plot spaces do not block events, because several spaces may need to receive
761    // the same event sequence (e.g., dragging coordinate translation)
762    BOOL handledEvent = NO;
763    for ( CPTPlotSpace *space in self.plotSpaces ) {
764        BOOL handled = [space pointingDeviceDownEvent:event atPoint:interactionPoint];
765        handledEvent |= handled;
766    }
767
768    return handledEvent;
769}
770
771-(BOOL)pointingDeviceUpEvent:(id)event atPoint:(CGPoint)interactionPoint
772{
773    // Plots
774    for ( CPTPlot *plot in [self.plots reverseObjectEnumerator] ) {
775        if ( [plot pointingDeviceUpEvent:event atPoint:interactionPoint] ) return YES;
776    }
777
778    // Axes Set
779    if ( [self.axisSet pointingDeviceUpEvent:event atPoint:interactionPoint] ) return YES;
780
781    // Plot area
782    if ( [self.plotAreaFrame pointingDeviceUpEvent:event atPoint:interactionPoint] ) return YES;
783
784    // Plot spaces
785    // Plot spaces do not block events, because several spaces may need to receive
786    // the same event sequence (e.g., dragging coordinate translation)
787    BOOL handledEvent = NO;
788    for ( CPTPlotSpace *space in self.plotSpaces ) {
789        BOOL handled = [space pointingDeviceUpEvent:event atPoint:interactionPoint];
790        handledEvent |= handled;
791    }
792
793    return handledEvent;
794}
795
796-(BOOL)pointingDeviceDraggedEvent:(id)event atPoint:(CGPoint)interactionPoint
797{
798    // Plots
799    for ( CPTPlot *plot in [self.plots reverseObjectEnumerator] ) {
800        if ( [plot pointingDeviceDraggedEvent:event atPoint:interactionPoint] ) return YES;
801    }
802
803    // Axes Set
804    if ( [self.axisSet pointingDeviceDraggedEvent:event atPoint:interactionPoint] ) return YES;
805
806    // Plot area
807    if ( [self.plotAreaFrame pointingDeviceDraggedEvent:event atPoint:interactionPoint] ) return YES;
808
809    // Plot spaces
810    // Plot spaces do not block events, because several spaces may need to receive
811    // the same event sequence (e.g., dragging coordinate translation)
812    BOOL handledEvent = NO;
813    for ( CPTPlotSpace *space in self.plotSpaces ) {
814        BOOL handled = [space pointingDeviceDraggedEvent:event atPoint:interactionPoint];
815        handledEvent |= handled;
816    }
817
818    return handledEvent;
819}
820
821-(BOOL)pointingDeviceCancelledEvent:(id)event
822{
823    // Plots
824    for ( CPTPlot *plot in [self.plots reverseObjectEnumerator] ) {
825        if ( [plot pointingDeviceCancelledEvent:event] ) return YES;
826    }
827
828    // Axes Set
829    if ( [self.axisSet pointingDeviceCancelledEvent:event] ) return YES;
830
831    // Plot area
832    if ( [self.plotAreaFrame pointingDeviceCancelledEvent:event] ) return YES;
833
834    // Plot spaces
835    BOOL handledEvent = NO;
836    for ( CPTPlotSpace *space in self.plotSpaces ) {
837        BOOL handled = [space pointingDeviceCancelledEvent:event];
838        handledEvent |= handled;
839    }
840
841    return handledEvent;
842}
843
844@end
845
846#pragma mark -
847
848@implementation CPTGraph(AbstractFactoryMethods)
849
850/**	@brief Creates a new plot space for the graph.
851 *	@return A new plot space.
852 **/
853-(CPTPlotSpace *)newPlotSpace
854{
855	return nil;
856}
857
858/**	@brief Creates a new axis set for the graph.
859 *	@return A new axis set.
860 **/
861-(CPTAxisSet *)newAxisSet
862{
863	return nil;
864}
865
866@end
867