Is it possible to select a range of data in the data chart by dragging the mouse and then sending the data range to a bound method (viewmodel)?
Basically the functionality we need is similar to the "DragZoom" default interaction, but we want to replace the zoom operation with our own operation. Using the SeriesMouseLeftButtonDown/Up events does not work well since the start and end points are not directly over data points.
It would be great if we could get the behavoir of drawing the box as in DragZoom but opon completion just have the box boundaries sent to the viewmodel.
thanks
One way of approaching this would be to basically just hi-jack the zoom change and use it to determineselected axis range. You would, of course, need to be craftier if you wanted to be able to zoom or select.Let me know if this helps!The xaml:
<UserControl.Resources> <local:TestData x:Key="data1" /> </UserControl.Resources> <Grid x:Name="LayoutRoot" Background="White"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <ig:XamDataChart Name="xamDataChart1" VerticalZoomable="True" HorizontalZoomable="True" > <ig:XamDataChart.Axes> <ig:CategoryXAxis x:Name="xAxis" ItemsSource="{StaticResource data1}" Label="{}{Label}"/> <ig:NumericYAxis x:Name="yAxis" /> </ig:XamDataChart.Axes> <ig:XamDataChart.Series> <ig:LineSeries x:Name="testLine" ItemsSource="{StaticResource data1}" XAxis="{Binding ElementName=xAxis}" YAxis="{Binding ElementName=yAxis}" ValueMemberPath="Value" /> </ig:XamDataChart.Series> </ig:XamDataChart> <StackPanel Grid.Row="1" Orientation="Horizontal" > <TextBlock Text="Selected: " /> <TextBlock Text="X: " /> <TextBlock Text="{Binding StartSelectionX}" /> <TextBlock Text="," /> <TextBlock Text="{Binding EndSelectionX}" /> <TextBlock Text="Y:" /> <TextBlock Text="{Binding StartSelectionY}" /> <TextBlock Text="," /> <TextBlock Text="{Binding EndSelectionY}" /> </StackPanel> </Grid>
And the code behind:
public partial class MainPage : UserControl, INotifyPropertyChanged { public MainPage() { InitializeComponent(); DataContext = this; SetBinding( AttemptedScaleProperty, new Binding("WindowRect") { Source = xamDataChart1 }); } public static readonly DependencyProperty AttemptedScaleProperty = DependencyProperty.Register( "AttemptedScale", typeof(Rect), typeof(MainPage), new PropertyMetadata( Rect.Empty, (o, e) => (o as MainPage).OnAttemptedScaleChanged(e))); public Rect AttemptedScale { get { return (Rect)GetValue(AttemptedScaleProperty); } set { SetValue(AttemptedScaleProperty, value); } } private Rect _selectedRect; private bool _recursing; private double _startSelectionX; public double StartSelectionX { get { return _startSelectionX; } set { _startSelectionX = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("StartSelectionX")); } } } private double _endSelectionX; public double EndSelectionX { get { return _endSelectionX; } set { _endSelectionX = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("EndSelectionX")); } } } private double _startSelectionY; public double StartSelectionY { get { return _startSelectionY; } set { _startSelectionY = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("StartSelectionY")); } } } private double _endSelectionY; public double EndSelectionY { get { return _endSelectionY; } set { _endSelectionY = value; if (PropertyChanged != null) { PropertyChanged( this, new PropertyChangedEventArgs("EndSelectionY")); } } } private void OnAttemptedScaleChanged( DependencyPropertyChangedEventArgs e) { if ((Rect)e.NewValue != _selectedRect && (Rect)e.OldValue != Rect.Empty && !_recursing) { _selectedRect = (Rect)e.NewValue; _recursing = true; xamDataChart1.WindowRect = (Rect)e.OldValue; _recursing = false; Rect viewportRect = new Rect(0,0,1,1); CategoryXAxis x = xamDataChart1.Axes[0] as CategoryXAxis; NumericYAxis y = xamDataChart1.Axes[1] as NumericYAxis; StartSelectionX = x.GetUnscaledValue(0, _selectedRect, viewportRect); EndSelectionX = x.GetUnscaledValue(1, _selectedRect, viewportRect); StartSelectionY = y.GetUnscaledValue(0, _selectedRect, viewportRect); EndSelectionY = y.GetUnscaledValue(1, _selectedRect, viewportRect); } } public event PropertyChangedEventHandler PropertyChanged; } public class TestDataItem { public string Label { get; set; } public double Value { get; set; } } public class TestData : ObservableCollection<TestDataItem> { private static Random rand = new Random(); public TestData() { double curr = 0; for (int i = 0; i < 1000; i++) { if (rand.NextDouble() > .5) { curr += rand.NextDouble(); } else { curr -= rand.NextDouble(); } Add( new TestDataItem() { Label = i.ToString(), Value = curr }); } } }
-Graham
Graham - Your reply worked like we wanted! Thanks for the great code sample!
The StartSelection and EndSelection variables above should be the axis coordinates of the selection. Or are you talking about the screen coordinates?
Is there a way to get the actual coordinates of the rectangle drawn?We need to be able to select not only the points whose x and y is inside the rectangle, but also the ones at least 1 of whose marker points is inside the rectangle.
Simplest thing might be to have a button that toggles the behavior next to the chart. I don't believe it would be easy for you to distinguish whether the window rect changed event was occuring due to mouse wheel, mouse drag, or zoom bar. You may be able to listen to the various mouse drag events that bubble up and only perform the selection operation if a drag operation had just been performed on the chart, for example.
Agreed on the code sample - it was REALLY helpful. THANK YOU!
I've run into a bit of a problem though - the AttemptedScale property is also set when you use a zoombar or the mouse wheel to zoom in or out. Our workflow should ideally allow zooming in/out with the zoombars or the mouse wheel while in a "selection state." Do you have any suggestions on how we can accomplish that? It would be nice to have at least a drag rectangle as a native element...
Thanks,
Gary