diff --git a/Example 1/Classes/Controller/PSHTreeGraphViewController.m b/Example 1/Classes/Controller/PSHTreeGraphViewController.m index e992e34..021c3e3 100644 --- a/Example 1/Classes/Controller/PSHTreeGraphViewController.m +++ b/Example 1/Classes/Controller/PSHTreeGraphViewController.m @@ -44,6 +44,8 @@ - (void) setRootClassName:(NSString *)newRootClassName [rootClassName_ release]; rootClassName_ = [newRootClassName copy]; + treeGraphView_.treeGraphOrientation = PSTreeGraphOrientationStyleHorizontalFlipped; + // Get an ObjCClassWrapper for the named Objective-C Class, and set it as the TreeGraph's root. [treeGraphView_ setModelRoot:[ObjCClassWrapper wrapperForClassNamed:rootClassName_]]; } diff --git a/PSTreeGraphView/PSBaseBranchView.m b/PSTreeGraphView/PSBaseBranchView.m index ee99514..4b17c50 100644 --- a/PSTreeGraphView/PSBaseBranchView.m +++ b/PSTreeGraphView/PSBaseBranchView.m @@ -50,7 +50,8 @@ - (UIBezierPath *) directConnectionsPath PSTreeGraphOrientationStyle treeDirection = [[self enclosingTreeGraph] treeGraphOrientation]; - if ( treeDirection == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeDirection == PSTreeGraphOrientationStyleHorizontal ) || + ( treeDirection == PSTreeGraphOrientationStyleHorizontalFlipped )){ rootPoint = CGPointMake(CGRectGetMinX(bounds), CGRectGetMidY(bounds)); } else { @@ -70,7 +71,8 @@ - (UIBezierPath *) directConnectionsPath CGRect subviewBounds = [subview bounds]; CGPoint targetPoint = CGPointZero; - if ( treeDirection == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeDirection == PSTreeGraphOrientationStyleHorizontal ) || + ( treeDirection == PSTreeGraphOrientationStyleHorizontalFlipped )){ targetPoint = [self convertPoint:CGPointMake(CGRectGetMinX(subviewBounds), CGRectGetMidY(subviewBounds)) fromView:subview]; } else { @@ -116,6 +118,14 @@ - (UIBezierPath *) orthogonalConnectionsPath // Compute point at right edge of root node, from which its connecting line to the vertical line will emerge. rootPoint = CGPointMake(CGRectGetMinX(bounds), CGRectGetMidY(bounds)); + } else if ( treeDirection == PSTreeGraphOrientationStyleHorizontalFlipped ){ + // Compute point at left edge of root node, from which its connecting line to the vertical line will emerge. + rootPoint = CGPointMake(CGRectGetMaxX(bounds), + CGRectGetMidY(bounds)); + } else if ( treeDirection == PSTreeGraphOrientationStyleVerticalFlipped ){ + // Compute point at top edge of root node, from which its connecting line to the vertical line will emerge. + rootPoint = CGPointMake(CGRectGetMidX(bounds), + CGRectGetMaxY(bounds)); } else { rootPoint = CGPointMake(CGRectGetMidX(bounds), CGRectGetMinY(bounds)); @@ -165,7 +175,8 @@ - (UIBezierPath *) orthogonalConnectionsPath CGRect subviewBounds = [subview bounds]; CGPoint targetPoint = CGPointZero; - if ( treeDirection == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeDirection == PSTreeGraphOrientationStyleHorizontal ) || + ( treeDirection == PSTreeGraphOrientationStyleHorizontalFlipped )){ targetPoint = [self convertPoint:CGPointMake(CGRectGetMinX(subviewBounds), CGRectGetMidY(subviewBounds)) fromView:subview]; } else { @@ -181,7 +192,8 @@ - (UIBezierPath *) orthogonalConnectionsPath // TODO: Make clean line joins (test at high values of line thickness to see the problem). - if ( treeDirection == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeDirection == PSTreeGraphOrientationStyleHorizontal ) || + ( treeDirection == PSTreeGraphOrientationStyleHorizontalFlipped )){ [path moveToPoint:CGPointMake(rootIntersection.x, targetPoint.y)]; if (minY > targetPoint.y) { @@ -212,7 +224,8 @@ - (UIBezierPath *) orthogonalConnectionsPath [path moveToPoint:rootPoint]; [path addLineToPoint:rootIntersection]; - if ( treeDirection == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeDirection == PSTreeGraphOrientationStyleHorizontal ) || + ( treeDirection == PSTreeGraphOrientationStyleHorizontalFlipped )){ // Add a stroke for the vertical connecting line. [path moveToPoint:CGPointMake(rootIntersection.x, minY)]; [path addLineToPoint:CGPointMake(rootIntersection.x, maxY)]; diff --git a/PSTreeGraphView/PSBaseSubtreeView.h b/PSTreeGraphView/PSBaseSubtreeView.h index c0f8876..b9f6ad8 100644 --- a/PSTreeGraphView/PSBaseSubtreeView.h +++ b/PSTreeGraphView/PSBaseSubtreeView.h @@ -74,6 +74,9 @@ - (CGSize) layoutGraphIfNeeded; +// Flip the treeGraph end for end (or top for bottom) +- (void) flipTreeGraph; + /// Resizes this subtree's nodeView to the minimum size required to hold its content, and returns the nodeView's /// new size. (This currently does nothing, and is just stubbed out for future use.) diff --git a/PSTreeGraphView/PSBaseSubtreeView.m b/PSTreeGraphView/PSBaseSubtreeView.m index ffcf8bd..c066413 100644 --- a/PSTreeGraphView/PSBaseSubtreeView.m +++ b/PSTreeGraphView/PSBaseSubtreeView.m @@ -197,6 +197,35 @@ - (CGSize) sizeNodeViewToFitContent return [self.nodeView frame].size; } +- (void) flipTreeGraph +{ + // Recurse for descendant SubtreeViews. + CGFloat myWidth = self.frame.size.width; + CGFloat myHeight = self.frame.size.height; + PSBaseTreeGraphView *treeGraph = [self enclosingTreeGraph]; + PSTreeGraphOrientationStyle treeOrientation = [treeGraph treeGraphOrientation]; + + NSArray *subviews = [self subviews]; + for (UIView *subview in subviews) { + CGPoint subviewCenter = subview.center; + CGPoint newCenter; + CGFloat offset; + if (treeOrientation == PSTreeGraphOrientationStyleHorizontalFlipped ){ + offset = subviewCenter.x; + newCenter = CGPointMake(myWidth-offset, subviewCenter.y); + } + else{ + offset = subviewCenter.y; + newCenter = CGPointMake(subviewCenter.x, myHeight-offset); + } + subview.center = newCenter; + if ([subview isKindOfClass:[PSBaseSubtreeView class]]) { + [(PSBaseSubtreeView *)subview flipTreeGraph]; + } + } +} + + - (CGSize) layoutGraphIfNeeded { // Return early if layout not needed @@ -246,7 +275,8 @@ - (CGSize) layoutExpandedGraph CGFloat maxHeight = 0.0f; CGPoint nextSubtreeViewOrigin = CGPointZero; - if ( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( treeOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )){ nextSubtreeViewOrigin = CGPointMake(rootNodeViewSize.width + parentChildSpacing, 0.0f); } else { nextSubtreeViewOrigin = CGPointMake(0.0f, rootNodeViewSize.height + parentChildSpacing); @@ -267,7 +297,8 @@ - (CGSize) layoutExpandedGraph // Position the SubtreeView. // [(animateLayout ? [subview animator] : subview) setFrameOrigin:nextSubtreeViewOrigin]; - if ( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) || + (treeOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )) { // Since SubtreeView is unflipped, lay out our child SubtreeViews going upward from our // bottom edge, from last to first. subview.frame = CGRectMake( nextSubtreeViewOrigin.x, @@ -308,7 +339,8 @@ - (CGSize) layoutExpandedGraph CGFloat totalHeight = 0.0f; CGFloat totalWidth = 0.0f; - if ( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) || + (treeOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )) { totalHeight = nextSubtreeViewOrigin.y; if (subtreeViewCount > 0) { totalHeight -= siblingSpacing; @@ -325,7 +357,8 @@ - (CGSize) layoutExpandedGraph if (subtreeViewCount > 0) { // Determine our width and height. - if ( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( treeOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )) { selfTargetSize = CGSizeMake(rootNodeViewSize.width + parentChildSpacing + maxWidth, MAX(totalHeight, rootNodeViewSize.height) ); } else { @@ -341,7 +374,8 @@ - (CGSize) layoutExpandedGraph selfTargetSize.height ); CGPoint nodeViewOrigin = CGPointZero; - if ( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( treeOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )){ // Position our nodeView vertically centered along the left edge of our new bounds. nodeViewOrigin = CGPointMake(0.0f, 0.5f * (selfTargetSize.height - rootNodeViewSize.height)); @@ -372,7 +406,8 @@ - (CGSize) layoutExpandedGraph // [_connectorsView setContentMode:UIViewContentModeScaleToFill ]; - if ( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( treeOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )){ connectorsView_.frame = CGRectMake(rootNodeViewSize.width, 0.0f, parentChildSpacing, @@ -444,7 +479,8 @@ - (CGSize) layoutCollapsedGraph // [_connectorsView setContentMode:UIViewContentModeScaleToFill ]; PSTreeGraphOrientationStyle treeOrientation = [[self enclosingTreeGraph] treeGraphOrientation]; - if ( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( treeOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( treeOrientation == PSTreeGraphOrientationStyleHorizontal )){ connectorsView_.frame = CGRectMake(0.0f, 0.5f * selfTargetSize.height, 0.0f, diff --git a/PSTreeGraphView/PSBaseTreeGraphView.h b/PSTreeGraphView/PSBaseTreeGraphView.h index 85d77ca..8a5959c 100644 --- a/PSTreeGraphView/PSBaseTreeGraphView.h +++ b/PSTreeGraphView/PSBaseTreeGraphView.h @@ -28,6 +28,8 @@ typedef enum PSTreeGraphConnectingLineStyle : NSUInteger { typedef enum PSTreeGraphOrientationStyle : NSUInteger { PSTreeGraphOrientationStyleHorizontal = 0, PSTreeGraphOrientationStyleVertical = 1, + PSTreeGraphOrientationStyleHorizontalFlipped = 2, + PSTreeGraphOrientationStyleVerticalFlipped = 3, } PSTreeGraphOrientationStyle; @@ -148,6 +150,12 @@ typedef enum PSTreeGraphOrientationStyle : NSUInteger { @property (nonatomic, assign) PSTreeGraphOrientationStyle treeGraphOrientation; +/// Is the TreeGraph flipped +/// Flipped means the graph is drawn with the branches to the left or top and the root node +/// to the right or bottom. Default is NO + +@property (nonatomic, assign) BOOL treeGraphFlipped; + /// Returns YES if the tree needs relayout. - (BOOL) needsGraphLayout; diff --git a/PSTreeGraphView/PSBaseTreeGraphView.m b/PSTreeGraphView/PSBaseTreeGraphView.m index b902cf4..6d12df5 100644 --- a/PSTreeGraphView/PSBaseTreeGraphView.m +++ b/PSTreeGraphView/PSBaseTreeGraphView.m @@ -56,6 +56,7 @@ @interface PSBaseTreeGraphView () // Layout Behavior BOOL resizesToFillEnclosingScrollView_; PSTreeGraphOrientationStyle treeGraphOrientation_; + BOOL treeGraphFlipped_; // Styling // UIColor *backgroundColor; @@ -77,7 +78,7 @@ @interface PSBaseTreeGraphView () UIView *inputView_; } -- (void) configureDetaults; +- (void) configureDefaults; - (PSBaseSubtreeView *) newGraphForModelNode:(id )modelNode; - (void) buildGraph; - (void) updateFrameSizeForContentAndClipView; @@ -153,6 +154,16 @@ - (void) setTreeGraphOrientation:(PSTreeGraphOrientationStyle)newTreeGraphOrient } } +@synthesize treeGraphFlipped = treeGraphFlipped_; + +- (void) setTreeGraphFlipped:(BOOL)newTreeGraphFlipped +{ + if (treeGraphFlipped_ != newTreeGraphFlipped) { + treeGraphFlipped_ = newTreeGraphFlipped; + [[self rootSubtreeView] recursiveSetConnectorsViewsNeedDisplay]; + } +} + @synthesize connectingLineStyle = connectingLineStyle_; - (void) setConnectingLineStyle:(PSTreeGraphConnectingLineStyle)newConnectingLineStyle @@ -196,7 +207,7 @@ - (void) setShowsSubtreeFrames:(BOOL)newShowsSubtreeFrames #pragma mark - Initialization -- (void) configureDetaults +- (void) configureDefaults { [self setBackgroundColor: [UIColor colorWithRed:0.55 green:0.76 blue:0.93 alpha:1.0]]; //[self setClipsToBounds:YES]; @@ -211,6 +222,7 @@ - (void) configureDetaults siblingSpacing_ = 10.0; animatesLayout_ = YES; resizesToFillEnclosingScrollView_ = YES; + treeGraphFlipped_ = NO; treeGraphOrientation_ = PSTreeGraphOrientationStyleHorizontal ; connectingLineStyle_ = PSTreeGraphConnectingLineStyleOrthogonal ; connectingLineWidth_ = 1.0; @@ -254,7 +266,7 @@ - (id) initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { - [self configureDetaults]; + [self configureDefaults]; } return self; } @@ -282,7 +294,7 @@ - (id) initWithCoder:(NSCoder *)decoder self = [super initWithCoder:decoder]; if (self) { - [self configureDetaults]; + [self configureDefaults]; if ([decoder containsValueForKey:@"animatesLayout"]) animatesLayout_ = [decoder decodeBoolForKey:@"animatesLayout"]; @@ -558,7 +570,8 @@ - (void) updateRootSubtreeViewPositionForSize:(CGSize)rootSubtreeViewSize if ( [self resizesToFillEnclosingScrollView] ) { CGRect bounds = [self bounds]; - if ( [self treeGraphOrientation] == PSTreeGraphOrientationStyleHorizontal ) { + if (( [self treeGraphOrientation] == PSTreeGraphOrientationStyleHorizontal ) || + ( [self treeGraphOrientation] == PSTreeGraphOrientationStyleHorizontalFlipped )){ newOrigin = CGPointMake([self contentMargin], 0.5 * (bounds.size.height - rootSubtreeViewSize.height)); } else { @@ -617,7 +630,11 @@ - (CGSize) layoutGraphIfNeeded // Position the TreeGraph's root SubtreeView. [self updateRootSubtreeViewPositionForSize:rootSubtreeViewSize]; - + + if (( [self treeGraphOrientation] == PSTreeGraphOrientationStyleHorizontalFlipped ) || + ( [self treeGraphOrientation] == PSTreeGraphOrientationStyleVerticalFlipped )){ + [rootSubtreeView flipTreeGraph]; + } return rootSubtreeViewSize; } else { return rootSubtreeView ? [rootSubtreeView frame].size : CGSizeZero; @@ -831,7 +848,8 @@ - (IBAction) moveToNearestChild:(id)sender if (nodeView) { CGRect nodeViewFrame = [nodeView frame]; id nearestChild = nil; - if ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )){ nearestChild = [subtreeView modelNodeClosestToY:CGRectGetMidY(nodeViewFrame)]; } else { nearestChild = [subtreeView modelNodeClosestToX:CGRectGetMidX(nodeViewFrame)]; @@ -852,7 +870,8 @@ - (IBAction) moveToNearestChild:(id)sender - (void) moveUp:(id)sender { - if ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )){ [self moveToSiblingByRelativeIndex:1]; } else { [self moveToParent:sender]; @@ -862,7 +881,8 @@ - (void) moveUp:(id)sender - (void) moveDown:(id)sender { - if ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )){ [self moveToSiblingByRelativeIndex:-1]; } else { [self moveToNearestChild:sender]; @@ -872,7 +892,8 @@ - (void) moveDown:(id)sender - (void) moveLeft:(id)sender { - if ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )){ [self moveToParent:sender]; } else { [self moveToSiblingByRelativeIndex:1]; @@ -882,7 +903,8 @@ - (void) moveLeft:(id)sender - (void) moveRight:(id)sender { - if ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) { + if (( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontal ) || + ( self.treeGraphOrientation == PSTreeGraphOrientationStyleHorizontalFlipped )){ [self moveToNearestChild:sender]; } else { [self moveToSiblingByRelativeIndex:-1];