Blog

Zooming and Panning in Silverlight using Visiblox Charts

Zooming and panning are features that are often needed when using a charting component. This article shows how zooming and panning can be achieved using the free version of Visiblox Silverlight charts with a few lines of code. It then digs a bit deeper to understand how zooming and panning is implemented on Visiblox charts.

The structure of the article is the following:

Setting Up a Basic Chart

Before adding zooming and panning support, let's create a simple chart with some random data. This is pretty straightforward to do, let's add a Chartin XAML and in the constructor of the main page auto generate some points:

<UserControl (...) xmlns:charts="clr-namespace:Visiblox.Charts;assembly=Visiblox.Charts">
    <Grid x:Name="LayoutRoot" Background="White">
        <charts:Chart x:Name="MainChart"/>
    </Grid>
</UserControl>
// Add 100 points to the chart
var rnd = new Random();
var dataSerires = new DataSeries<DateTime, double>(){ Title = "Series 1" };
var startDate = Convert.ToDateTime("2010. 08. 01.");
for (int i = 0; i < 100; i++)
{
    dataSerires.Add(new DataPoint<DateTime, double>(startDate.AddDays(i), rnd.Next(-20, 20)));
}
// Add a line series with the created data series that shows points
MainChart.Series.Add(new LineSeries() { DataSeries = dataSerires, ShowPoints = true, ToolTipEnabled=true});

The result is the following simple chart with a line series:

 

Adding Zooming and Panning

The Behaviour Model

Both zooming and panning are quite similar in both of them have to respond to mouse events. For such "interactive" behaviours, Visiblox has defined the Behaviour model which is an easy extension point for creating interactive components. Both zoom and pan are such behaviours ( IBehaviours). To use them, one simply has to assign either of them as the Behaviour property of the chart. So let's add that single line to enable them: 

Zooming using the ZoomBehaviour

Enabling zoom can be done by setting the ZoomBehaviouras the Behaviour of the chart:

<charts:Chart x:Name="MainChart">
    <charts:Chart.Behaviour>
        <charts:ZoomBehaviour />
    </charts:Chart.Behaviour>
</charts:Chart>

On the following chart, zooming has been enabled. Click and select a rectangle to zoom in, double click to zoom out:

 

Panning with the PanBehaviour

Enabling panning is done is done in a similar way, by setting the PanBehaviour as the behaviour of the chart. By default, however, the chart renders itself to fit the plottable area, thus making panning pointless.

Panning is typically useful when the chart's axes' ranges are set so that part of it is off the screen. To do so, let's manually set the range of the X axis to be smaller than the range of the series:

<charts:Chart x:Name="MainChart">
    <!-- Set the X range to be smaller than the range of the series we're generating-->
    <charts:Chart.XAxis>
        <charts:DateTimeAxis>
            <charts:DateTimeAxis.Range>
                <charts:DateTimeRange Minimum="2010. 09. 01" Maximum="2010. 10. 01"/>
            </charts:DateTimeAxis.Range>
        </charts:DateTimeAxis>
    </charts:Chart.XAxis>
    <!-- Enable panning -->
    <charts:Chart.Behaviour>
        <charts:PanBehaviour/>
    </charts:Chart.Behaviour>
</charts:Chart>

The result is a chart that can be panned in either direction. Just click and drag any area of the chart:

 

Multiple Behaviours using the BehaviourManager

As discussed before, both zooming and panning are examples of behaviours on the Visiblox charts. The free version of Visiblox ships with some other behaviours such as CrosshairBehaviour (displaying a crosshair on the chart) and TrackballBehaviour (displaying trackball(s) on series). There might be cases when it would be desirable to use two or more behaviours at the same time, however the chart's Behaviour property is of type IBehaviour, meaning only one behaviour can be set.

This problem can be solved by using the BehaviourManagerclass which is also an IBehaviour and follows the composite pattern, being composed of multiple behaviours. So for example enabling panning and crosshair can be done assigning a BehaviourManager to the chart's Behaviour property the following way:

<charts:Chart.Behaviour>
    <charts:BehaviourManager AllowMultipleEnabled="True">
        <charts:PanBehaviour/>
        <charts:CrosshairBehaviour ShowAxisLabels="True"/>
    </charts:BehaviourManager>                
</charts:Chart.Behaviour>

By using the BehaviourManager it is possible to view the crosshair and pan at the same time as in this example:

Download the source code of this example here: Zooming and Panning with Visiblox.zip

 

Understanding how Zooming and Panning Work

Now that we've seen how behaviours can add interaction to Silverlight or WPF charts using Visiblox, it's interesting to see what the ZoomBehaviour and PanBehaviour actually do to make zooming and panning possible.

All of the behaviours shipped with Visiblox have mouse events forwarded to them, consume those, draw something on the plot area and utilize the API of the Visiblox Chart object. In this case, both ZoomBehaviour and PanBehaviour simply animate the Scale and Offset of the Zoom property on the X and Y axis of the chart. Since it's mostly the chart that's doing the work of performing the actual zooming / panning, it should be easy enough to control these, for example with a slider.

 

Zooming and Panning via Bindings

To control the zoom (or pan) level with a slider, we simply have to set up bindings to the Scale and Offset of the X and Y axis' Zoom property, for example like this:

<StackPanel Orientation="Horizontal">
    <TextBlock>X Axis Zoom:</TextBlock>
    <TextBlock Margin="10,0,0,0">Scale:</TextBlock>
    <Slider Minimum="0.1" Maximum="5.0" Value="{Binding ElementName=MainChart, Path=XAxis.Zoom.Scale, Mode=TwoWay}" Width="100"/>
    <TextBlock Text="{Binding ElementName=MainChart, Path=XAxis.Zoom.Scale}"/>
    <TextBlock Margin="10,0,0,0">Offset:</TextBlock>
    <Slider Minimum="-1.5" Maximum="1.5" Value="{Binding ElementName=MainChart, Path=XAxis.Zoom.Offset, Mode=TwoWay}" Width="100"/>
    <TextBlock Text="{Binding ElementName=MainChart, Path=XAxis.Zoom.Offset}"/>
</StackPanel>
<StackPanel Orientation="Horizontal">
    <TextBlock>Y Axis Zoom:</TextBlock>
    <TextBlock Margin="10,0,0,0">Scale:</TextBlock>
    <Slider Minimum="0.1" Maximum="5.0" Value="{Binding ElementName=MainChart, Path=YAxis.Zoom.Scale, Mode=TwoWay}" Width="100"/>
    <TextBlock Text="{Binding ElementName=MainChart, Path=YAxis.Zoom.Scale}"/>
    <TextBlock Margin="10,0,0,0">Offset:</TextBlock>
    <Slider Minimum="-1.5" Maximum="1.5" Value="{Binding ElementName=MainChart, Path=YAxis.Zoom.Offset, Mode=TwoWay}" Width="100"/>
    <TextBlock Text="{Binding ElementName=MainChart, Path=YAxis.Zoom.Offset}"/>
</StackPanel>

As a result it's now possible to change the zoom scale and offset of both axes with sliders:

Download the source of this example here: Zooming and Panning using Bindings.zip.

 

Conclusion

In this article I've shown how easy it is to implement zooming and panning on Visiblox Silverlight (or WPF) charts. Both zooming and panning are behaviours, which behaviours can be used together, and it's also possible to create custom behaviours according to custom needs. I've also taken a step further to understand how ZoomBehaviour and PanBehaviour use the chart's API to perform zooming and panning and created an example where the zoom scale and offset are controlled by sliders.

All of the examples created in this article were done with the free version of Visiblox charts. If you're interested in a Silverlight / WPF charting component with built in support for various interactions such as zooming and panning, download the free version of Visiblox Charts now to get started or look at some of the examples (most with source code) to get an idea of the chart's capabilities.

Comments

[...] Orosz describes the ZoomCanvas in more detail in his recent post on Panning & Zooming the [...]

 

Hi,

to handle a big amount of data (more than 2.000.000 measurement points) I'm calculating (filtering) the data to display. This works fine. To do so, I'm triggering on the ZoomStarted and ZoomEnded. When ZoomStarted is fired, I'm clearing the series, so that only the grid is animated. The scale value for the zooming I'm getting when the ZoomEnded value is fired. I'd like to get this value when the ZoomStarted event is fired to do the calculating with the help of an backgroundWorker in parallel to the zooming animation.

So is there a way to get the scale target value for a zooming, when the ZoomStart event is fired ?

Regards, Matthias

 
Posted by Matthias

Hi Matthias!

Thanks for the feedback. Your request is a valid one: unfortunately at the moment I don't think there's a way you can access the final zoom value when ZoomStarted is fired as this information is internal and is used to set off an animation on the Zoom of the axes.

However I've opened a bug on this and we'll be adding this information to the ZoomStarted event's event arguments. Hopefully this will make it in the next release in a few weeks. I'll let you know when this has shipped.

Gergely

 
Posted by Gergely Orosz

Hi Gergely,

Thanks, this will be nice!

By the way, I'd like to handle the unzooming by myself. I wrote an handler to catch the double click to the chart. The event is fired, but additional still the ZoomStart and ZoomEnded events are fired. How can I disable the double click handling of the control, so that it's not unzooming ?

Best Regards, Matthias

 
Posted by Matthias

Hi Matthias!

Double click is not an event provided by the framework, so it's quite complicated to catch the second click and not pass it on to the chart.

The simpler way to handle this event is to create a class that's the subclass of ZoomBehaviour and override the MouseLeftButtonDoubleClick method, handling the double click event here. You would then set this custom class as the behaviour of the chart instead of ZoomBehaviour. So all you would need to do is something like this:

public class CustomZoomBehaviour : ZoomBehaviour { public override void MouseLeftButtonDoubleClick(Point position) { // Implement custom behaviour when the user has double clicked } }

Chart.Behaviour = new CustomZoomBehaviour();

Cheers,

Gergely

 
Posted by Gergely Orosz

Thanks Gergley! That's it, workes fine.

Best Regards, Matthias

 
Posted by Matthias

Thanks for this; I have applied it to some work we are doing in WPF (so not quite off-topic) alongside the MultiSeries attached properties described by Colin Eberhardt (http://www.scottlogic.co.uk/blog/colin/2011/03/mvvm-charting-binding-multiple-series-to-a-visiblox-chart/). However; I have come across a problem; I'd like the IsEnabled of the behaviours to be bound to my ViewModel (the whole chart viewmodel; not the individual series viewmodels). Whereas I can bind the axes properties, I can't seem to bind any behaviours properties. I get a 'Cannot find governing FrameworkElement or FrameworkContentElement for target element error' which I guess is because the behaviours are DependencyObjects not FrameworkElements and so cannot inherit the DataContext because they are outside the tree. Any ideas on a way round this?

 
Posted by Stephen Starkie

Hi Stephen,

Behaviours are indeed DependencyObjects, thus they don't have a DataContext like FrameworkElements do. However, even if they were FrameworkElements, they would not automatically inherit the DataContext of their parent as they are not in the visual tree.

A possible workaround for this scenario is to create an IBehaviour that inherits from FrameworkElement (I'll call this BindableBehaviour) and that wraps another behavior, somehow like this:

public class BindableBehaviour:FrameworkElement, IBehaviour
{
    // Declare the Behaviour dependency property of type IBehaviour that will be the wrapped behaviour following the adaptee pattern
  // Declare an IsEnabled dependency property that set's the Behaviuour's IsEnabled property on change
  // Implement other members of IBehaviour, forwaring calls to the Behaviour member
}

Then simply use it in XAML somehow like this:


    


    
        
    

Because the DataContext is not propagated to the Behaviour due to it not being present in the visual tree, you'll have to manually set its DataContext as well:

var viewModel = new ViewModel();
(MainChart.Behaviour as BindableBehaviour).DataContext = viewModel;

After this done, the changes made on the ViewModel to the IsEnabled property will propagate to the behaviour. I'm sending a working solution over on email as well on this.

 
Posted by Gergely Orosz

Thanks Gergely,

I've done what you've suggested and it has worked well for me!

Stephen.

 
Posted by Stephen Starkie

Gergely,

I have another problem; after implementing the behaviours I would also like to bind the zoom of the axes to my view model; like you have done with the sliders above; but 'the other way around'.

<charts:LinearAxis Zoom="{Binding Path=XZoom}" x:Name="XAxis" Title="{Binding Path=XTitle}"/>

However, I get the exception;

XamlParseException: "'Set property 'Visiblox.Charts.AxisBase(System.Double).Zoom' threw an exception.' Line number '40' and line position '36'."

Internal Exception: "Value cannot be null.\r\nParameter name: Zoom"

As you can see from the markup, the axes titles are already bound to my ViewModel; and they work fine.

Any ideas; I really don't get this one! I guess if I really can't do it this way, I can listen for events both ways; but it's not ideal.

Stephen.

 
Posted by Stephen Starkie

Hi Stephen,

Currently an ArgumentValidation is thrown when setting the Zoom of the Axis to null - this is not supported in the code at the minute. I'm adding this issue to our buglist to be addressed in one of the upcoming releases.

Until then you can workaround this issue by creating an IValueConverter that returns a new Zoom instance instead of a null value, thus allowing the binding to happen without errors.

Cheers,

Gergely

 
Posted by Gergely Orosz

Hi, I've just started evaluating Visiblox:

I have a Visiblox chart with multiple Y-axes, different units on each axis.

The user can click any of several buttons to display data as line series on the appropriate axis.

If the user clicks on data that plots against the primary or secondary Y-axis, it's possible to zoom the plot. If they click on data that plots on an "additional" Y-axis and there is no existing data on the primary or secondary Y-axis, the plot will not zoom.

If there is existing data plotted against the primary or secondary Y-axis, the chart zooms fine.

I'm guessing this is expected behavior in Visiblox, but am I missing any way to enable zooming with data plotted only on an "additional" Y-axis?

I know I could keep track of which axes are active and change the units on them to make sure that data is always plotted first on one of the functioning axes, but the user can also remove data from any axis at will, and I'd have to replot the data any time they removed data from the both of the functioning axes, leaving only data on the "additional" axis. Possible, but messy.

Thanks in advance!

 
Posted by John DeFiore

Hi John,

This is a known issue with the component which has recently been fixed. Visiblox Charts v2.2 is due out any day now and contains a new feature of the ZoomBehaviour which allows you to specify which Axes should be used/ignored when performing a zoom.

Thanks,

Antony

 
Posted by Antony Welsh
Hi, I have a chart with BehaviourManager. When I run my app with trial version 2.1.4.31043 the TrackballBehaviour shows black lines and labels (It is ok). However, when I use trial version 2.3.5.27853 the lines and labels are white. How can I change their color?
 
Posted by Socrates Jimenez

Hi Socrates,

I can't reproduce what you're seeing with Visiblox Charts 2.3. In my example the trackballs and crosshairs remain black. In one upgrade since 2.1 we did add additional inheritance of default foreground and background colours so that might be the problem. You can explicitly style the trackballs using the TrackballStyle property on the series and you can style the crosshair using CrosshairBehaviour.CrosshairStyle.

I hope that helps but just send us an email if you need more help!

Jesse

 
Posted by Jesse
Hi, I'm trying to do Multi-touch zoom using WPF. I would like to zoom at the centre of the manipulation and not at the leftmost point like the example in the Zooming and Panning via Bindings section. Please advice how i should go about doing this. I know a touch version is coming out in the future but I would like to show my client this feature that this is possible. Thanks!
 
Posted by Linus
Hi Linus,

I'm afraid we don't support multi-touch in the desktop versions of 2.3 and earlier. As you pointed out it's coming, but until we release 3.0 supporting it would mean pretty much writing it from scratch. I'm sure you could probably re-template the chart and attach gesture handlers to it and then write a behaviour to consume these and manilupate the axis' zooms accordingly but it wouldn't be trivial. If you wanted to show multi-touch working with Visiblox you could try the WP7 version which does have support for gestures already.

Sorry I can't offer further advice.

Regards,

Jesse
 
Posted by Jesse
Hi, I would appreciate if there is away to adjust the "Viewport", while adding new Series ,Figures or what ever to the chart I really miss the fitTofiew function in Dynamic Data Display control. thanks in advance.
 
Posted by Badr
Hi Badr, The chart will automatically adjust the axis ranges to fit the data, unless a range has been set manually. In this case, or if you have annotations outside the data range you want to display, you will need to manually set the range on each axis. Regards, William
 
Posted by William

Post a comment