Skip to content
This repository has been archived by the owner on Dec 2, 2020. It is now read-only.

Pan/Zoom and Variable X Axis; many other changes #300

Open
wants to merge 41 commits into
base: feature
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
59f25dd
Add encoder/decoder for BEMLineGraph properties
mackworth Mar 18, 2017
9bfe179
Add Types to all Arrays
mackworth Mar 18, 2017
7823735
Create new TestBed app to manipulate almost all parameters
mackworth Mar 19, 2017
7dc9f3a
Add colors, gradients, and alphas to TestBed
mackworth Mar 26, 2017
78c8cac
Fix assorted bugs in BEMSimpleLineGraph, especially null-data related
mackworth Mar 26, 2017
a10ad12
Allow use on iPhone and Split View
mackworth Mar 26, 2017
1169854
Fix bug with TouchLineInput color (doesn't change after initial setting)
mackworth Mar 26, 2017
019aa1e
Bug fixes:
mackworth Mar 28, 2017
1c14ccd
Supports bezier curves when line isn't being drawn
mackworth Mar 29, 2017
3ae0ea5
Using cubic instead of quadratic Bezier curves
mackworth Apr 1, 2017
43ff256
Simplifying code for controlPoints in cubicBezier
mackworth Apr 1, 2017
005c92c
Add variable X Axis support
mackworth Apr 5, 2017
5a795c6
Ensure all BEMnulls doesn't crash
mackworth Apr 7, 2017
346fc8b
Remove prev reference lines if axis turned off
mackworth Apr 7, 2017
6faa0f8
Remove prev reference lines if axis turned off
mackworth Apr 7, 2017
78d94cb
Remove prev reference lines if axis turned off
mackworth Apr 7, 2017
73714a6
Implement zoom/pan for data
mackworth Apr 9, 2017
3390172
Add user scaling (zoom/pan) to TestBed UI
mackworth Apr 9, 2017
45e9509
Delegate callback upon scale change
mackworth Apr 11, 2017
8bc4be6
Further implementation of pan/zoom
mackworth Apr 27, 2017
5606474
Better handle overlapping labels with large numbers of points
mackworth Apr 27, 2017
7b3678f
Add Area/Correlation to stats display
mackworth Apr 28, 2017
66cd74b
Major internal restructure
mackworth May 2, 2017
6478f2c
Properties restructure:
mackworth May 2, 2017
064170a
Merge NewBezierAlgo into NoOffsets
mackworth May 2, 2017
d615e0d
Fix cubic merge glitch
mackworth May 2, 2017
bf9d985
Don't add off-screen points to line (fixes animation and faster)
mackworth May 2, 2017
2607d5b
Move to point array versus dual number arrays
mackworth May 3, 2017
a69ef45
Implement xAxis at top Option
mackworth May 3, 2017
13b0172
Drop points off screen, but still include ones immediately offscreen …
mackworth May 6, 2017
fd993c0
Bug fixes:
mackworth May 7, 2017
5c5bcf3
Convert back to NSInteger in API for Swift compatibility
mackworth May 7, 2017
30dfcd8
Misc NSInteger type issues when compiling with strict and floats on 3…
mackworth May 10, 2017
773f5a7
Add ability to take screen shot bigger (or smaller) than screen
mackworth May 11, 2017
d416969
Handle one datapoint in regular routines, not as special case
mackworth May 12, 2017
7a712df
PR #295 fixes
mackworth May 15, 2017
d87e5c0
Update .travis.yml
mackworth May 15, 2017
a4b5155
Bugfixes:
mackworth May 20, 2017
7484033
Merge remote-tracking branch 'origin/variableX' into variableX
mackworth May 20, 2017
d04d190
Add adaptiveDataPoints property that lets calculations be based eithe…
mackworth May 29, 2017
0656b81
Better control over maxZoom
mackworth Jun 6, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Implement zoom/pan for data
Double tap to toggle full scale and zoomed
Remove touchInput recognizers when disabled
  • Loading branch information
mackworth committed Apr 9, 2017
commit 73714a6e5942cd72ababb46c92c130315289e3de
6 changes: 5 additions & 1 deletion Classes/BEMSimpleLineGraphView.h
Original file line number Diff line number Diff line change
@@ -156,7 +156,7 @@ IB_DESIGNABLE @interface BEMSimpleLineGraphView : UIView <UIGestureRecognizerDel
@property (nonatomic) BEMLineAnimation animationGraphStyle;


/// If set to YES, the graph will report the value of the closest point from the user current touch location. The 2 methods for touch event bellow should therefore be implemented. Default value is NO.
/// If set to YES, the graph will report the value of the closest point from the user current touch location. The 2 methods for touch event below should therefore be implemented. Default value is NO.
@property (nonatomic) BOOL enableTouchReport;


@@ -169,6 +169,10 @@ IB_DESIGNABLE @interface BEMSimpleLineGraphView : UIView <UIGestureRecognizerDel
@property (nonatomic) BOOL enablePopUpReport;


/// If set to YES, the graph will react to pinch/zoom gesture to allow user to focus on one section of data. When zoomed, panGesture means move graph and double-tap will restore scale. Default value is NO.
@property (nonatomic) BOOL enableUserScaling;


/// The way the graph is drawn, with or without bezier curved lines. Default value is NO.
@property (nonatomic) IBInspectable BOOL enableBezierCurve;

136 changes: 131 additions & 5 deletions Classes/BEMSimpleLineGraphView.m
Original file line number Diff line number Diff line change
@@ -93,6 +93,21 @@ @interface BEMSimpleLineGraphView () {
/// This gesture recognizer picks up the initial touch on the graph view
@property (strong, nonatomic) UILongPressGestureRecognizer *longPressGesture;

@property (strong, nonatomic) UIPinchGestureRecognizer *zoomGesture;

// set by zoomGesture to scale X axis
@property (nonatomic) CGFloat lastScale;
@property (nonatomic) CGFloat zoomAnchorPercentage;
@property (nonatomic) CGFloat zoomMovementBase;
@property (nonatomic) CGFloat zoomMovement;
@property (nonatomic) CGFloat currentScale;

//used to restore zoom
@property (strong, nonatomic) UITapGestureRecognizer *doubleTapGesture;
// set by doubleTap to remember previous scale
@property (nonatomic) CGFloat doubleTapScale;
@property (nonatomic) CGFloat doubleTapZoomMovement;

/// The label displayed when enablePopUpReport is set to YES
@property (strong, nonatomic) UILabel *popUpLabel;

@@ -160,6 +175,9 @@ -(void) restorePropertyWithCoder:(NSCoder *) coder {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wnullable-to-nonnull-conversion"

RestoreProperty (animationGraphEntranceTime, Float);
RestoreProperty (animationGraphStyle, Integer);

RestoreProperty (colorXaxisLabel, Object);
RestoreProperty (colorYaxisLabel, Object);
RestoreProperty (colorTop, Object);
@@ -185,6 +203,7 @@ -(void) restorePropertyWithCoder:(NSCoder *) coder {

RestoreProperty (enableTouchReport, Bool);
RestoreProperty (enablePopUpReport, Bool);
RestoreProperty (enableUserScaling, Bool);
RestoreProperty (enableBezierCurve, Bool);
RestoreProperty (enableXAxisLabel, Bool);
RestoreProperty (enableYAxisLabel, Bool);
@@ -247,6 +266,7 @@ -(void) encodePropertiesWithCoder: (NSCoder *) coder {

EncodeProperty (enableTouchReport, Bool);
EncodeProperty (enablePopUpReport, Bool);
EncodeProperty (enableUserScaling, Bool);
EncodeProperty (enableBezierCurve, Bool);
EncodeProperty (enableXAxisLabel, Bool);
EncodeProperty (enableYAxisLabel, Bool);
@@ -318,6 +338,12 @@ - (void)commonInit {
_formatStringForValues = @"%.0f";
_interpolateNullValues = YES;
_displayDotsOnly = NO;
_enableUserScaling = NO;
_lastScale = 1.0;
_zoomMovement = 0;
_zoomMovementBase = 0;
_doubleTapScale = 1.0;
_doubleTapZoomMovement = 0;

// Initialize the various arrays
xAxisLabelTexts = [NSMutableArray array];
@@ -354,6 +380,7 @@ - (void)drawGraph {

// Setup the touch report
[self layoutTouchReport];
[self startUserScaling];

// Let the delegate know that the graph finished updates
if ([self.delegate respondsToSelector:@selector(lineGraphDidFinishLoading:)])
@@ -371,6 +398,18 @@ - (void)layoutSubviews {
[self drawGraph];
}

-(UIView *) viewForFirstBaselineLayout {
//necessary for iOS 8.x
if ([super respondsToSelector:@selector(viewForFirstBaselineLayout)]) {
return [super viewForFirstBaselineLayout];
} else {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return [super viewForBaselineLayout];
#pragma clang diagnostic pop
}
}

-(void) clearGraph {
for (UIView * subvView in self.subviews) {
[subvView removeFromSuperview];
@@ -438,6 +477,34 @@ - (void)layoutNumberOfPoints {
}
}

- (void)startUserScaling {
if (self.enableUserScaling) {
if (!self.zoomGesture) {
self.lastScale = 1.0;
self.zoomGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handleZoomGestureAction:)];
self.zoomGesture.delegate = self;
[self.viewForFirstBaselineLayout addGestureRecognizer:self.zoomGesture];

self.doubleTapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapGestureAction:)];
self.doubleTapGesture.delegate = self;
self.doubleTapGesture.numberOfTapsRequired = 2;
[self.viewForFirstBaselineLayout addGestureRecognizer:self.doubleTapGesture];
}
} else {
self.lastScale = 1.0;
if (self.zoomGesture) {
self.zoomGesture.delegate = nil;
[self.viewForFirstBaselineLayout removeGestureRecognizer:self.zoomGesture];
self.zoomGesture = nil;
}
if (self.doubleTapGesture) {
self.doubleTapGesture.delegate = nil;
[self.viewForFirstBaselineLayout removeGestureRecognizer:self.doubleTapGesture];
self.doubleTapGesture = nil;
}
}
}

- (void)layoutTouchReport {
// If the touch report is enabled, set it up
if (self.enableTouchReport == YES || self.enablePopUpReport == YES) {
@@ -463,6 +530,18 @@ - (void)layoutTouchReport {
[self.panView addGestureRecognizer:self.longPressGesture];
}
[self addSubview:self.panView];
} else {
[self.touchInputLine removeFromSuperview];
if (self.panView) {
self.panGesture.delegate = nil;
[self.panView removeGestureRecognizer:self.panGesture];
self.panGesture = nil;
self.longPressGesture.delegate = nil;
[self.panView removeGestureRecognizer: self.longPressGesture];
self.longPressGesture = nil;
[self.panView removeFromSuperview];
self.panView = nil;
}
}
}

@@ -1285,7 +1364,13 @@ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
} else {
return NO;
}
return YES;
} else if ([gestureRecognizer isEqual:self.zoomGesture]) {
((UIPinchGestureRecognizer *)gestureRecognizer).scale = self.lastScale;
self.doubleTapScale = 1.0;
self.doubleTapZoomMovement = 0;
self.zoomMovementBase = [gestureRecognizer locationInView:self].x ;
self.zoomAnchorPercentage = self.zoomMovementBase / (self.frame.size.width - self.YAxisLabelXOffset);
return YES;
} else {
return [super gestureRecognizerShouldBegin:gestureRecognizer];
}
@@ -1299,6 +1384,36 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceive
return YES;
}

#pragma mark Handle zoom gesture
- (void)handleZoomGestureAction:(UIPinchGestureRecognizer *)recognizer {
if (recognizer.scale >= 1.0 || self.lastScale != 1.0) {
self.lastScale = MAX(1.0, recognizer.scale);
if (recognizer.numberOfTouches > 1) { //avoid dragging when lifting fingers off
CGFloat currentX = [recognizer locationInView:self].x;
self.zoomMovement += currentX - self.zoomMovementBase;
self.zoomMovementBase = currentX;
}
CGFloat saveAnimation = self.animationGraphEntranceTime;
self.animationGraphEntranceTime = 0;
[self reloadGraph];
self.animationGraphEntranceTime = saveAnimation;
}
}

-(void)handleDoubleTapGestureAction:(UITapGestureRecognizer *) recognizer {
if (fabs(self.lastScale -1.0) < 0.01) {
self.lastScale = self.doubleTapScale;
self.zoomMovement = self.doubleTapZoomMovement ;
self.doubleTapScale = 1.0;
} else {
self.doubleTapZoomMovement = self.zoomMovement;
self.doubleTapScale = self.lastScale;
self.zoomMovement = 0;
self.lastScale = 1.0;
}
[self reloadGraph];
}

- (void)handleGestureAction:(UIGestureRecognizer *)recognizer {
CGPoint translation = [recognizer locationInView:self.viewForFirstBaselineLayout];

@@ -1435,11 +1550,22 @@ -(void) getData {
//now calculate point locations in view
[xAxisValues removeAllObjects];
CGFloat xAxisWidth = (self.frame.size.width - self.YAxisLabelXOffset);
CGFloat valueRange = self.maxXValue- self.minXValue;
if (self.lastScale <= 0.0) self.lastScale = 1.0;
CGFloat totalValueRangeWidth = self.maxXValue - self.minXValue;
CGFloat valueRangeWidth = (totalValueRangeWidth) / self.lastScale;
CGFloat valueRangeBase = self.minXValue + self.zoomAnchorPercentage *(totalValueRangeWidth - valueRangeWidth);
self.currentScale = xAxisWidth/valueRangeWidth;
CGFloat maxXLocation = (self.maxXValue - valueRangeBase) * self.currentScale;
if (maxXLocation + self.zoomMovement < xAxisWidth ) {
self.zoomMovement = xAxisWidth - maxXLocation;
} else {
CGFloat minXLocation = (self.minXValue - valueRangeBase) * self.currentScale;
if (minXLocation + self.zoomMovement > 0) {
self.zoomMovement = -minXLocation;
}
}
for (NSNumber * value in xAxisPoints) {
CGFloat xValue = value.floatValue;
CGFloat percent = (valueRange <= 0) ? 0.5 : (xValue - self.minXValue)/valueRange;
CGFloat positionOnXAxis = percent * xAxisWidth;
CGFloat positionOnXAxis = (value.floatValue - valueRangeBase) * self.currentScale + self.zoomMovement;

[xAxisValues addObject:@(positionOnXAxis)];
}
6 changes: 3 additions & 3 deletions Sample Project/TestBed/DetailViewController.m
Original file line number Diff line number Diff line change
@@ -92,8 +92,6 @@ - (void)hydrateDatasets {
date = [self dateForGraphAfterDate:date];
}
[self checkMaximums];
NSLog(@"dates: %@",self.arrayOfDates);
NSLog(@"values: %@",self.arrayOfValues);
}

-(void) checkMaximums {
@@ -343,7 +341,9 @@ - (void)lineGraph:(BEMSimpleLineGraphView *)graph modifyPopupView:(UIView *)popu
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wformat-nonliteral"
if (!self.myGraph.formatStringForValues.length) return;
self.customViewLabel.text = [NSString stringWithFormat:self.myGraph.formatStringForValues, [self lineGraph:graph valueForPointAtIndex:index] ];
CGFloat dotValue = [self lineGraph:graph valueForPointAtIndex:index] ;
if (dotValue >= BEMNullGraphValue) return;
self.customViewLabel.text = [NSString stringWithFormat:self.myGraph.formatStringForValues, dotValue];
#pragma pop
}