iOS Charts zoom into a range of values - ios

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

Related

Facing error in swift UI “Invalid frame dimension (negative or non-finite)” while drawing negative value in chart

In widget iOS14, I want to show values in graphical form using bar chart.
I have used this library "https://github.com/dawigr/BarChart" to draw bar chart.
But in Xcode12+, it's not showing negative values and considering negative value as 0 and throwing warning as shown in screen shot.
"[SwiftUI] Invalid frame dimension (negative or non-finite)"
You could try to normalise your input Values to prevent getting errors like this.
e.g.: if your data set contains values from -10 to 100, your min normalised value would be 0 and your max normalised value 1. This only works if your numbers are CGFloat, Double or something like this, numbers in Int format would be rounded up.
This could be done by using an extension like this:
extension Array where Element == Double {
var noramlized: [Double] {
if let min = self.min(), let max = self.max() {
return self.map{ ($0 - min) / (max - min) }
}
return []
}
}
I don't no how you get your values for the frame exactly, but I think you did something like this:
// normalise your data set:
let data : [Double] = [Double]()
youChart(inputData: data.noramlized)
// get values for the frame
let height = data.noramlized.max()
// if your normalised value is too small for your purpose (your max will always be 1.0 but in relation to the rest it might fit), you can scale it up like height * 20.
// the width might be a non changing value that you will enter manually or it will append on the count of bars in your chart.

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.

How to animate Marker from coordinate A to B in Google Maps iOS (Swift) in a particular duration

I want to animate Marker on GoogleMaps for iOS
between 2 points in at a particular speed or duration
like point A and B are two points and I want to move marker from A to B
then how to do it if I want to do in a particular time frame like suppose 3 seconds then it automatically takes 3 seconds to move from Coordinate A to B.
I want to make effect somewhat like Uber have when cars are moving from one point to another.
You can use DispatchQueue.main.asyncAfter like,
func showFirstMarker()
{
// Fetch the coordinates of first location and plot as marker.
// Call a function after 3 seconds, to show the second marker.
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {self.showSecondMarker()})
}
func showSecondMarker()
{
// Erase all markers in maps.
// Fetch the second coordinate and plot as marker.
}

zoom in zoom out at levels in shinobicharts 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.

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