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