The ChartPanAndZoomBehavior is a really nice feature that lets the user zoom and pan once zoomed in. There is one disadvantage, and that is that you are limited to current items source.
I am showing data over time so when you are zoomed out you can not zoom out further then the original items source. Also you cannot pan right or left when zoomed out. I would like to be able disable this behavior and adjust the items source depending on the gesture so you can pan en zoom without limits. How can I achieve this behavior preferably in a MVVM way?
Best regards,
Michel Moorlag
10 Answers, 1 is accepted
Anyone any ideas?
Thinking this over I would like to be able to bind a command to a pinch event with parameters the give me zoom in or out , the "amount" or level of the pinch and maybe the direction too (horizontal vs vertical). And I would like to be able to bind a command to the pan event giving me direction of the pan en the amount of pan. That would give me control to adjust the items source depending on the users gestures.
But I guess that would be a feature request wouldn't it?
I am not sure that you will be able to achieve these requirements with the built-in features of the chart.
At this moment the chart does not have such events as the ones you are talking about, namely zoom with the level of pinch, and pan with direction. The chart exposes a Zoom and a PanOffset properties, which are the coerced results of a touch operation. If you need access to these native touch operations, I guess you can implement custom renderers and try to intercept these touch events at your own effort and risk.
The way the chart works is that it calculates an axis Min and Max upon data binding. Then zooming only changes the zoom level and pan offset, thus changing the visible range, but never changes the Min and Max.
At this moment we do not have an exposed property for the visible range of the axis. If we implement such a property, would that help you? Could you possibly manually set the Minimum and Maximum of the axis to whatever is your actual min and max dates, and then upon VisibleRange changed notification update the items source? If you think this could work, then we can create a feature request for such a visible range property.
Regards,
Petar Marchev
Progress Telerik
Hello Petar,
Thank you for you response.
If this VisibleRange property would be a two way bindable property which can be set from source but also be updated on the pinch/pan gestures of the user then I think that would be a perfect solution. Maybe even a more simple and cleaner solution then events when I think of it.
So yeah, please make the feature request.
I have created a feature request for this in our feedback portal where you can vote and track its status. It is worth mentioning that putting a setter for this property is something that will most likely not happen. Also note that even if there was a setter for this property, you would not be able to set the VisibleRange to be larger than the actual axis range, in other words the visible range will surely be a subset of the actual range.
Regards,
Petar Marchev
Progress Telerik
Hi Petar,
I can live with that as long as it can be set form the control depending on the gestures of the user so I can adjust the data set of the item source. It has my vote.
Regards,
Michel.
For those looking for a solution I found a way. First I wanted to implement extra behaviors to the chart but it seems that the chart behaviors are not the same as the default X.F. view behaviors so this was a dead end. So I decided to embed the chart in my own gesturecontainer. Then I added bindable commands and properties so I determine when and how much was swiped/pinched. That I then can use to adjust the itemssource.
So this is my gesturecontainer:
public class GestureContainer : ContentView
{
public static readonly BindableProperty PanCommandProperty = BindableProperty.Create("PanCommand", typeof(ICommand), typeof(GestureContainer), null);
public static readonly BindableProperty PinchCommandProperty = BindableProperty.Create("PinchCommand", typeof(ICommand), typeof(GestureContainer), null);
public static readonly BindableProperty HorizontalShiftProperty = BindableProperty.Create("HorizontalShift", typeof(double), typeof(GestureContainer), 0.0);
public static readonly BindableProperty HorizontalZoomProperty = BindableProperty.Create("HorizontalZoom", typeof(double), typeof(GestureContainer), 0.0);
public GestureContainer()
{
// Set PanGestureRecognizer.TouchPoints to control the
// number of touch points needed to pan
var panGesture = new PanGestureRecognizer();
panGesture.PanUpdated += OnPanUpdated;
GestureRecognizers.Add(panGesture);
var pinchGesture = new PinchGestureRecognizer();
pinchGesture.PinchUpdated += OnPinchUpdated;
GestureRecognizers.Add(pinchGesture);
}
public void UnsubcribeContainer()
{
if(GestureRecognizers[0] is PanGestureRecognizer)
(GestureRecognizers[0] as PanGestureRecognizer).PanUpdated -= OnPanUpdated;
if (GestureRecognizers[1] is PinchGestureRecognizer)
(GestureRecognizers[1] as PinchGestureRecognizer).PinchUpdated -= OnPinchUpdated;
}
void OnPanUpdated(object sender, PanUpdatedEventArgs e)
{
switch (e.StatusType)
{
case GestureStatus.Running:
if (PanCommand == null)
{
return;
}
if (PanCommand.CanExecute(e))
{
HorizontalShift = e.TotalX;
PanCommand.Execute(e);
}
break;
case GestureStatus.Completed:
break;
}
}
void OnPinchUpdated(object sender, PinchGestureUpdatedEventArgs e)
{
switch (e.Status)
{
case GestureStatus.Running:
if (PinchCommand == null)
{
return;
}
if (PinchCommand.CanExecute(e))
{
HorizontalZoom = e.Scale;
PinchCommand.Execute(e);
}
break;
case GestureStatus.Completed:
break;
}
}
public ICommand PanCommand
{
get { return (ICommand)GetValue(PanCommandProperty); }
set { SetValue(PanCommandProperty, value); }
}
public ICommand PinchCommand
{
get { return (ICommand)GetValue(PinchCommandProperty); }
set { SetValue(PinchCommandProperty, value); }
}
public double HorizontalShift
{
get { return (double)GetValue(HorizontalShiftProperty); }
set { SetValue(HorizontalShiftProperty, value); }
}
public double HorizontalZoom
{
get { return (double)GetValue(HorizontalZoomProperty); }
set { SetValue(HorizontalZoomProperty, value); }
}
}
So in my page I embedded the chart in the container like this:
<hc:GestureContainer x:Name="gesturecontainer"
PanCommand="{Binding PanCommand}" HorizontalShift="{Binding GraphHorizontalShift, Mode=TwoWay}"
PinchCommand="{Binding PinchCommand}" HorizontalZoom="{Binding GraphHorizontalZoom, Mode=TwoWay}">
<telerikChart:RadCartesianChart PaletteName="Light" SelectionPaletteName="Light" x:Name="chart"
VerticalOptions="FillAndExpand"
BackgroundColor="Transparent"
>
<telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:DateTimeContinuousAxis LabelFormat="dd-MM" GapLength="0.3" PlotMode="BetweenTicks" MajorStepUnit="Day" MajorStep="{Binding MajorStepSize}" LabelFitMode="Rotate" />
</telerikChart:RadCartesianChart.HorizontalAxis>
<telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:NumericalAxis Minimum="0"/>
</telerikChart:RadCartesianChart.VerticalAxis>
<telerikChart:RadCartesianChart.Grid>
<telerikChart:CartesianChartGrid MajorLinesVisibility="Y" MajorLineThickness="1" />
</telerikChart:RadCartesianChart.Grid>
<telerikChart:RadCartesianChart.Series>
<telerikChart:AreaSeries CategoryBinding="Date" ValueBinding="Value" ItemsSource="{Binding Serie1}" DisplayName="{Binding Legend1}" />
<telerikChart:LineSeries CategoryBinding="Date" ValueBinding="Value" ItemsSource="{Binding Serie2}" DisplayName="{Binding Legend2}" />
</telerikChart:RadCartesianChart.Series>
</telerikChart:RadCartesianChart>
</hc:GestureContainer>
hc Is the Helperclasses namespace of my gesturecontainer
In my viewmodel I bind to the commands to determine when there was a pan or pinch gesture and I can read the GraphHorizontalShift and GraphHorizontalZoom properties to determine how much was panned or pinched.
I am not sure If it is needed but before I leave the page I unsubcribe the pan and pinch events by overloading the OnDisappearing method in code behind of the page like this:
protected override void OnDisappearing()
{
gesturecontainer.UnsubcribeContainer();
base.OnDisappearing();
}
Works like a carm and the user can now swipe and zoom without limits.
Thank you for sharing the approach so that other users might use it if needed. I have added some points to your account as a token of gratitude.
Have a great rest of the week.
Regards,
Stefan Nenchev
Progress Telerik
Hi Stefan,
No problem, I hope it can help others.
The next thing is that I would like to do is set the Axis Majorstep property depending on the zoom level. I have bound the MajorStep property on a calculated value depending on the zoom level but it does never get read. It turns out that axis value can't be bound? See https://feedback.telerik.com/Project/168/Feedback/Details/211875-chart-issue-with-bindingcontext-of-chart-axes-and-grid . For full MVVM support all properties should be bindable all the way. It's almost one year ago since this bug is reported but nothing happened since then. It would be really really nice if this could be fixed soon because these kind of issues make it really hard to make full use of these very nice controls.
Your last question is not directly related to the original question about the pan-zoom behavior being limited to the current items source. We prefer to keep questions separated into different threads because this makes it much easier to follow a conversation and for people to find answers. This is why I will ask that you open a new thread about each separate question you may have. Thank you for understanding.
Regards,
Petar Marchev
Progress Telerik
Hi Petar,
I understand, no problem.
Best Regards,
Michel Moorlag