@@ -128,6 +128,7 @@ -(id)init
128
128
yScaleType = CPTScaleTypeLinear;
129
129
lastDragPoint = CGPointZero ;
130
130
isDragging = NO ;
131
+ hasMomentum = NO ;
131
132
}
132
133
return self;
133
134
}
@@ -162,6 +163,9 @@ -(void)encodeWithCoder:(NSCoder *)coder
162
163
[coder encodeObject: self .globalYRange forKey: @" CPTXYPlotSpace.globalYRange" ];
163
164
[coder encodeInt: self .xScaleType forKey: @" CPTXYPlotSpace.xScaleType" ];
164
165
[coder encodeInt: self .yScaleType forKey: @" CPTXYPlotSpace.yScaleType" ];
166
+ [coder encodeBool: self .allowsMomentum forKey: @" CPTXYPlotSpace.allowsMomentum" ];
167
+ [coder encodeBool: self .elasticGlobalXRange forKey: @" CPTXYPlotSpace.elasticGlobalXRange" ];
168
+ [coder encodeBool: self .elasticGlobalYRange forKey: @" CPTXYPlotSpace.elasticGlobalYRange" ];
165
169
166
170
// No need to archive these properties:
167
171
// lastDragPoint
@@ -178,8 +182,13 @@ -(id)initWithCoder:(NSCoder *)coder
178
182
xScaleType = (CPTScaleType)[coder decodeIntForKey : @" CPTXYPlotSpace.xScaleType" ];
179
183
yScaleType = (CPTScaleType)[coder decodeIntForKey : @" CPTXYPlotSpace.yScaleType" ];
180
184
185
+ self.allowsMomentum = [coder decodeBoolForKey: @" CPTXYPlotSpace.allowsMomentum" ];
186
+ self.elasticGlobalXRange = [coder decodeBoolForKey: @" CPTXYPlotSpace.elasticGlobalXRange" ];
187
+ self.elasticGlobalYRange = [coder decodeBoolForKey: @" CPTXYPlotSpace.elasticGlobalYRange" ];
188
+
181
189
lastDragPoint = CGPointZero ;
182
190
isDragging = NO ;
191
+ hasMomentum = NO ;
183
192
}
184
193
return self;
185
194
}
@@ -272,7 +281,15 @@ -(void)setXRange:(CPTPlotRange *)range
272
281
NSParameterAssert (range);
273
282
274
283
if ( ![range isEqualToRange: xRange] ) {
275
- CPTPlotRange *constrainedRange = [self constrainRange: range toGlobalRange: self .globalXRange];
284
+ CPTPlotRange *constrainedRange;
285
+
286
+ if ( self.elasticGlobalXRange ) {
287
+ constrainedRange = [range copy ];
288
+ }
289
+ else {
290
+ constrainedRange = [self constrainRange: range toGlobalRange: self .globalXRange];
291
+ }
292
+
276
293
[xRange release ];
277
294
xRange = [constrainedRange copy ];
278
295
@@ -297,7 +314,15 @@ -(void)setYRange:(CPTPlotRange *)range
297
314
NSParameterAssert (range);
298
315
299
316
if ( ![range isEqualToRange: yRange] ) {
300
- CPTPlotRange *constrainedRange = [self constrainRange: range toGlobalRange: self .globalYRange];
317
+ CPTPlotRange *constrainedRange;
318
+
319
+ if ( self.elasticGlobalXRange ) {
320
+ constrainedRange = [range copy ];
321
+ }
322
+ else {
323
+ constrainedRange = [self constrainRange: range toGlobalRange: self .globalYRange];
324
+ }
325
+
301
326
[yRange release ];
302
327
yRange = [constrainedRange copy ];
303
328
@@ -822,8 +847,16 @@ -(void)scaleBy:(CGFloat)interactionScale aboutPoint:(CGPoint)plotAreaPoint
822
847
newRangeY = [theDelegate plotSpace: self willChangePlotRangeTo: newRangeY forCoordinate: CPTCoordinateY];
823
848
}
824
849
850
+ BOOL elasticGlobalXRange = self.elasticGlobalXRange ;
851
+ BOOL elasticGlobalYRange = self.elasticGlobalYRange ;
852
+ self.elasticGlobalXRange = NO ;
853
+ self.elasticGlobalYRange = NO ;
854
+
825
855
self.xRange = newRangeX;
826
856
self.yRange = newRangeY;
857
+
858
+ self.elasticGlobalXRange = elasticGlobalXRange;
859
+ self.elasticGlobalYRange = elasticGlobalYRange;
827
860
}
828
861
829
862
// / @endcond
@@ -858,7 +891,8 @@ -(BOOL)pointingDeviceDownEvent:(CPTNativeEvent *)event atPoint:(CGPoint)interact
858
891
BOOL handledByDelegate = [super pointingDeviceDownEvent: event atPoint: interactionPoint];
859
892
860
893
if ( handledByDelegate ) {
861
- isDragging = NO ;
894
+ isDragging = NO ;
895
+ hasMomentum = NO ;
862
896
return YES ;
863
897
}
864
898
@@ -872,6 +906,9 @@ -(BOOL)pointingDeviceDownEvent:(CPTNativeEvent *)event atPoint:(CGPoint)interact
872
906
// Handle event
873
907
lastDragPoint = pointInPlotArea;
874
908
isDragging = YES ;
909
+ hasMomentum = NO ;
910
+ rubberBand = kCPTRubberBandNone ;
911
+
875
912
return YES ;
876
913
}
877
914
@@ -909,7 +946,17 @@ -(BOOL)pointingDeviceUpEvent:(CPTNativeEvent *)event atPoint:(CGPoint)interactio
909
946
}
910
947
911
948
if ( isDragging ) {
912
- isDragging = NO ;
949
+ isDragging = NO ;
950
+ hasMomentum = self.allowsMomentum ;
951
+ rubberBand = kCPTRubberBandNone ;
952
+
953
+ if ( self.allowsMomentum ) {
954
+ dispatch_after (dispatch_time (DISPATCH_TIME_NOW, NSEC_PER_SEC / 60 ), dispatch_get_current_queue (), ^{
955
+ [self handleMomentum ];
956
+ }
957
+ );
958
+ }
959
+
913
960
return YES ;
914
961
}
915
962
@@ -961,6 +1008,8 @@ -(BOOL)pointingDeviceDraggedEvent:(CPTNativeEvent *)event atPoint:(CGPoint)inter
961
1008
pointToUse = CPTPointMake (lastDragPoint.x + displacement.x , lastDragPoint.y + displacement.y );
962
1009
}
963
1010
1011
+ momentum = displacement;
1012
+
964
1013
NSDecimal lastPoint[2 ], newPoint[2 ];
965
1014
[self plotPoint: lastPoint forPlotAreaViewPoint: lastDragPoint];
966
1015
[self plotPoint: newPoint forPlotAreaViewPoint: pointToUse];
@@ -975,8 +1024,31 @@ -(BOOL)pointingDeviceDraggedEvent:(CPTNativeEvent *)event atPoint:(CGPoint)inter
975
1024
976
1025
CPTPlotRange *globalX = self.globalXRange ;
977
1026
if ( globalX ) {
978
- newRangeX = (CPTMutablePlotRange *)[self constrainRange: newRangeX toGlobalRange: globalX];
1027
+ if ( !self.elasticGlobalXRange ) {
1028
+ newRangeX = (CPTMutablePlotRange *)[self constrainRange: newRangeX toGlobalRange: globalX];
1029
+ }
1030
+ else if ( newRangeX.locationDouble < globalX.locationDouble ) {
1031
+ if ( shiftX._isNegative ) {
1032
+ NSDecimal diff = CPTDecimalDivide (CPTDecimalSubtract (globalX.location , newRangeX.location ), newRangeX.length );
1033
+ diff = CPTDecimalFromDouble ( MIN (1.0 , [[NSDecimalNumber decimalNumberWithDecimal: CPTDecimalMultiply ( diff, CPTDecimalFromDouble (3.0 ) )] doubleValue ]) );
1034
+
1035
+ // diff=1 => no scaling
1036
+ // diff=0 => full scaling
1037
+ newRangeX.location = CPTDecimalAdd ( self.xRange .location , CPTDecimalMultiply ( shiftX, CPTDecimalSubtract (CPTDecimalFromDouble (1.0 ), diff) ) );
1038
+ }
1039
+ }
1040
+ else if ( newRangeX.endDouble > globalX.endDouble ) {
1041
+ if ( !shiftX._isNegative ) {
1042
+ NSDecimal diff = CPTDecimalDivide (CPTDecimalSubtract (newRangeX.end , globalX.end ), newRangeX.length );
1043
+ diff = CPTDecimalFromDouble ( MIN (1.0 , [[NSDecimalNumber decimalNumberWithDecimal: CPTDecimalMultiply ( diff, CPTDecimalFromDouble (3.0 ) )] doubleValue ]) );
1044
+
1045
+ // diff=1 => no scaling
1046
+ // diff=0 => full scaling
1047
+ newRangeX.location = CPTDecimalAdd ( self.xRange .location , CPTDecimalMultiply ( shiftX, CPTDecimalSubtract (CPTDecimalFromDouble (1.0 ), diff) ) );
1048
+ }
1049
+ }
979
1050
}
1051
+
980
1052
CPTPlotRange *globalY = self.globalYRange ;
981
1053
if ( globalY ) {
982
1054
newRangeY = (CPTMutablePlotRange *)[self constrainRange: newRangeY toGlobalRange: globalY];
@@ -1000,6 +1072,161 @@ -(BOOL)pointingDeviceDraggedEvent:(CPTNativeEvent *)event atPoint:(CGPoint)inter
1000
1072
return NO ;
1001
1073
}
1002
1074
1075
+ -(void )handleMomentum
1076
+ {
1077
+ CPTPlotArea *plotArea = self.graph .plotAreaFrame .plotArea ;
1078
+
1079
+ if ( !self.allowsUserInteraction || !plotArea || !hasMomentum || !self.allowsMomentum ) {
1080
+ return ;
1081
+ }
1082
+
1083
+ CGPoint pointToUse = CPTPointMake (lastDragPoint.x + momentum.x , lastDragPoint.y + momentum.y );
1084
+
1085
+ id <CPTPlotSpaceDelegate> theDelegate = self.delegate ;
1086
+
1087
+ CGFloat friction = 0 .9f ;
1088
+ CPTPlotRange *globalX = self.globalXRange ;
1089
+ CPTPlotRange *globalY = self.globalYRange ;
1090
+
1091
+ if ( rubberBand != kCPTRubberBandNone ) {
1092
+ NSDecimal diff;
1093
+
1094
+ if ( rubberBand == kCPTRubberBandLeft ) {
1095
+ diff = CPTDecimalSubtract (globalX.location , self.xRange .location );
1096
+ }
1097
+ else {
1098
+ diff = CPTDecimalSubtract (self.xRange .end , globalX.end );
1099
+ }
1100
+
1101
+ diff = CPTDecimalDivide (diff, self.xRange .length );
1102
+ diff = CPTDecimalFromDouble ( MIN (1.0 , [[NSDecimalNumber decimalNumberWithDecimal: CPTDecimalMultiply ( diff, CPTDecimalFromDouble (3.0 ) )] doubleValue ]) );
1103
+
1104
+ // diff=0 => friction=0.9
1105
+ // diff=1 => friction=0.6
1106
+ friction = 0 .9f - ([[NSDecimalNumber decimalNumberWithDecimal: diff] floatValue ]) * 0 .25f ;
1107
+ // NSLog(@"rubberBand %f; %f", momentum.x, friction);
1108
+ }
1109
+
1110
+ momentum = CPTPointMake (momentum.x * friction, momentum.y * friction);
1111
+
1112
+ NSDecimal lastPoint[2 ], newPoint[2 ];
1113
+ [self plotPoint: lastPoint forPlotAreaViewPoint: lastDragPoint];
1114
+ [self plotPoint: newPoint forPlotAreaViewPoint: pointToUse];
1115
+
1116
+ CPTMutablePlotRange *newRangeX = [[self .xRange mutableCopy ] autorelease ];
1117
+ CPTMutablePlotRange *newRangeY = [[self .yRange mutableCopy ] autorelease ];
1118
+
1119
+ NSDecimal shiftX = CPTDecimalSubtract (lastPoint[0 ], newPoint[0 ]);
1120
+ NSDecimal shiftY = CPTDecimalSubtract (lastPoint[1 ], newPoint[1 ]);
1121
+
1122
+ if ( rubberBand != kCPTRubberBandNone ) {
1123
+ if ( rubberBand == kCPTRubberBandLeft ) {
1124
+ shiftX._isNegative = 0 ;
1125
+ }
1126
+ else if ( rubberBand == kCPTRubberBandRight ) {
1127
+ shiftX._isNegative = 1 ;
1128
+ }
1129
+ }
1130
+
1131
+ newRangeX.location = CPTDecimalAdd (newRangeX.location , shiftX);
1132
+ newRangeY.location = CPTDecimalAdd (newRangeY.location , shiftY);
1133
+
1134
+ if ( globalX ) {
1135
+ if ( !self.elasticGlobalXRange ) {
1136
+ newRangeX = (CPTMutablePlotRange *)[self constrainRange: newRangeX toGlobalRange: globalX];
1137
+ }
1138
+ else {
1139
+ if ( newRangeX.locationDouble < globalX.locationDouble ) {
1140
+ if ( rubberBand == kCPTRubberBandNone ) {
1141
+ NSDecimal diff = CPTDecimalDivide (CPTDecimalSubtract (globalX.location , newRangeX.location ), newRangeX.length );
1142
+ diff = CPTDecimalFromDouble ( MIN (1.0 , [[NSDecimalNumber decimalNumberWithDecimal: CPTDecimalMultiply ( diff, CPTDecimalFromDouble (3.0 ) )] doubleValue ]) );
1143
+
1144
+ // slow momentum down faster past global range bounds
1145
+ friction = 0 .9f - ([[NSDecimalNumber decimalNumberWithDecimal: diff] floatValue ]) / 3 .0f ;
1146
+ momentum = CPTPointMake (momentum.x * friction, momentum.y );
1147
+
1148
+ // diff=1 => no scaling
1149
+ // diff=0 => full scaling
1150
+ newRangeX.location = CPTDecimalAdd ( self.xRange .location , CPTDecimalMultiply ( shiftX, CPTDecimalSubtract (CPTDecimalFromDouble (1.0 ), diff) ) );
1151
+ }
1152
+ }
1153
+ else if ( newRangeX.endDouble > globalX.endDouble ) {
1154
+ if ( rubberBand == kCPTRubberBandNone ) {
1155
+ NSDecimal diff = CPTDecimalDivide (CPTDecimalSubtract (newRangeX.end , globalX.end ), newRangeX.length );
1156
+ diff = CPTDecimalFromDouble ( MIN (1.0 , [[NSDecimalNumber decimalNumberWithDecimal: CPTDecimalMultiply ( diff, CPTDecimalFromDouble (3.0 ) )] doubleValue ]) );
1157
+
1158
+ // slow momentum down faster past global range bounds
1159
+ friction = 0 .9f - ([[NSDecimalNumber decimalNumberWithDecimal: diff] floatValue ]) / 3 .0f ;
1160
+ momentum = CPTPointMake (momentum.x * friction, momentum.y );
1161
+
1162
+ // diff=1 => no scaling
1163
+ // diff=0 => full scaling
1164
+ newRangeX.location = CPTDecimalAdd ( self.xRange .location , CPTDecimalMultiply ( shiftX, CPTDecimalSubtract (CPTDecimalFromDouble (1.0 ), diff) ) );
1165
+ }
1166
+ }
1167
+ else if ( rubberBand != kCPTRubberBandNone ) {
1168
+ if ( rubberBand == kCPTRubberBandLeft ) {
1169
+ newRangeX.location = globalX.location ;
1170
+ }
1171
+ else if ( rubberBand == kCPTRubberBandRight ) {
1172
+ newRangeX.location = CPTDecimalSubtract (globalX.end , newRangeX.length );
1173
+ }
1174
+
1175
+ rubberBand = NO ;
1176
+ hasMomentum = NO ;
1177
+ }
1178
+ }
1179
+ }
1180
+
1181
+ if ( globalY ) {
1182
+ newRangeY = (CPTMutablePlotRange *)[self constrainRange: newRangeY toGlobalRange: globalY];
1183
+ }
1184
+
1185
+ // Delegate override
1186
+ if ( [theDelegate respondsToSelector: @selector (plotSpace:willChangePlotRangeTo:forCoordinate: )] ) {
1187
+ self.xRange = [theDelegate plotSpace: self willChangePlotRangeTo: newRangeX forCoordinate: CPTCoordinateX];
1188
+ self.yRange = [theDelegate plotSpace: self willChangePlotRangeTo: newRangeY forCoordinate: CPTCoordinateY];
1189
+ }
1190
+ else {
1191
+ self.xRange = newRangeX;
1192
+ self.yRange = newRangeY;
1193
+ }
1194
+
1195
+ lastDragPoint = pointToUse;
1196
+
1197
+ if ( hasMomentum ) {
1198
+ if ( ABS (momentum.x ) < 0.5 ) {
1199
+ CGFloat rubberBandMomentum = 22 .0f ;
1200
+
1201
+ if ( rubberBand != kCPTRubberBandNone ) {
1202
+ momentum = CGPointMake (momentum.x / friction, momentum.y / friction);
1203
+ }
1204
+ else {
1205
+ if ( globalX && (self.xRange .locationDouble < globalX.locationDouble ) ) {
1206
+ momentum.x = rubberBandMomentum;
1207
+ rubberBand = kCPTRubberBandLeft ;
1208
+ // NSLog(@"rubberBand left");
1209
+ }
1210
+ else if ( globalX && (self.xRange .endDouble > globalX.endDouble ) ) {
1211
+ momentum.x = -rubberBandMomentum;
1212
+ rubberBand = kCPTRubberBandRight ;
1213
+ // NSLog(@"rubberBand right");
1214
+ }
1215
+ else {
1216
+ hasMomentum = NO ;
1217
+ }
1218
+ }
1219
+ }
1220
+
1221
+ if ( hasMomentum ) {
1222
+ dispatch_after (dispatch_time (DISPATCH_TIME_NOW, NSEC_PER_SEC / 60 ), dispatch_get_current_queue (), ^{
1223
+ [self handleMomentum ];
1224
+ }
1225
+ );
1226
+ }
1227
+ }
1228
+ }
1229
+
1003
1230
// / @}
1004
1231
1005
1232
@end
0 commit comments