SciChart® the market leader in Fast WPF Charts, WPF 3D Charts, iOS Chart, Android Chart and JavaScript Chart Components
Please note! These examples are new to SciChart iOS v4 release! SciChart’s OpenGL ES and Metal iOS and Metal macOS Chart library ships with hundred of Objective-C and Swift iOS&macOS Chart Examples which you can browse, play with and view the source-code. All of this is possible with the new and improved SciChart iOS Examples Suite and demo application for Mac, which ships as part of the SciChart SDK.
This example demonstrates how to createa custom gestures-based chart modifier that you can attach to a chart in your iOS application to perfrom a specific behaviour with SciChart iOS API. Any time you want to do something to alter the behavior of any built-in gesture modifiers, you should be thinking about creating a custom gesture-based chart modifier to do it.
In this example, a single finger vertical pinch-zoom gesture-based chart modifier is implemented to zoom the chart on X-Axis. Also it is enabled by double-tap, similar to how it works in Google Maps App.
Learn more from SCIGestureModifierBase API Documentation
The Swift and Objective-C source code for the iOS and macOS Custom Gesture Modifierexample is included below (Scroll down!).
Did you know that we have the source code for all our example available for free on Github?
Clone the SciChart.iOS.Examples from Github.
Also the SciChart iOS and Scichart macOS Trials contain the full source for the examples (link below).
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// CustomGestureModifier.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
import SciChart.Protected.SCIGestureModifierBase
class CustomGestureModifier: SCIGestureModifierBase {
private var initialLocation = CGPoint.zero
private let scaleFactor: CGFloat = -50
private var canPan = true
#if os(iOS)
private lazy var doubleTapGesture: DoubleTouchDownGestureRecognizer = {
let gesture = DoubleTouchDownGestureRecognizer(target: self, action: #selector(handleDoubleTapGesture))
return gesture
}()
override func attach(to services: ISCIServiceContainer!) {
super.attach(to: services)
if let parentSurface = self.parentSurface as? SCIView {
parentSurface.addGestureRecognizer(doubleTapGesture)
}
canPan = false
}
@objc private func handleDoubleTapGesture(_ gesture: DoubleTouchDownGestureRecognizer) {
canPan = true
}
override func onGestureEnded(with args: SCIGestureModifierEventArgs) {
canPan = false
}
override func onGestureCancelled(with args: SCIGestureModifierEventArgs) {
canPan = false
}
#endif
override func onGestureBegan(with args: SCIGestureModifierEventArgs) {
guard canPan else { return }
guard let gesture = args.gestureRecognizer as? SCIPanGestureRecognizer else { return }
let parentView = self.parentSurface.modifierSurface.view
initialLocation = gesture.location(in: parentView)
}
override func onGestureChanged(with args: SCIGestureModifierEventArgs) {
guard canPan else { return }
guard let gesture = args.gestureRecognizer as? SCIPanGestureRecognizer else { return }
let parentView = self.parentSurface.modifierSurface.view
let translationY = gesture.translation(in: parentView).y
performZoom(point: initialLocation, yScaleFactor: translationY)
gesture.setTranslation(.zero, in: parentView)
}
override func createGestureRecognizer() -> SCIGestureRecognizer {
return SCIPanGestureRecognizer()
}
private func performZoom(point: CGPoint, yScaleFactor:CGFloat) {
let fraction = yScaleFactor / scaleFactor
for i in 0 ..< self.xAxes.count {
growAxis(self.xAxes[i], at: point, by: fraction)
}
}
private func growAxis(_ axis: ISCIAxis, at point: CGPoint, by fraction: CGFloat) {
let width = axis.layoutSize.width
let coord = width - point.x
let minFraction = (coord / width) * fraction
let maxFraction = (1 - coord / width) * fraction
axis.zoom(byFractionMin: minFraction, max: maxFraction)
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2019. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// ImpulseChartView.m is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
#import "CustomGestureModifierChartView.h"
#import "SCDDataManager.h"
#import "SCDCustomGestureModifier.h"
@implementation CustomGestureModifierChartView
- (Class)associatedType { return SCIChartSurface.class; }
- (BOOL)showDefaultModifiersInToolbar { return NO; }
- (void)initExample {
id<ISCIAxis> xAxis = [SCINumericAxis new];
xAxis.growBy = [[SCIDoubleRange alloc] initWithMin:0.1 max:0.1];
id<ISCIAxis> yAxis = [SCINumericAxis new];
yAxis.growBy = [[SCIDoubleRange alloc] initWithMin:0.1 max:0.1];
SCDDoubleSeries *ds1Points = [SCDDataManager getDampedSinewaveWithAmplitude:1.0 DampingFactor:0.05 PointCount:50 Freq:5];
SCIXyDataSeries *dataSeries = [[SCIXyDataSeries alloc] initWithXType:SCIDataType_Double yType:SCIDataType_Double];
[dataSeries appendValuesX:ds1Points.xValues y:ds1Points.yValues];
SCIEllipsePointMarker *ellipsePointMarker = [SCIEllipsePointMarker new];
ellipsePointMarker.strokeStyle = nil;
ellipsePointMarker.fillStyle = [[SCISolidBrushStyle alloc] initWithColorCode:0xFF0066FF];
ellipsePointMarker.size = CGSizeMake(10, 10);
SCIFastImpulseRenderableSeries *rSeries = [SCIFastImpulseRenderableSeries new];
rSeries.dataSeries = dataSeries;
rSeries.pointMarker = ellipsePointMarker;
rSeries.strokeStyle = [[SCISolidPenStyle alloc] initWithColorCode:0xFF0066FF thickness:1.0];
#if TARGET_OS_OSX
NSString *zoomText = @"Pan vertically to Zoom In/Out.";
#elif TARGET_OS_IOS
NSString *zoomText = @"Double Tap and pan vertically to Zoom In/Out.";
#endif
SCITextAnnotation *annotation = [SCITextAnnotation new];
annotation.text = [NSString stringWithFormat:@"%@ %@",zoomText, @"\nDouble tap to Zoom Extents."];
annotation.fontStyle = [[SCIFontStyle alloc] initWithFontSize:16 andTextColor:SCIColor.whiteColor];
annotation.x1 = @(0.5);
annotation.y1 = @(0);
annotation.coordinateMode = SCIAnnotationCoordinateMode_Relative;
annotation.verticalAnchorPoint = SCIVerticalAnchorPoint_Top;
annotation.horizontalAnchorPoint = SCIHorizontalAnchorPoint_Center;
SCDCustomGestureModifier *customGestureModifier = [SCDCustomGestureModifier new];
customGestureModifier.receiveHandledEvents = YES;
[SCIUpdateSuspender usingWithSuspendable:self.surface withBlock:^{
[self.surface.xAxes add:xAxis];
[self.surface.yAxes add:yAxis];
[self.surface.renderableSeries add:rSeries];
[self.surface.annotations add:annotation];
[self.surface.chartModifiers addAll:customGestureModifier, [SCIZoomExtentsModifier new], nil];
[SCIAnimations waveSeries:rSeries duration:3.0 andEasingFunction:[SCICubicEase new]];
}];
}
@end
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// CustomGestureModifierChartView.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
class CustomGestureModifierChartView: SCDSingleChartViewController<SCIChartSurface> {
override var associatedType: AnyClass { return SCIChartSurface.self }
override var showDefaultModifiersInToolbar: Bool { return false }
override func initExample() {
let xAxis = SCINumericAxis()
xAxis.growBy = SCIDoubleRange(min: 0.1, max: 0.1)
let yAxis = SCINumericAxis()
yAxis.growBy = SCIDoubleRange(min: 0.1, max: 0.1)
let ds1points = SCDDataManager.getDampedSinewave(withAmplitude: 1.0, dampingFactor: 0.05, pointCount: 50, freq: 5)
let dataSeries = SCIXyDataSeries(xType: .double, yType: .double)
dataSeries.append(x: ds1points.xValues, y: ds1points.yValues)
let ellipsePointMarker = SCIEllipsePointMarker()
ellipsePointMarker.strokeStyle = nil
ellipsePointMarker.fillStyle = SCISolidBrushStyle(colorCode: 0xFF0066FF)
ellipsePointMarker.size = CGSize(width: 10, height: 10)
let rSeries = SCIFastImpulseRenderableSeries()
rSeries.dataSeries = dataSeries
rSeries.pointMarker = ellipsePointMarker
rSeries.strokeStyle = SCISolidPenStyle(colorCode: 0xFF0066FF, thickness: 1.0)
#if os(OSX)
let zoomText = "Pan vertically to Zoom In/Out."
#elseif os(iOS)
let zoomText = "Double Tap and pan vertically to Zoom In/Out."
#endif
let annotation = SCITextAnnotation()
annotation.text = zoomText + " \nDouble tap to Zoom Extents."
annotation.fontStyle = SCIFontStyle(fontSize: 16, andTextColor: .white)
annotation.set(x1: 0.5)
annotation.set(y1: 0)
annotation.coordinateMode = .relative
annotation.verticalAnchorPoint = .top
annotation.horizontalAnchorPoint = .center
let customGestureModifier = CustomGestureModifier()
customGestureModifier.receiveHandledEvents = true
SCIUpdateSuspender.usingWith(surface) {
self.surface.xAxes.add(xAxis)
self.surface.yAxes.add(yAxis)
self.surface.renderableSeries.add(rSeries)
self.surface.annotations.add(annotation)
self.surface.chartModifiers.add(items: customGestureModifier, SCIZoomExtentsModifier())
SCIAnimations.wave(rSeries, duration: 3.0, andEasingFunction: SCICubicEase())
}
}
}
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// DoubleTouchDownGestureRecognizer.swift is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
import Foundation
#if os(iOS)
class DoubleTouchDownGestureRecognizer: SCITapGestureRecognizer {
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
numberOfTapsRequired = 2
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
guard let firstTouch = touches.first, firstTouch.tapCount == 2 else { return }
if self.state == .possible {
self.state = .recognized
}
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
self.state = .failed
}
}
#endif
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// SCDCustomGestureModifier.m is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
#import "SCDCustomGestureModifier.h"
#import <SciChart/SCIGestureModifierBase+Protected.h>
#import "SCDDoubleTouchDownGestureRecognizer.h"
@implementation SCDCustomGestureModifier {
#if TARGET_OS_IOS
SCDDoubleTouchDownGestureRecognizer *_doubleTapGesture;
#endif
CGPoint _initialLocation;
CGFloat _scaleFactor;
BOOL _canPan;
}
- (void)attachTo:(id<ISCIServiceContainer>)services {
[super attachTo:services];
_initialLocation = CGPointZero;
_scaleFactor = -50.0;
#if TARGET_OS_OSX
_canPan = YES;
#elif TARGET_OS_IOS
_canPan = NO;
_doubleTapGesture = [[SCDDoubleTouchDownGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTapGesture:)];
if ([self.parentSurface isKindOfClass:[SCIView class]]) {
SCIView *surfaceView = (SCIView *)self.parentSurface;
[surfaceView addGestureRecognizer:_doubleTapGesture];
}
#endif
}
#if TARGET_OS_IOS
- (void)onGestureEndedWithArgs:(SCIGestureModifierEventArgs *)args {
_canPan = NO;
}
- (void)onGestureCancelledWithArgs:(SCIGestureModifierEventArgs *)args {
_canPan = NO;
}
- (void)handleDoubleTapGesture:(SCDDoubleTouchDownGestureRecognizer *)gesture {
_canPan = YES;
}
#endif
- (void)onGestureBeganWithArgs:(SCIGestureModifierEventArgs *)args {
if (!_canPan) return;
SCIPanGestureRecognizer *gesture = (SCIPanGestureRecognizer *)args.gestureRecognizer;
SCIView *parentView = self.parentSurface.modifierSurface.view;
_initialLocation = [gesture locationInView:parentView];
}
- (void)onGestureChangedWithArgs:(SCIGestureModifierEventArgs *)args {
if (!_canPan) return;
SCIPanGestureRecognizer *gesture = (SCIPanGestureRecognizer *)args.gestureRecognizer;
SCIView *parentView = self.parentSurface.modifierSurface.view;
CGFloat translationY = [gesture translationInView:parentView].y;
[self p_SCD_performZoom:_initialLocation yScaleFactor:translationY];
[gesture setTranslation:CGPointZero inView:parentView];
}
- (SCIGestureRecognizer *)createGestureRecognizer {
return [SCIPanGestureRecognizer new];
}
- (void)p_SCD_performZoom:(CGPoint)point yScaleFactor:(CGFloat)yScaleFactor {
CGFloat fraction = yScaleFactor / _scaleFactor;
for (NSUInteger i = 0, count = self.xAxes.count; i < count; i++) {
[self growAxis:self.xAxes[i] atPoint:point byFraction:fraction];
}
}
- (void)growAxis:(id<ISCIAxis>)axis atPoint:(CGPoint)point byFraction:(CGFloat)fraction {
CGFloat width = axis.layoutSize.width;
CGFloat coord = width - point.x;
double minFraction = (coord / width) * fraction;
double maxFraction = (1 - coord / width) * fraction;
[axis zoomByFractionMin:minFraction max:maxFraction];
}
@end
//******************************************************************************
// SCICHART® Copyright SciChart Ltd. 2011-2020. All rights reserved.
//
// Web: http://www.scichart.com
// Support: support@scichart.com
// Sales: sales@scichart.com
//
// SCDDoubleTouchDownGestureRecognizer.m is part of the SCICHART® Examples. Permission is hereby granted
// to modify, create derivative works, distribute and publish any part of this source
// code whether for commercial, private or personal use.
//
// The SCICHART® examples are distributed in the hope that they will be useful, but
// without any warranty. It is provided "AS IS" without warranty of any kind, either
// expressed or implied.
//******************************************************************************
#import "SCDDoubleTouchDownGestureRecognizer.h"
#if TARGET_OS_IOS
@implementation SCDDoubleTouchDownGestureRecognizer
- (instancetype)initWithTarget:(id)target action:(SEL)action {
self = [super initWithTarget:target action:action];
if (self) {
self.numberOfTapsRequired = 2;
}
return self;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSOrderedSet<UITouch *> *orderedTouches = [[NSOrderedSet alloc] initWithSet:touches];
UITouch *firstTouch = [orderedTouches firstObject];
if (firstTouch.tapCount != 2) return;
if (self.state == UIGestureRecognizerStatePossible) {
self.state = UIGestureRecognizerStateRecognized;
}
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateFailed;
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
self.state = UIGestureRecognizerStateFailed;
}
@end
#endif