1#import "CPTAxis.h"
2#import "CPTAxisLabelGroup.h"
3#import "CPTAxisSet.h"
4#import "CPTFill.h"
5#import "CPTGridLineGroup.h"
6#import "CPTLineStyle.h"
7#import "CPTPlotGroup.h"
8#import "CPTPlotArea.h"
9
10static const int kCPTNumberOfLayers = 6;	// number of primary layers to arrange
11
12/**	@cond */
13@interface CPTPlotArea()
14
15@property (nonatomic, readwrite, assign) CPTGraphLayerType *bottomUpLayerOrder;
16@property (nonatomic, readwrite, assign, getter=isUpdatingLayers) BOOL updatingLayers;
17
18-(void)updateLayerOrder;
19-(unsigned)indexForLayerType:(CPTGraphLayerType)layerType;
20
21@end
22/**	@endcond */
23
24#pragma mark -
25
26/** @brief A layer representing the actual plotting area of a graph.
27 *
28 *	All plots are drawn inside this area while axes, titles, and borders may fall outside.
29 *	The layers are arranged so that the graph elements are drawn in the following order:
30 *	-# Background fill
31 *	-# Minor grid lines
32 *	-# Major grid lines
33 *	-# Background border
34 *	-# Axis lines with major and minor tick marks
35 *	-# Plots
36 *	-# Axis labels
37 *	-# Axis titles
38 **/
39@implementation CPTPlotArea
40
41/** @property minorGridLineGroup
42 *	@brief The parent layer for all minor grid lines.
43 **/
44@synthesize minorGridLineGroup;
45
46/** @property majorGridLineGroup
47 *	@brief The parent layer for all major grid lines.
48 **/
49@synthesize majorGridLineGroup;
50
51/** @property axisSet
52 *	@brief The axis set.
53 **/
54@synthesize axisSet;
55
56/** @property plotGroup
57 *	@brief The plot group.
58 **/
59@synthesize plotGroup;
60
61/** @property axisLabelGroup
62 *	@brief The parent layer for all axis labels.
63 **/
64@synthesize axisLabelGroup;
65
66/** @property axisTitleGroup
67 *	@brief The parent layer for all axis titles.
68 **/
69@synthesize axisTitleGroup;
70
71/** @property topDownLayerOrder
72 *	@brief An array of graph layers to be drawn in an order other than the default.
73 *
74 *	The array should reference the layers using the constants defined in #CPTGraphLayerType.
75 *	Layers should be specified in order starting from the top layer.
76 *	Only the layers drawn out of the default order need be specified; all others will
77 *	automatically be placed at the bottom of the view in their default order.
78 *
79 *	If this property is nil, the layers will be drawn in the default order (bottom to top):
80 *	-# Minor grid lines
81 *	-# Major grid lines
82 *	-# Axis lines, including the tick marks
83 *	-# Plots
84 *	-# Axis labels
85 *	-# Axis titles
86 *
87 *	Example usage:
88 *	<code>[graph setTopDownLayerOrder:[NSArray arrayWithObjects:
89 *	[NSNumber numberWithInt:CPTGraphLayerTypePlots],
90 *	[NSNumber numberWithInt:CPTGraphLayerTypeAxisLabels],
91 *	[NSNumber numberWithInt:CPTGraphLayerTypeMajorGridLines],
92 *	..., nil]];</code>
93 **/
94@synthesize topDownLayerOrder;
95
96/** @property borderLineStyle
97 *	@brief The line style for the layer border.
98 *	If nil, the border is not drawn.
99 **/
100@dynamic borderLineStyle;
101
102/** @property fill
103 *	@brief The fill for the layer background.
104 *	If nil, the layer background is not filled.
105 **/
106@synthesize fill;
107
108// Private properties
109@synthesize bottomUpLayerOrder;
110@synthesize updatingLayers;
111
112#pragma mark -
113#pragma mark Init/Dealloc
114
115-(id)initWithFrame:(CGRect)newFrame
116{
117	if ( (self = [super initWithFrame:newFrame]) ) {
118		minorGridLineGroup = nil;
119		majorGridLineGroup = nil;
120		axisSet = nil;
121		plotGroup = nil;
122		axisLabelGroup = nil;
123		axisTitleGroup = nil;
124		fill = nil;
125		topDownLayerOrder = nil;
126		bottomUpLayerOrder = malloc(kCPTNumberOfLayers * sizeof(CPTGraphLayerType));
127		[self updateLayerOrder];
128
129		CPTPlotGroup *newPlotGroup = [(CPTPlotGroup *)[CPTPlotGroup alloc] initWithFrame:newFrame];
130		self.plotGroup = newPlotGroup;
131		[newPlotGroup release];
132
133		self.needsDisplayOnBoundsChange = YES;
134	}
135	return self;
136}
137
138-(id)initWithLayer:(id)layer
139{
140	if ( (self = [super initWithLayer:layer]) ) {
141		CPTPlotArea *theLayer = (CPTPlotArea *)layer;
142
143		minorGridLineGroup = [theLayer->minorGridLineGroup retain];
144		majorGridLineGroup = [theLayer->majorGridLineGroup retain];
145		axisSet = [theLayer->axisSet retain];
146		plotGroup = [theLayer->plotGroup retain];
147		axisLabelGroup = [theLayer->axisLabelGroup retain];
148		axisTitleGroup = [theLayer->axisTitleGroup retain];
149		fill = [theLayer->fill retain];
150		topDownLayerOrder = [theLayer->topDownLayerOrder retain];
151		bottomUpLayerOrder = malloc(kCPTNumberOfLayers * sizeof(CPTGraphLayerType));
152		memcpy(bottomUpLayerOrder, theLayer->bottomUpLayerOrder, kCPTNumberOfLayers * sizeof(CPTGraphLayerType));
153	}
154	return self;
155}
156
157-(void)dealloc
158{
159	[minorGridLineGroup release];
160	[majorGridLineGroup release];
161	[axisSet release];
162	[plotGroup release];
163	[axisLabelGroup release];
164	[axisTitleGroup release];
165	[fill release];
166	[topDownLayerOrder release];
167	free(bottomUpLayerOrder);
168
169	[super dealloc];
170}
171
172-(void)finalize
173{
174	free(bottomUpLayerOrder);
175	[super finalize];
176}
177
178#pragma mark -
179#pragma mark NSCoding methods
180
181-(void)encodeWithCoder:(NSCoder *)coder
182{
183	[super encodeWithCoder:coder];
184
185	[coder encodeObject:self.minorGridLineGroup forKey:@"CPTPlotArea.minorGridLineGroup"];
186	[coder encodeObject:self.majorGridLineGroup forKey:@"CPTPlotArea.majorGridLineGroup"];
187	[coder encodeObject:self.axisSet forKey:@"CPTPlotArea.axisSet"];
188	[coder encodeObject:self.plotGroup forKey:@"CPTPlotArea.plotGroup"];
189	[coder encodeObject:self.axisLabelGroup forKey:@"CPTPlotArea.axisLabelGroup"];
190	[coder encodeObject:self.axisTitleGroup forKey:@"CPTPlotArea.axisTitleGroup"];
191	[coder encodeObject:self.fill forKey:@"CPTPlotArea.fill"];
192	[coder encodeObject:self.topDownLayerOrder forKey:@"CPTPlotArea.topDownLayerOrder"];
193
194	// No need to archive these properties:
195	// bottomUpLayerOrder
196	// updatingLayers
197}
198
199-(id)initWithCoder:(NSCoder *)coder
200{
201    if ( (self = [super initWithCoder:coder]) ) {
202		minorGridLineGroup = [[coder decodeObjectForKey:@"CPTPlotArea.minorGridLineGroup"] retain];
203		majorGridLineGroup = [[coder decodeObjectForKey:@"CPTPlotArea.majorGridLineGroup"] retain];
204		axisSet = [[coder decodeObjectForKey:@"CPTPlotArea.axisSet"] retain];
205		plotGroup = [[coder decodeObjectForKey:@"CPTPlotArea.plotGroup"] retain];
206		axisLabelGroup = [[coder decodeObjectForKey:@"CPTPlotArea.axisLabelGroup"] retain];
207		axisTitleGroup = [[coder decodeObjectForKey:@"CPTPlotArea.axisTitleGroup"] retain];
208		fill = [[coder decodeObjectForKey:@"CPTPlotArea.fill"] copy];
209		topDownLayerOrder = [[coder decodeObjectForKey:@"CPTPlotArea.topDownLayerOrder"] retain];
210
211		bottomUpLayerOrder = malloc(kCPTNumberOfLayers * sizeof(CPTGraphLayerType));
212		[self updateLayerOrder];
213}
214    return self;
215}
216
217#pragma mark -
218#pragma mark Drawing
219
220-(void)renderAsVectorInContext:(CGContextRef)context
221{
222	if ( self.hidden ) return;
223
224	[super renderAsVectorInContext:context];
225
226	[self.fill fillRect:self.bounds inContext:context];
227
228	NSArray *theAxes = self.axisSet.axes;
229
230	for ( CPTAxis *axis in theAxes ) {
231		[axis drawBackgroundBandsInContext:context];
232	}
233	for ( CPTAxis *axis in theAxes ) {
234		[axis drawBackgroundLimitsInContext:context];
235	}
236}
237
238#pragma mark -
239#pragma mark Layout
240
241-(void)layoutSublayers
242{
243	[super layoutSublayers];
244
245	CALayer *superlayer = self.superlayer;
246	CGRect sublayerBounds = [self convertRect:superlayer.bounds fromLayer:superlayer];
247	sublayerBounds.origin = CGPointZero;
248	CGPoint sublayerPosition = [self convertPoint:self.bounds.origin toLayer:superlayer];
249	sublayerPosition = CGPointMake(-sublayerPosition.x, -sublayerPosition.y);
250
251    NSSet *excludedLayers = [self sublayersExcludedFromAutomaticLayout];
252	for (CALayer *subLayer in self.sublayers) {
253    	if ( [excludedLayers containsObject:subLayer] ) continue;
254		subLayer.frame = CGRectMake(sublayerPosition.x, sublayerPosition.y, sublayerBounds.size.width, sublayerBounds.size.height);
255	}
256
257	// make the plot group the same size as the plot area to clip the plots
258	CPTPlotGroup *thePlotGroup = self.plotGroup;
259	if ( thePlotGroup ) {
260		CGSize selfBoundsSize = self.bounds.size;
261		thePlotGroup.frame = CGRectMake(0.0, 0.0, selfBoundsSize.width, selfBoundsSize.height);
262	}
263}
264
265#pragma mark -
266#pragma mark Layer ordering
267
268-(void)updateLayerOrder
269{
270	CPTGraphLayerType *buLayerOrder = self.bottomUpLayerOrder;
271	for ( int i = 0; i < kCPTNumberOfLayers; i++ ) {
272		*(buLayerOrder++) = i;
273	}
274
275	NSArray *tdLayerOrder = self.topDownLayerOrder;
276	if ( tdLayerOrder ) {
277		buLayerOrder = self.bottomUpLayerOrder;
278
279		for ( NSUInteger layerIndex = 0; layerIndex < [tdLayerOrder count]; layerIndex++ ) {
280			CPTGraphLayerType layerType = [[tdLayerOrder objectAtIndex:layerIndex] intValue];
281			NSUInteger i = kCPTNumberOfLayers - layerIndex - 1;
282			while ( buLayerOrder[i] != layerType ) {
283				if ( i == 0 ) break;
284				i--;
285			}
286			while ( i < kCPTNumberOfLayers - layerIndex - 1 ) {
287				buLayerOrder[i] = buLayerOrder[i + 1];
288				i++;
289			}
290			buLayerOrder[kCPTNumberOfLayers - layerIndex - 1] = layerType;
291		}
292	}
293
294	// force the layer hierarchy to update
295	self.updatingLayers = YES;
296	self.minorGridLineGroup = self.minorGridLineGroup;
297	self.majorGridLineGroup = self.majorGridLineGroup;
298	self.axisSet = self.axisSet;
299	self.plotGroup = self.plotGroup;
300	self.axisLabelGroup = self.axisLabelGroup;
301	self.axisTitleGroup = self.axisTitleGroup;
302	self.updatingLayers = NO;
303}
304
305-(unsigned)indexForLayerType:(CPTGraphLayerType)layerType
306{
307	CPTGraphLayerType *buLayerOrder = self.bottomUpLayerOrder;
308	unsigned index = 0;
309
310	for ( NSInteger i = 0; i < kCPTNumberOfLayers; i++ ) {
311		if ( buLayerOrder[i] == layerType ) {
312			break;
313		}
314		switch ( buLayerOrder[i] ) {
315			case CPTGraphLayerTypeMinorGridLines:
316				if ( self.minorGridLineGroup ) index++;
317				break;
318			case CPTGraphLayerTypeMajorGridLines:
319				if ( self.majorGridLineGroup ) index++;
320				break;
321			case CPTGraphLayerTypeAxisLines:
322				if ( self.axisSet ) index++;
323				break;
324			case CPTGraphLayerTypePlots:
325				if ( self.plotGroup ) index++;
326				break;
327			case CPTGraphLayerTypeAxisLabels:
328				if ( self.axisLabelGroup ) index++;
329				break;
330			case CPTGraphLayerTypeAxisTitles:
331				if ( self.axisTitleGroup ) index++;
332				break;
333		}
334	}
335	return index;
336}
337
338#pragma mark -
339#pragma mark Axis set layer management
340
341/**	@brief Checks for the presence of the specified layer group and adds or removes it as needed.
342 *	@param layerType The layer type being updated.
343 **/
344-(void)updateAxisSetLayersForType:(CPTGraphLayerType)layerType
345{
346	BOOL needsLayer = NO;
347	CPTAxisSet *theAxisSet = self.axisSet;
348	for ( CPTAxis *axis in theAxisSet.axes ) {
349		switch ( layerType ) {
350			case CPTGraphLayerTypeMinorGridLines:
351				if ( axis.minorGridLineStyle ) {
352					needsLayer = YES;
353				}
354				break;
355			case CPTGraphLayerTypeMajorGridLines:
356				if ( axis.majorGridLineStyle ) {
357					needsLayer = YES;
358				}
359				break;
360			case CPTGraphLayerTypeAxisLabels:
361				if ( axis.axisLabels.count > 0 ) {
362					needsLayer = YES;
363				}
364				break;
365			case CPTGraphLayerTypeAxisTitles:
366				if ( axis.axisTitle ) {
367					needsLayer = YES;
368				}
369				break;
370			default:
371				break;
372		}
373	}
374
375	if ( needsLayer ) {
376		[self setAxisSetLayersForType:layerType];
377	}
378	else {
379		switch ( layerType ) {
380			case CPTGraphLayerTypeMinorGridLines:
381				self.minorGridLineGroup = nil;
382				break;
383			case CPTGraphLayerTypeMajorGridLines:
384				self.majorGridLineGroup = nil;
385				break;
386			case CPTGraphLayerTypeAxisLabels:
387				self.axisLabelGroup = nil;
388				break;
389			case CPTGraphLayerTypeAxisTitles:
390				self.axisTitleGroup = nil;
391				break;
392			default:
393				break;
394		}
395	}
396
397}
398
399/**	@brief Ensures that a group layer is set for the given layer type.
400 *	@param layerType The layer type being updated.
401 **/
402-(void)setAxisSetLayersForType:(CPTGraphLayerType)layerType
403{
404	switch ( layerType ) {
405		case CPTGraphLayerTypeMinorGridLines:
406			if ( !self.minorGridLineGroup ) {
407				CPTGridLineGroup *newGridLineGroup = [(CPTGridLineGroup *)[CPTGridLineGroup alloc] initWithFrame:self.bounds];
408				self.minorGridLineGroup = newGridLineGroup;
409				[newGridLineGroup release];
410			}
411			break;
412		case CPTGraphLayerTypeMajorGridLines:
413			if ( !self.majorGridLineGroup ) {
414				CPTGridLineGroup *newGridLineGroup = [(CPTGridLineGroup *)[CPTGridLineGroup alloc] initWithFrame:self.bounds];
415				self.majorGridLineGroup = newGridLineGroup;
416				[newGridLineGroup release];
417			}
418			break;
419		case CPTGraphLayerTypeAxisLabels:
420			if ( !self.axisLabelGroup ) {
421				CPTAxisLabelGroup *newAxisLabelGroup = [(CPTAxisLabelGroup *)[CPTAxisLabelGroup alloc] initWithFrame:self.bounds];
422				self.axisLabelGroup = newAxisLabelGroup;
423				[newAxisLabelGroup release];
424			}
425			break;
426		case CPTGraphLayerTypeAxisTitles:
427			if ( !self.axisTitleGroup ) {
428				CPTAxisLabelGroup *newAxisTitleGroup = [(CPTAxisLabelGroup *)[CPTAxisLabelGroup alloc] initWithFrame:self.bounds];
429				self.axisTitleGroup = newAxisTitleGroup;
430				[newAxisTitleGroup release];
431			}
432			break;
433		default:
434			break;
435	}
436}
437
438/**	@brief Computes the sublayer index for the given layer type and axis.
439 *	@param axis The axis of interest.
440 *	@param layerType The layer type being updated.
441 *	@return The sublayer index for the given layer type.
442 **/
443-(unsigned)sublayerIndexForAxis:(CPTAxis *)axis layerType:(CPTGraphLayerType)layerType
444{
445	unsigned index = 0;
446
447	for ( CPTAxis *currentAxis in self.graph.axisSet.axes ) {
448		if ( currentAxis == axis ) break;
449
450		switch ( layerType ) {
451			case CPTGraphLayerTypeMinorGridLines:
452				if ( currentAxis.minorGridLineStyle ) index++;
453				break;
454			case CPTGraphLayerTypeMajorGridLines:
455				if ( currentAxis.majorGridLineStyle ) index++;
456				break;
457			case CPTGraphLayerTypeAxisLabels:
458				if ( currentAxis.axisLabels.count > 0 ) index++;
459				break;
460			case CPTGraphLayerTypeAxisTitles:
461				if ( currentAxis.axisTitle ) index++;
462				break;
463			default:
464				break;
465		}
466	}
467
468	return index;
469}
470
471#pragma mark -
472#pragma mark Accessors
473
474-(CPTLineStyle *)borderLineStyle
475{
476	return self.axisSet.borderLineStyle;
477}
478
479-(void)setBorderLineStyle:(CPTLineStyle *)newLineStyle
480{
481	self.axisSet.borderLineStyle = newLineStyle;
482}
483
484-(void)setMinorGridLineGroup:(CPTGridLineGroup *)newGridLines
485{
486	if ( (newGridLines != minorGridLineGroup) || self.isUpdatingLayers ) {
487		[minorGridLineGroup removeFromSuperlayer];
488		[newGridLines retain];
489		[minorGridLineGroup release];
490		minorGridLineGroup = newGridLines;
491		if ( minorGridLineGroup ) {
492			minorGridLineGroup.plotArea = self;
493			minorGridLineGroup.major = NO;
494			[self insertSublayer:minorGridLineGroup atIndex:[self indexForLayerType:CPTGraphLayerTypeMinorGridLines]];
495		}
496        [self setNeedsLayout];
497	}
498}
499
500-(void)setMajorGridLineGroup:(CPTGridLineGroup *)newGridLines
501{
502	if ( (newGridLines != majorGridLineGroup) || self.isUpdatingLayers ) {
503		[majorGridLineGroup removeFromSuperlayer];
504		[newGridLines retain];
505		[majorGridLineGroup release];
506		majorGridLineGroup = newGridLines;
507		if ( majorGridLineGroup ) {
508			majorGridLineGroup.plotArea = self;
509			majorGridLineGroup.major = YES;
510			[self insertSublayer:majorGridLineGroup atIndex:[self indexForLayerType:CPTGraphLayerTypeMajorGridLines]];
511		}
512        [self setNeedsLayout];
513	}
514}
515
516-(void)setAxisSet:(CPTAxisSet *)newAxisSet
517{
518	if ( (newAxisSet != axisSet) || self.isUpdatingLayers ) {
519		[axisSet removeFromSuperlayer];
520		for ( CPTAxis *axis in axisSet.axes ) {
521			axis.plotArea = nil;
522		}
523
524		[newAxisSet retain];
525		[axisSet release];
526		axisSet = newAxisSet;
527		[self updateAxisSetLayersForType:CPTGraphLayerTypeMajorGridLines];
528		[self updateAxisSetLayersForType:CPTGraphLayerTypeMinorGridLines];
529		[self updateAxisSetLayersForType:CPTGraphLayerTypeAxisLabels];
530		[self updateAxisSetLayersForType:CPTGraphLayerTypeAxisTitles];
531
532		if ( axisSet ) {
533			[self insertSublayer:axisSet atIndex:[self indexForLayerType:CPTGraphLayerTypeAxisLines]];
534			for ( CPTAxis *axis in axisSet.axes ) {
535				axis.plotArea = self;
536			}
537		}
538        [self setNeedsLayout];
539	}
540}
541
542-(void)setPlotGroup:(CPTPlotGroup *)newPlotGroup
543{
544	if ( (newPlotGroup != plotGroup) || self.isUpdatingLayers ) {
545		[plotGroup removeFromSuperlayer];
546		[newPlotGroup retain];
547		[plotGroup release];
548		plotGroup = newPlotGroup;
549		if ( plotGroup ) {
550			[self insertSublayer:plotGroup atIndex:[self indexForLayerType:CPTGraphLayerTypePlots]];
551		}
552        [self setNeedsLayout];
553	}
554}
555
556-(void)setAxisLabelGroup:(CPTAxisLabelGroup *)newAxisLabelGroup
557{
558	if ( (newAxisLabelGroup != axisLabelGroup) || self.isUpdatingLayers ) {
559		[axisLabelGroup removeFromSuperlayer];
560		[newAxisLabelGroup retain];
561		[axisLabelGroup release];
562		axisLabelGroup = newAxisLabelGroup;
563		if ( axisLabelGroup ) {
564			[self insertSublayer:axisLabelGroup atIndex:[self indexForLayerType:CPTGraphLayerTypeAxisLabels]];
565		}
566        [self setNeedsLayout];
567	}
568}
569
570-(void)setAxisTitleGroup:(CPTAxisLabelGroup *)newAxisTitleGroup
571{
572	if ( (newAxisTitleGroup != axisTitleGroup) || self.isUpdatingLayers ) {
573		[axisTitleGroup removeFromSuperlayer];
574		[newAxisTitleGroup retain];
575		[axisTitleGroup release];
576		axisTitleGroup = newAxisTitleGroup;
577		if ( axisTitleGroup ) {
578			[self insertSublayer:axisTitleGroup atIndex:[self indexForLayerType:CPTGraphLayerTypeAxisTitles]];
579		}
580        [self setNeedsLayout];
581	}
582}
583
584-(void)setTopDownLayerOrder:(NSArray *)newArray
585{
586	if ( newArray != topDownLayerOrder) {
587		[topDownLayerOrder release];
588		topDownLayerOrder = [newArray retain];
589		[self updateLayerOrder];
590	}
591}
592
593@end
594