zoom in zoom out at levels in shinobicharts ios - ios

I am using ShinobiCharts in my iOS app to plot a line chart. This requires a feature where in the default view will be in Days. When i pinch zoom, i will be getting Weeks data, and more pinch zooming will give me Months data. Same applies for zoom out in reverse order.
I am not able to find a way to show this data at different zoom levels.
Please help me with this.
Im using following delegate method to check zoom level
- (void)sChartIsZooming:(ShinobiChart *)chart withChartMovementInformation:
(const SChartMovementInformation *)information;
but i dont find any way to check zoom levels.

One way of checking this is to determine the number of days currently displayed within the axis' visible range.
First off you'll need a way to record the current granularity of data on display in the chart:
typedef NS_ENUM(NSUInteger, DataView)
{
DataViewDaily,
DataViewWeekly,
DataViewMonthly,
};
The initial view will be DataViewDaily and is assigned within viewDidLoad to the property currentDataView.
Then within sChartIsZooming:withChartMovementInformation: you could do:
- (void)sChartIsZooming:(ShinobiChart *)chart withChartMovementInformation:(const SChartMovementInformation *)information
{
// Assuming x is our independent axis
CGFloat span = [_chart.xAxis.axisRange.span doubleValue];
static NSUInteger dayInterval = 60 * 60 * 24;
NSUInteger numberOfDaysDisplayed = span / dayInterval;
DataView previousDataView = _currentDataView;
if (numberOfDaysDisplayed <= 7)
{
// Show daily data
_currentDataView = DataViewDaily;
}
else if (numberOfDaysDisplayed <= 30)
{
// Show weekly data
_currentDataView = DataViewWeekly;
}
else
{
// Show monthly data
_currentDataView = DataViewMonthly;
}
// Only reload if the granularity has changed
if (previousDataView != _currentDataView)
{
// Reload and redraw chart to show new data
[_chart reloadData];
[_chart redrawChart];
}
}
Now within your datasource method sChart:dataPointAtIndex:forSeriesAtIndex: you can return the appropriate data points by switching on the value of _currentDataView.
Note that you may need to also update sChart:numberOfDataPointsForSeriesAtIndex to return the number of points to display at the current view level.

Related

SciCharts - ISCIAxisCore visible min and max index

I am using the SciChart API for 2D Charts from SciCharts, I wanted to know if it is possible to know the min and max visible index/indices from ISCIAxisCore.
I need to get this information from the callback made to SCIVisibleRangeChangeListener when visible range as changed. So I can calculate some extended information, all the data is stored in a few arrays and the graph is only showing a section of it and I need to show some average values based on the visible range of the graph.
I know I could use Swift API to get the index out of the array but this seems to me like the most inefficient way of getting the visible min and max index of the data set, as it will need to search in a data set that can span more than 5k records.
I suspect, you are looking for one of the following:
search index range directly on the DataSeries, using -getIndicesXRange:xCoordinateCalculator:
search index in the underlying ISCIList, via the -findIndexOf:searchMode:isSorted: on the dataSeries.xValues
I added the following listener with prints into our Line Chart Example, which showcases how to use getIndicesXRange:
xAxis.visibleRangeChangeListener = { (axis, oldRange, newRange, animated) in
guard animated == false else { return }
if let axis = axis, let min = newRange?.minAsDouble, let max = newRange?.maxAsDouble {
let indicesRange = SCIIndexRange()
dataSeries.getIndicesXRange(indicesRange, xCoordinateCalculator: axis.currentCoordinateCalculator)
print("Values: \(min) : \(max)")
print("Min: \(indicesRange.min), Max \(indicesRange.max)")
}
}
Hope that helps.

iOS Charts zoom into a range of values

Is it possible to set the chart zoom to a range of values within the dataset? So currently we have a chart that can display close to 100 values within it. What we're trying to achieve is the ability to zoom into a range of values i.e. The first 12 values within the dataset and then the user is able to scroll in either direction to see the remaining values within the chart.
We've currently tried using the setVisibleXRangeMaximum function but that only seems to display the first value within the chart rather than 12. And this may be because the data that we receive from he service can be unpredictable i.e. one value may be 1000 and the next maybe 100,000.
This is an extension that will let you zoom into a range of values like this:
extension BarLineChartViewBase {
func focusXRange(start: Double, end: Double) {
guard end > start else {
return
}
// Reset the zoom back to the full chart view, so that the setVisibleXRange call will zoom in to the desired range,
// setVisibleXRange only zooms in, not out.
// resetZoom() seems like it should be the right API call, but it doesn't work if the user is already zoomed
// in closer than the range to focus.
// zoomToCenter(scaleX: 1, scaleY: 1) similarly doesn't work
while !isFullyZoomedOut {
zoomOut()
}
let distance = end - start
// If the distance to focus on is greater than the xAxis maximum, we can't zoom in any further
guard distance < (xAxis.axisMaximum - xAxis.axisMinimum) else {
return
}
// Set the visible range to the distance between start and end. This will zoom the chart in to a range of that distance
setVisibleXRange(minXRange: xAxis.axisMinimum, maxXRange: xAxis.axisMinimum + distance)
// Move the chart to the start point
moveViewToX(start)
// Due to the previous setVisibleXRange call, the user cannot zoom back out.
// Restore the visible range to the full width of the dataset, so that the user can zoom back out.
// Note: this will not zoom the chart back out; setVisibleXRange will only zoom in, not out
setVisibleXRange(minXRange: xAxis.axisMinimum, maxXRange: xAxis.axisMaximum)
}
}
// Use it like
// chartView.focusXRange(0, 10) // Focus on the first ten data points
// chartView.focusXRange(chartView.xAxis.axisMaximum - 10, chartView.xAxis.axisMaximum) // Focus on the last ten data points

ios: predict if two annotation views would overlap each other

I'm developing some MKMapView logic. There are many annotations on my map view. I need to compose few locations in one if those annotation views would overlap each other, and display Annotation view with changes. So I should predict that case, and I need to determinate that only from annotations' location properties and current MKMapView's zoom.
- (BOOL)shouldAlert:(CoordinatesAlert *)alert1 beInGroupWithAlert:(CoordinatesAlert *)alert2 {
// somehow calculate current map view zoom
CGFloat zoom = ...
if ([alert1.location distanceFromLocation:alert2.location] / zoom < kSomeCoeficient) {
return YES;
}
return NO;
}
Also I worried about this solution because I need to reload annotations and those views on every zoom change.
How can I do this in better way? Is there any default solution for annotation views clustering?
All of exist libraries do not suit for my needs, they all works with areas but I need to group annotations only if they would be overlapped. Also I do not need so many annotations, so i do not need performance optimization.
So, I've found some solution, it is better then nothing.
- (BOOL)isViewForLocation:(CLLocation *)location1 overlappedByViewForLocation:(CLLocation *)location2 {
const CLLocationDegrees deltaLatitude = ABS(location1.coordinate.latitude - location1.coordinate.latitude);
const CLLocationDegrees deltaLongitude = ABS(location2.coordinate.longitude - location2.coordinate.longitude);
const CLLocationDegrees mapAreaLatitude = self.mapView.region.span.latitudeDelta;
const CLLocationDegrees mapAreaLongitude = self.mapView.region.span.longitudeDelta;
return (deltaLatitude / mapAreaLatitude) < (kAnnotationTapArea.size.height / self.mapView.frame.size.height)
&& (deltaLongitude / mapAreaLongitude) < (kAnnotationTapArea.size.width / self.mapView.frame.size.width);
}

Coreplot iOS - Custom space between graph bars

I want to know if it is possible to have custom spacing between bars after some fixed interval using Coreplot iOS library.
Like in the image below, after each 7 bars an unusual barspace is shown.
And if it is possible can you please guide how can this be achieved ?
CPTBarPlot has the code to manage this.
-(BOOL)barAtRecordIndex:(NSUInteger)idx basePoint:(CGPoint *)basePoint tipPoint:(CGPoint *)tipPoint
Basically gets the bar and sets its basePoint and tipPoint.
At the end, it is using barOffsetLength to offset each bar based on its index.
CGFloat barOffsetLength = [self lengthInView:self.barOffset] * self.barOffsetScale;
For vertical bars, in your case, its offsetting the x coord of base and tip point. These are usually the same. Here you have the choice of adding your own offset.
Simply, here's what you need to do there in the same function:
CGFloat barOffsetLength = [self lengthInView:self.barOffset] * self.barOffsetScale;
if ([self.dataSource hasGapBeforeIndex:idx]) {
offsetGap += [self.dataSource gapValue];
}
// Offset
if ( horizontalBars ) {
basePoint->y += barOffsetLength;
tipPoint->y += barOffsetLength;
}
else {
//HERO
basePoint->x += barOffsetLength + offsetGap;
tipPoint->x += barOffsetLength + offsetGap;
}
Here, you introduce a new variable in CPTBarPlot called offsetGap which gets increments everytime you introduce a gap. (be careful, this needs to be reset to zero when you change the dataset).
Also, in CPTPlotDataSource introduce
- (BOOL) hasGapBeforeIndex:(NSUInteger)index;
- (CGFloat) gapValue;
and implement it in your View Controller. Now you can introduce the gap anywhere.
PS: This obviously is a hack and upsets the axis labels and other things that might also need adjustment, but gives an overview anyway.
I played around with the sample app to achieve this.
You need to modify the positioning in your Core Plot data source method for the x axis
- (NSNumber *) numberForPlot:(CPTPlot *)plot field:(NSUInteger)fieldEnum recordIndex:(NSUInteger)idx
and take into account where you want the spacing to occur. If you still don't get it, please post some code and I'll show you on that.
Logic example :
I want to represent the data for a month, lets say one that has 30 days, but at each 5 days, I want a pause at each 5 days. So instead of returning 30 in
- (NSUInteger)numberOfRecordsForPlot:(CPTPlot *)plot
, you return 34, and at indexes 6, 11, 16, 21 and 26 you return 0 for the method above.
You can extend this if you want not that much space for the 'pauses' and return double the amount of days (60), minus 4 (because for the pauses you return only for one record the value 0) and return for each 2 records the corresponding value in your data source. This can be again extended to your needed multiplier. I hope you got what I mean.
Thanks to #zakishaheen answer I managed to achieve this, but I broke label position and scroll content size 😄. This implementation is hacky thats why I decided not to continue with fixing it, its more just an example.
I created custom CustomOffsetBarPlot class and apply some Objective-C runtime magic.
- (BOOL)superImplementation:(SEL)selector idx:(NSUInteger)idx basePoint:(nonnull CGPoint *)basePoint tipPoint:(nonnull CGPoint *)tipPoint {
Class granny = [self superclass];
BOOL(* grannyImp)(id,SEL,NSUInteger,CGPoint*, CGPoint*) = (BOOL (*)(id,SEL,NSUInteger,CGPoint*, CGPoint*))class_getMethodImplementation(granny, selector);
return grannyImp(self, selector, idx, basePoint, tipPoint);
}
-(BOOL)barAtRecordIndex:(NSUInteger)idx basePoint:(nonnull CGPoint *)basePoint tipPoint:(nonnull CGPoint *)tipPoint {
SEL selector = _cmd;
CGPoint originBasePointStart = *basePoint;
CGPoint originTipPointStart = *tipPoint;
[self superImplementation:selector idx:0 basePoint:&originBasePointStart tipPoint:&originTipPointStart];
BOOL result = [self superImplementation:selector idx:idx basePoint:basePoint tipPoint:tipPoint];
Class granny = [self class];
SEL lengthView = NSSelectorFromString(#"lengthInView:");
CGFloat(* grannyImp)(id,SEL,NSDecimal) = (CGFloat (*)(id,SEL,NSDecimal))class_getMethodImplementation(granny, lengthView);
CGFloat barOffsetLengthOrigin = grannyImp(self, selector, self.barOffset.decimalValue);
NSInteger barOffsetLength = originBasePointStart.x + idx * 18 + idx * 5; // idx * 5 - your offset
basePoint->x = barOffsetLength;
tipPoint->x = barOffsetLength;
return result;
}

How do I calculate the speed of which a user drags an image?

I have an image that the user can drag to the right and it will spring back when the user releases it. I want to execute some code when a user drags it quickly and releases it. Now I have a very awkward requirement that the user can drag the image, then keep it still for any length of time (for example 5 seconds), then drag it quickly and release it. As long as the image is moving above a certain speed when it is released, it will execute the code. If it falls below the minimum speed, it executes some different code. So that means I can't calculate the length of time between the beginning of the gesture and the end and execute the code depending on the length of time. What can I do? I guess I somehow need to know the speed at which the image is moving in it's last 500 milliseconds before the gesture ends. However I've hit a brick wall figuring out how to do that. Any help would be greatly appreciated.
Can you please include an explanation and possible example code with your answer as that would be a great help.
If you get the start X,Y coordinates of when the image is dragged, and the X,Y coordinates for when the mouse is released, you can use pythagoras' theorm to calculate the distance between the two points: http://en.wikipedia.org/wiki/Pythagorean_theorem
Also, if you start a timer when the mouse is moved (and mouse button is down), and stop it in the mouseup event, you can calculate the speed using the time and distance (speed = distance / time)
edit following comments:
point delayedMousePos;
point previousMousePos;
bool secondDrag = false;
bool isStopped = false;
var timeFirstStopped;
var positionCount = 0;
array previousMousePositions[3];
// timer which monitors mouse position (set to an interval of say, 10ms)
function timerMonitorMousePos_Elapsed() {
point currentMousePos = getMousePos();
if (isStopped == false) {
if (positionCount >= 2) {
array_shift(previousMousePositions); // remove the first element of the array and move everything down to reindex numerical array to start counting from zero
positionCount = 2; // keep positionCount within array bounds
}
previousMousePositions[positionCount] = currentMousePos; // add the new position to the end of the 'stack'
positionCount++;
}
if (currentMousePos == previousMousePos) { // start check for stationary
isStopped = true;
if (timeFirstStopped == null) {
timeFirstStopped = NOW();
} else {
if (NOW() - timeFirstStopped >= 500) { // we have been stopped for at least 500ms (assumes time is counted in milliseconds)
secondDrag = true;
// previousMousePositions[0] = the mouse position 30ms before the mouse stopped
}
}
} else {
isStopped = false;
timeFirstStopped = null;
}
previousMousePos = currentMousePos;
}
I wouldn't use a timer. I would just save the starting date/time along with x,y position when the dragging starts.
When the dragging has ended, save the ending date/time and position. From those information, I can calculate the distance in pixel and duration in milliseconds.
After searching some more on the internet, I finally answered my own question.
I worked out what I needed to do:
My UIPanGestureRecognizer:
- (IBAction)handlePan3:(UIPanGestureRecognizer *)recognizer3
Get the velocity of the users finger moving across the screen:
CGPoint vel = [recognizer velocityInView:self.myView];
Then:
if (vel.x > /*value*/) {
// Your code
}
I was about to give up, but no! I got there in the end. Thanks for everyones help. I've upvoted one or two answers because they were helpful. bobnoble actually gave the suggestion to use velocityInView and I found this other stack overflow question which gave me the info I needed: iOS - Making sense of velocityInView on UIPanGesture

Resources