Initial Commit
This commit is contained in:
128
Charts/Navigation/AboutWindow.xaml
Normal file
128
Charts/Navigation/AboutWindow.xaml
Normal file
@@ -0,0 +1,128 @@
|
||||
<Window x:Class="Microsoft.Research.DynamicDataDisplay.Charts.Navigation.AboutWindow" x:ClassModifier="internal"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:l="clr-namespace:Microsoft.Research.DynamicDataDisplay"
|
||||
xmlns:p="clr-namespace:Microsoft.Research.DynamicDataDisplay.MarkupExtensions"
|
||||
Width="400"
|
||||
ResizeMode="NoResize" WindowStartupLocation="CenterOwner" ShowInTaskbar="False"
|
||||
Icon="{x:Static l:D3IconHelper.DynamicDataDisplayIcon}"
|
||||
Title="{p:Resource AboutTitle}"
|
||||
KeyDown="Window_KeyDown" SizeToContent="Height">
|
||||
<Grid>
|
||||
<FlowDocumentScrollViewer VerticalScrollBarVisibility="Disabled">
|
||||
<FlowDocument FontSize="14">
|
||||
<FlowDocument.Resources>
|
||||
<Style TargetType="{x:Type Run}" x:Key="nav">
|
||||
<Setter Property="Foreground" Value="#FF00A010"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="InlineUIContainer" x:Key="{x:Type InlineUIContainer}">
|
||||
<Setter Property="BaselineAlignment" Value="Center"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ContentControl" x:Key="btn">
|
||||
<Setter Property="Margin" Value="0,1,0,1"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontFamily" Value="Lucida Console"/>
|
||||
</Style>
|
||||
|
||||
<Style TargetType="ContentControl" x:Key="commandBtn">
|
||||
<Setter Property="Margin" Value="0,1,0,1"/>
|
||||
<Setter Property="FontSize" Value="14"/>
|
||||
<Setter Property="FontFamily" Value="Lucida Console"/>
|
||||
</Style>
|
||||
</FlowDocument.Resources>
|
||||
|
||||
<Paragraph>
|
||||
<Hyperlink Foreground="#FF0A00A0" FontSize="16" Click="Hyperlink_Click" ToolTip="{p:Resource AboutSiteUrl}" NavigateUri="http://dynamicdatadisplay.codeplex.com/">Dynamic Data Display</Hyperlink>
|
||||
<Run Name="r1" Text="{p:Resource About_1}"/>
|
||||
<InlineUIContainer>
|
||||
<RepeatButton Content="Shift" Style="{DynamicResource btn}"/>
|
||||
</InlineUIContainer>
|
||||
<Run Name="r2" Text="{p:Resource About_2}"/>
|
||||
<InlineUIContainer>
|
||||
<RepeatButton Content="Ctrl" Style="{DynamicResource btn}"/>
|
||||
</InlineUIContainer>
|
||||
<Run Name="r3" Text="{p:Resource About_3}"/>
|
||||
<LineBreak/>
|
||||
<Span>
|
||||
<Run Name="r4" Style="{DynamicResource nav}" Text="{p:Resource About_4}"/> ,
|
||||
</Span>
|
||||
<Run Name="r6" Text="{p:Resource About_6}"/>
|
||||
<InlineUIContainer>
|
||||
<RepeatButton Style="{DynamicResource btn}" Content="+" Command="{x:Static l:ChartCommands.ZoomIn}" CommandTarget="{Binding}"/>
|
||||
</InlineUIContainer>
|
||||
<Run Name="r5" Text="{p:Resource About_5}"/>
|
||||
<InlineUIContainer>
|
||||
<RepeatButton Style="{DynamicResource btn}" Content="-" Command="{x:Static l:ChartCommands.ZoomOut}" CommandTarget="{Binding}"/>
|
||||
</InlineUIContainer>
|
||||
<Run Name="r7" Text="{p:Resource About_7}"/>
|
||||
<LineBreak/>
|
||||
<Run Name="r8" Text="{p:Resource About_8}" Style="{DynamicResource nav}"/>
|
||||
<Run Name="r9" Text="{p:Resource About_2}"/>
|
||||
<InlineUIContainer>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<RepeatButton Grid.Column="1" Grid.Row="0" Style="{DynamicResource btn}" FontSize="16"
|
||||
Content="↑" Margin="2" Command="{x:Static l:ChartCommands.ScrollUp}" CommandTarget="{Binding}"/>
|
||||
<RepeatButton Grid.Column="0" Grid.Row="1" Style="{DynamicResource btn}" FontSize="16"
|
||||
Content="←" Margin="2" Command="{x:Static l:ChartCommands.ScrollLeft}" CommandTarget="{Binding}"/>
|
||||
<RepeatButton Grid.Column="1" Grid.Row="1" Style="{DynamicResource btn}" FontSize="16"
|
||||
Content="↓" Margin="2" Command="{x:Static l:ChartCommands.ScrollDown}" CommandTarget="{Binding}"/>
|
||||
<RepeatButton Grid.Column="2" Grid.Row="1" Style="{DynamicResource btn}" FontSize="16"
|
||||
Content="→" Margin="2" Command="{x:Static l:ChartCommands.ScrollRight}" CommandTarget="{Binding}"/>
|
||||
</Grid>
|
||||
</InlineUIContainer>
|
||||
<Run Name="r10" Text="{p:Resource About_9}"/>
|
||||
<LineBreak/>
|
||||
<InlineUIContainer>
|
||||
<RepeatButton Style="{DynamicResource commandBtn}" Content="Ctrl"/>
|
||||
</InlineUIContainer>
|
||||
<Run>+</Run>
|
||||
<Run Name="r11" Style="{DynamicResource nav}" Text="{p:Resource About_10}"/>
|
||||
<Run Name="r14" Text="{p:Resource About_12}"/>
|
||||
<LineBreak/>
|
||||
<InlineUIContainer>
|
||||
<RepeatButton Style="{DynamicResource commandBtn}" Content="Shift"/>
|
||||
</InlineUIContainer>
|
||||
<Run>+</Run>
|
||||
<Run Name="r12" Style="{DynamicResource nav}" Text="{p:Resource About_10}"/>
|
||||
<Run Name="r13" Text="{p:Resource About_11}"/>
|
||||
<LineBreak/>
|
||||
<InlineUIContainer>
|
||||
<Button Style="{DynamicResource commandBtn}" Content="Home" Command="{x:Static l:ChartCommands.FitToView}" CommandTarget="{Binding}"/>
|
||||
</InlineUIContainer>
|
||||
<Run Name="r15" Text="{p:Resource About_13}"/>
|
||||
<LineBreak/>
|
||||
<InlineUIContainer>
|
||||
<Button Style="{DynamicResource commandBtn}" Content="F11" Command="{x:Static l:ChartCommands.CopyScreenshot}" CommandTarget="{Binding}"/>
|
||||
</InlineUIContainer>
|
||||
<Run Name="r16" Text="{p:Resource About_15}"/>
|
||||
<LineBreak/>
|
||||
<InlineUIContainer>
|
||||
<Button Style="{DynamicResource commandBtn}" Content="Ctrl" Command="{x:Static l:ChartCommands.SaveScreenshot}" CommandTarget="{Binding}"/>
|
||||
</InlineUIContainer>
|
||||
<Run>+</Run>
|
||||
<InlineUIContainer>
|
||||
<Button Style="{DynamicResource commandBtn}" Content="S" Command="{x:Static l:ChartCommands.SaveScreenshot}" CommandTarget="{Binding}"/>
|
||||
</InlineUIContainer>
|
||||
<Run Name="r17" Text="{p:Resource About_15}"/>
|
||||
</Paragraph>
|
||||
<Paragraph TextAlignment="Center">
|
||||
<Hyperlink Foreground="#FF0A00A0" NavigateUri="http://dynamicdatadisplay.codeplex.com" ToolTip="{p:Resource AboutSireUrl}" Click="Hyperlink_Click_1">
|
||||
<TextBlock Text="{p:Resource AboutSiteUrl}"/>
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
</FlowDocument>
|
||||
</FlowDocumentScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
50
Charts/Navigation/AboutWindow.xaml.cs
Normal file
50
Charts/Navigation/AboutWindow.xaml.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Shapes;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for AboutWindow.xaml
|
||||
/// </summary>
|
||||
internal partial class AboutWindow : Window
|
||||
{
|
||||
public AboutWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void Hyperlink_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Hyperlink source = (Hyperlink)sender;
|
||||
Process.Start(source.NavigateUri.ToString());
|
||||
}
|
||||
|
||||
private void Window_KeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
// close on Esc or Enter pressed
|
||||
if (e.Key == Key.Escape || e.Key == Key.Enter)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
}
|
||||
|
||||
private void Hyperlink_Click_1(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Hyperlink source = (Hyperlink)sender;
|
||||
Process.Start(source.NavigateUri.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
222
Charts/Navigation/AxisCursorGraph.cs
Normal file
222
Charts/Navigation/AxisCursorGraph.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Input;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Data;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds to ChartPlotter two lines upon axes, showing current cursor position.
|
||||
/// </summary>
|
||||
public class AxisCursorGraph : DependencyObject, IPlotterElement
|
||||
{
|
||||
static AxisCursorGraph()
|
||||
{
|
||||
Line.StrokeProperty.AddOwner(typeof(AxisCursorGraph), new FrameworkPropertyMetadata(Brushes.Red));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AxisCursorGraph"/> class.
|
||||
/// </summary>
|
||||
public AxisCursorGraph() { }
|
||||
|
||||
#region ShowVerticalLine property
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show line upon vertical axis.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if line upon vertical axis is shown; otherwise, <c>false</c>.</value>
|
||||
public bool ShowVerticalLine
|
||||
{
|
||||
get { return (bool)GetValue(ShowVerticalLineProperty); }
|
||||
set { SetValue(ShowVerticalLineProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies ShowVerticalLine dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty ShowVerticalLineProperty = DependencyProperty.Register(
|
||||
"ShowVerticalLine",
|
||||
typeof(bool),
|
||||
typeof(AxisCursorGraph),
|
||||
new FrameworkPropertyMetadata(true, OnShowLinePropertyChanged));
|
||||
|
||||
private static void OnShowLinePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
AxisCursorGraph graph = (AxisCursorGraph)d;
|
||||
graph.UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ShowHorizontalLine property
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show line upon horizontal axis.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if lien upon horizontal axis is shown; otherwise, <c>false</c>.</value>
|
||||
public bool ShowHorizontalLine
|
||||
{
|
||||
get { return (bool)GetValue(ShowHorizontalLineProperty); }
|
||||
set { SetValue(ShowHorizontalLineProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowHorizontalLineProperty = DependencyProperty.Register(
|
||||
"ShowHorizontalLine",
|
||||
typeof(bool),
|
||||
typeof(AxisCursorGraph),
|
||||
new FrameworkPropertyMetadata(true, OnShowLinePropertyChanged));
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
private Line leftLine;
|
||||
private Line bottomLine;
|
||||
private Canvas leftCanvas;
|
||||
private Canvas bottomCanvas;
|
||||
|
||||
private Plotter2D plotter;
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = (Plotter2D)plotter;
|
||||
|
||||
this.plotter.Viewport.PropertyChanged += Viewport_PropertyChanged;
|
||||
|
||||
var parent = plotter.MainGrid;
|
||||
parent.MouseMove += parent_MouseMove;
|
||||
parent.MouseEnter += parent_MouseEnter;
|
||||
parent.MouseLeave += parent_MouseLeave;
|
||||
|
||||
Style lineStyle = new Style(typeof(Line));
|
||||
AddBindingSetter(lineStyle, Line.StrokeProperty);
|
||||
AddBindingSetter(lineStyle, Line.StrokeThicknessProperty);
|
||||
|
||||
leftCanvas = new Canvas();
|
||||
Grid.SetRow(leftCanvas, 1);
|
||||
Grid.SetColumn(leftCanvas, 0);
|
||||
leftLine = new Line { Style = lineStyle, IsHitTestVisible = false };
|
||||
leftCanvas.Children.Add(leftLine);
|
||||
parent.Children.Add(leftCanvas);
|
||||
|
||||
bottomCanvas = new Canvas();
|
||||
Grid.SetRow(bottomCanvas, 2);
|
||||
Grid.SetColumn(bottomCanvas, 1);
|
||||
bottomLine = new Line { Style = lineStyle, IsHitTestVisible = false };
|
||||
bottomCanvas.Children.Add(bottomLine);
|
||||
parent.Children.Add(bottomCanvas);
|
||||
}
|
||||
|
||||
private void AddBindingSetter(Style style, DependencyProperty property)
|
||||
{
|
||||
style.Setters.Add(new Setter(property,
|
||||
new Binding
|
||||
{
|
||||
Path = new PropertyPath(property.Name),
|
||||
Source = this
|
||||
}));
|
||||
}
|
||||
|
||||
void Viewport_PropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
private void UpdateUIRepresentation()
|
||||
{
|
||||
if (plotter == null) return;
|
||||
|
||||
var transform = plotter.Viewport.Transform;
|
||||
DataRect visible = plotter.Viewport.Visible;
|
||||
Rect output = plotter.Viewport.Output;
|
||||
|
||||
Point mousePos = Mouse.GetPosition(plotter.CentralGrid);
|
||||
|
||||
if (ShowVerticalLine)
|
||||
{
|
||||
double y = mousePos.Y;
|
||||
if (output.Top <= y && y <= output.Bottom)
|
||||
{
|
||||
leftLine.Visibility = Visibility.Visible;
|
||||
leftLine.X1 = 0;
|
||||
leftLine.X2 = plotter.LeftPanel.ActualWidth;
|
||||
|
||||
leftLine.Y1 = leftLine.Y2 = y;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftLine.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
leftLine.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
|
||||
if (ShowHorizontalLine)
|
||||
{
|
||||
double x = mousePos.X;
|
||||
if (output.Left <= x && x <= output.Right)
|
||||
{
|
||||
bottomLine.Visibility = Visibility.Visible;
|
||||
bottomLine.Y1 = 0;
|
||||
bottomLine.Y2 = plotter.BottomPanel.ActualHeight;
|
||||
|
||||
bottomLine.X1 = bottomLine.X2 = x;
|
||||
}
|
||||
else
|
||||
{
|
||||
bottomLine.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bottomLine.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
void parent_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
void parent_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
void parent_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
this.plotter.Viewport.PropertyChanged -= Viewport_PropertyChanged;
|
||||
|
||||
var parent = plotter.MainGrid;
|
||||
parent.MouseMove -= parent_MouseMove;
|
||||
parent.MouseEnter -= parent_MouseEnter;
|
||||
parent.MouseLeave -= parent_MouseLeave;
|
||||
|
||||
parent.Children.Remove(leftCanvas);
|
||||
parent.Children.Remove(bottomCanvas);
|
||||
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
405
Charts/Navigation/AxisNavigation.cs
Normal file
405
Charts/Navigation/AxisNavigation.cs
Normal file
@@ -0,0 +1,405 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
public class AxisNavigation : DependencyObject, IPlotterElement
|
||||
{
|
||||
public AxisPlacement Placement
|
||||
{
|
||||
get { return (AxisPlacement)GetValue(PlacementProperty); }
|
||||
set { SetValue(PlacementProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PlacementProperty = DependencyProperty.Register(
|
||||
"Placement",
|
||||
typeof(AxisPlacement),
|
||||
typeof(AxisNavigation),
|
||||
new FrameworkPropertyMetadata(AxisPlacement.Left, OnPlacementChanged));
|
||||
|
||||
private static void OnPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
AxisNavigation navigation = (AxisNavigation)d;
|
||||
navigation.OnPlacementChanged();
|
||||
}
|
||||
|
||||
private Panel listeningPanel;
|
||||
protected Panel ListeningPanel
|
||||
{
|
||||
get { return listeningPanel; }
|
||||
}
|
||||
|
||||
private void OnPlacementChanged()
|
||||
{
|
||||
SetListeningPanel();
|
||||
}
|
||||
|
||||
private void SetListeningPanel()
|
||||
{
|
||||
if (plotter == null) return;
|
||||
|
||||
AxisPlacement placement = Placement;
|
||||
switch (placement)
|
||||
{
|
||||
case AxisPlacement.Left:
|
||||
listeningPanel = plotter.LeftPanel;
|
||||
break;
|
||||
case AxisPlacement.Right:
|
||||
listeningPanel = plotter.RightPanel;
|
||||
break;
|
||||
case AxisPlacement.Top:
|
||||
listeningPanel = plotter.TopPanel;
|
||||
break;
|
||||
case AxisPlacement.Bottom:
|
||||
listeningPanel = plotter.BottomPanel;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private CoordinateTransform Transform
|
||||
{
|
||||
get { return plotter.Viewport.Transform; }
|
||||
}
|
||||
|
||||
private Panel hostPanel;
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = (Plotter2D)plotter;
|
||||
|
||||
hostPanel = plotter.MainGrid;
|
||||
|
||||
//hostPanel.Background = Brushes.Pink;
|
||||
|
||||
SetListeningPanel();
|
||||
|
||||
if (hostPanel != null)
|
||||
{
|
||||
hostPanel.MouseLeftButtonUp += OnMouseLeftButtonUp;
|
||||
hostPanel.MouseLeftButtonDown += OnMouseLeftButtonDown;
|
||||
hostPanel.MouseMove += OnMouseMove;
|
||||
hostPanel.MouseWheel += OnMouseWheel;
|
||||
|
||||
hostPanel.MouseRightButtonDown += OnMouseRightButtonDown;
|
||||
hostPanel.MouseRightButtonUp += OnMouseRightButtonUp;
|
||||
hostPanel.LostFocus += OnLostFocus;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLostFocus(object sender, RoutedEventArgs e)
|
||||
{
|
||||
//Debug.WriteLine("Lost Focus");
|
||||
RevertChanges();
|
||||
rmbPressed = false;
|
||||
lmbPressed = false;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
#region Right button down
|
||||
|
||||
DataRect rmbDragStartRect;
|
||||
private void OnMouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
OnMouseRightButtonDown(e);
|
||||
}
|
||||
|
||||
protected virtual void OnMouseRightButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
rmbInitialPosition = e.GetPosition(listeningPanel);
|
||||
|
||||
var foundActivePlotter = UpdateActivePlotter(e);
|
||||
if (foundActivePlotter)
|
||||
{
|
||||
rmbPressed = true;
|
||||
dragStartInViewport = rmbInitialPosition.ScreenToViewport(activePlotter.Transform);
|
||||
rmbDragStartRect = activePlotter.Visible;
|
||||
|
||||
listeningPanel.Background = fillBrush;
|
||||
listeningPanel.CaptureMouse();
|
||||
|
||||
//e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Right button up
|
||||
|
||||
private void OnMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
OnMouseRightButtonUp(e);
|
||||
}
|
||||
|
||||
protected virtual void OnMouseRightButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
if (rmbPressed)
|
||||
{
|
||||
rmbPressed = false;
|
||||
RevertChanges();
|
||||
|
||||
//e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void RevertChanges()
|
||||
{
|
||||
listeningPanel.ClearValue(Panel.CursorProperty);
|
||||
listeningPanel.Background = Brushes.Transparent;
|
||||
listeningPanel.ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
private const double wheelZoomSpeed = 1.2;
|
||||
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
Point mousePos = e.GetPosition(listeningPanel);
|
||||
|
||||
Rect listeningPanelBounds = new Rect(listeningPanel.RenderSize);
|
||||
if (!listeningPanelBounds.Contains(mousePos))
|
||||
return;
|
||||
|
||||
var foundActivePlotter = UpdateActivePlotter(e);
|
||||
if (!foundActivePlotter) return;
|
||||
|
||||
int delta = -e.Delta;
|
||||
|
||||
Point zoomTo = mousePos.ScreenToViewport(activePlotter.Transform);
|
||||
|
||||
double zoomSpeed = Math.Abs(delta / Mouse.MouseWheelDeltaForOneLine);
|
||||
zoomSpeed *= wheelZoomSpeed;
|
||||
if (delta < 0)
|
||||
{
|
||||
zoomSpeed = 1 / zoomSpeed;
|
||||
}
|
||||
|
||||
DataRect visible = activePlotter.Viewport.Visible.Zoom(zoomTo, zoomSpeed);
|
||||
DataRect oldVisible = activePlotter.Viewport.Visible;
|
||||
if (Placement.IsBottomOrTop())
|
||||
{
|
||||
visible.YMin = oldVisible.YMin;
|
||||
visible.Height = oldVisible.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
visible.XMin = oldVisible.XMin;
|
||||
visible.Width = oldVisible.Width;
|
||||
}
|
||||
activePlotter.Viewport.Visible = visible;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private const int RmbZoomScale = 200;
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (lmbPressed)
|
||||
{
|
||||
// panning:
|
||||
if (e.LeftButton == MouseButtonState.Released)
|
||||
{
|
||||
lmbPressed = false;
|
||||
RevertChanges();
|
||||
return;
|
||||
}
|
||||
|
||||
Point screenMousePos = e.GetPosition(listeningPanel);
|
||||
Point dataMousePos = screenMousePos.ScreenToViewport(activePlotter.Transform);
|
||||
DataRect visible = activePlotter.Viewport.Visible;
|
||||
double delta;
|
||||
if (Placement.IsBottomOrTop())
|
||||
{
|
||||
delta = (dataMousePos - dragStartInViewport).X;
|
||||
visible.XMin -= delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
delta = (dataMousePos - dragStartInViewport).Y;
|
||||
visible.YMin -= delta;
|
||||
}
|
||||
|
||||
if (screenMousePos != lmbInitialPosition)
|
||||
{
|
||||
listeningPanel.Cursor = Placement.IsBottomOrTop() ? Cursors.ScrollWE : Cursors.ScrollNS;
|
||||
}
|
||||
|
||||
activePlotter.Viewport.Visible = visible;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
else if (rmbPressed)
|
||||
{
|
||||
// one direction zooming:
|
||||
if (e.RightButton == MouseButtonState.Released)
|
||||
{
|
||||
rmbPressed = false;
|
||||
RevertChanges();
|
||||
return;
|
||||
}
|
||||
|
||||
Point screenMousePos = e.GetPosition(listeningPanel);
|
||||
DataRect visible = activePlotter.Viewport.Visible;
|
||||
double delta;
|
||||
|
||||
bool isHorizontal = Placement.IsBottomOrTop();
|
||||
if (isHorizontal)
|
||||
{
|
||||
delta = (screenMousePos - rmbInitialPosition).X;
|
||||
}
|
||||
else
|
||||
{
|
||||
delta = (screenMousePos - rmbInitialPosition).Y;
|
||||
}
|
||||
|
||||
if (delta < 0)
|
||||
delta = 1 / Math.Exp(-delta / RmbZoomScale);
|
||||
else
|
||||
delta = Math.Exp(delta / RmbZoomScale);
|
||||
|
||||
Point center = dragStartInViewport;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
visible = rmbDragStartRect.ZoomX(center, delta);
|
||||
}
|
||||
else
|
||||
{
|
||||
visible = rmbDragStartRect.ZoomY(center, delta);
|
||||
}
|
||||
|
||||
if (screenMousePos != lmbInitialPosition)
|
||||
{
|
||||
listeningPanel.Cursor = Placement.IsBottomOrTop() ? Cursors.ScrollWE : Cursors.ScrollNS;
|
||||
}
|
||||
|
||||
|
||||
activePlotter.Viewport.Visible = visible;
|
||||
|
||||
//e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private Point lmbInitialPosition;
|
||||
protected Point LmbInitialPosition
|
||||
{
|
||||
get { return lmbInitialPosition; }
|
||||
}
|
||||
|
||||
private Point rmbInitialPosition;
|
||||
private readonly SolidColorBrush fillBrush = new SolidColorBrush(Color.FromRgb(255, 228, 209)).MakeTransparent(0.2);
|
||||
private bool lmbPressed;
|
||||
private bool rmbPressed;
|
||||
private Point dragStartInViewport;
|
||||
private Plotter2D activePlotter;
|
||||
|
||||
#region Left button down
|
||||
|
||||
private void OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
OnMouseLeftButtonDown(e);
|
||||
}
|
||||
|
||||
protected virtual void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
lmbInitialPosition = e.GetPosition(listeningPanel);
|
||||
|
||||
var foundActivePlotter = UpdateActivePlotter(e);
|
||||
if (foundActivePlotter)
|
||||
{
|
||||
lmbPressed = true;
|
||||
dragStartInViewport = lmbInitialPosition.ScreenToViewport(activePlotter.Transform);
|
||||
|
||||
listeningPanel.Background = fillBrush;
|
||||
listeningPanel.CaptureMouse();
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool UpdateActivePlotter(MouseEventArgs e)
|
||||
{
|
||||
var axes = listeningPanel.Children.OfType<GeneralAxis>();
|
||||
|
||||
foreach (var axis in axes)
|
||||
{
|
||||
var positionInAxis = e.GetPosition(axis);
|
||||
Rect axisBounds = new Rect(axis.RenderSize);
|
||||
if (axisBounds.Contains(positionInAxis))
|
||||
{
|
||||
activePlotter = axis.Plotter;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#region Left button up
|
||||
|
||||
private void OnMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
OnMouseLeftButtonUp(e);
|
||||
}
|
||||
|
||||
protected virtual void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
if (lmbPressed)
|
||||
{
|
||||
lmbPressed = false;
|
||||
RevertChanges();
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
if (plotter.MainGrid != null)
|
||||
{
|
||||
hostPanel.MouseLeftButtonUp -= OnMouseLeftButtonUp;
|
||||
hostPanel.MouseLeftButtonDown -= OnMouseLeftButtonDown;
|
||||
hostPanel.MouseMove -= OnMouseMove;
|
||||
hostPanel.MouseWheel -= OnMouseWheel;
|
||||
|
||||
hostPanel.MouseRightButtonDown -= OnMouseRightButtonDown;
|
||||
hostPanel.MouseRightButtonUp -= OnMouseRightButtonUp;
|
||||
|
||||
hostPanel.LostFocus -= OnLostFocus;
|
||||
}
|
||||
listeningPanel = null;
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
private Plotter2D plotter;
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return "AxisNavigation: " + Placement;
|
||||
}
|
||||
}
|
||||
}
|
||||
170
Charts/Navigation/ChartCommands.cs
Normal file
170
Charts/Navigation/ChartCommands.cs
Normal file
@@ -0,0 +1,170 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
// TODO: probably optimize memory usage by replacing implicit creation of
|
||||
// all commands on first usage of this class -
|
||||
// create each command on first access directly to it.
|
||||
|
||||
/// <summary>Common chart plotter commands</summary>
|
||||
public static class ChartCommands
|
||||
{
|
||||
|
||||
#region Auxiliary code for creation of commands
|
||||
|
||||
private static RoutedUICommand CreateCommand(string name)
|
||||
{
|
||||
return new RoutedUICommand(name, name, typeof(ChartCommands));
|
||||
}
|
||||
|
||||
private static RoutedUICommand CreateCommand(string name, params Key[] keys)
|
||||
{
|
||||
InputGestureCollection gestures = new InputGestureCollection();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
gestures.Add(new KeyGesture(key));
|
||||
}
|
||||
return new RoutedUICommand(name, name, typeof(ChartCommands), gestures);
|
||||
}
|
||||
|
||||
private static RoutedUICommand CreateCommand(string name, MouseAction mouseAction) {
|
||||
return new RoutedUICommand(name, name, typeof(ChartCommands), new InputGestureCollection { new MouseGesture(mouseAction) });
|
||||
}
|
||||
|
||||
private static RoutedUICommand CreateCommand(string name, params InputGesture[] gestures)
|
||||
{
|
||||
return new RoutedUICommand(name, name, typeof(ChartCommands), new InputGestureCollection(gestures));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly RoutedUICommand zoomOutToMouse = CreateCommand("ZoomOutToMouse", MouseAction.RightDoubleClick);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the Zoom Out To Mouse command.
|
||||
/// </summary>
|
||||
public static RoutedUICommand ZoomOutToMouse
|
||||
{
|
||||
get { return ChartCommands.zoomOutToMouse; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand zoomInToMouse = CreateCommand("ZoomInToMouse", MouseAction.LeftDoubleClick);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the zoom in to mouse command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ZoomInToMouse
|
||||
{
|
||||
get { return ChartCommands.zoomInToMouse; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand zoomWithParam = CreateCommand("ZoomWithParam");
|
||||
/// <summary>
|
||||
/// Gets the value that represents the zoom with parameter command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ZoomWithParameter
|
||||
{
|
||||
get { return zoomWithParam; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand zoomIn = CreateCommand("ZoomIn", Key.OemPlus);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the zoom in command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ZoomIn
|
||||
{
|
||||
get { return zoomIn; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand zoomOut = CreateCommand("ZoomOut", Key.OemMinus);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the zoom out command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ZoomOut
|
||||
{
|
||||
get { return zoomOut; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand fitToView = CreateCommand("FitToView", Key.Home);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the fit to view command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand FitToView
|
||||
{
|
||||
get { return ChartCommands.fitToView; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand scrollLeft = CreateCommand("ScrollLeft", Key.Left);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the scroll left command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ScrollLeft
|
||||
{
|
||||
get { return ChartCommands.scrollLeft; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand scrollRight = CreateCommand("ScrollRight", Key.Right);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the scroll right command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ScrollRight
|
||||
{
|
||||
get { return ChartCommands.scrollRight; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand scrollUp = CreateCommand("ScrollUp", Key.Up);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the scroll up command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ScrollUp
|
||||
{
|
||||
get { return ChartCommands.scrollUp; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand scrollDown = CreateCommand("ScrollDown", Key.Down);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the scroll down command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ScrollDown
|
||||
{
|
||||
get { return ChartCommands.scrollDown; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand saveScreenshot = CreateCommand("SaveScreenshot", new KeyGesture(Key.S, ModifierKeys.Control));
|
||||
/// <summary>
|
||||
/// Gets the value that represents the save screenshot command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand SaveScreenshot
|
||||
{
|
||||
get { return ChartCommands.saveScreenshot; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand copyScreenshot = CreateCommand("CopyScreenshot", Key.F11);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the copy screenshot command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand CopyScreenshot
|
||||
{
|
||||
get { return ChartCommands.copyScreenshot; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand showHelp = CreateCommand("ShowHelp", Key.F1);
|
||||
/// <summary>
|
||||
/// Gets the value that represents the show help command.
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public static RoutedUICommand ShowHelp
|
||||
{
|
||||
get { return ChartCommands.showHelp; }
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Charts/Navigation/CursorCoordinateGraph.xaml
Normal file
59
Charts/Navigation/CursorCoordinateGraph.xaml
Normal file
@@ -0,0 +1,59 @@
|
||||
<d3:ContentGraph x:Class="Microsoft.Research.DynamicDataDisplay.Charts.Navigation.CursorCoordinateGraph"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d3="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts"
|
||||
xmlns:d3nav="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts.Navigation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
IsHitTestVisible="False" Panel.ZIndex="1">
|
||||
|
||||
<d3:ContentGraph.Resources>
|
||||
<Style x:Key="outerBorderStyle" TargetType="{x:Type Rectangle}" >
|
||||
<Setter Property="RadiusX" Value="10"/>
|
||||
<Setter Property="RadiusY" Value="10"/>
|
||||
<Setter Property="Stroke" Value="LightGray"/>
|
||||
<Setter Property="StrokeThickness" Value="1"/>
|
||||
<Setter Property="Fill" Value="#88FFFFFF"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="innerBorderStyle" TargetType="{x:Type Border}">
|
||||
<Setter Property="CornerRadius" Value="4"/>
|
||||
<Setter Property="Background" Value="White"/>
|
||||
<Setter Property="Margin" Value="8,4,8,4"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="textStyle" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="Margin" Value="2,1,2,1"/>
|
||||
</Style>
|
||||
|
||||
<Style x:Key="lineStyle" TargetType="{x:Type Line}">
|
||||
<Setter Property="Stroke" Value="{Binding
|
||||
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type d3nav:CursorCoordinateGraph}},
|
||||
Path=LineStroke}"/>
|
||||
<Setter Property="StrokeThickness" Value="{Binding
|
||||
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type d3nav:CursorCoordinateGraph}},
|
||||
Path=LineStrokeThickness}"/>
|
||||
<Setter Property="StrokeDashArray" Value="{Binding
|
||||
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type d3nav:CursorCoordinateGraph}},
|
||||
Path=LineStrokeDashArray}"/>
|
||||
<Setter Property="IsHitTestVisible" Value="False"/>
|
||||
</Style>
|
||||
</d3:ContentGraph.Resources>
|
||||
|
||||
<Canvas Name="content" Cursor="None" Background="Transparent" IsHitTestVisible="False">
|
||||
<Line Name="horizLine" Style="{StaticResource lineStyle}"/>
|
||||
<Line Name="vertLine" Style="{StaticResource lineStyle}"/>
|
||||
|
||||
<Grid Name="horizGrid" Canvas.Bottom="5">
|
||||
<Rectangle Style="{StaticResource outerBorderStyle}"/>
|
||||
<Border Style="{StaticResource innerBorderStyle}">
|
||||
<TextBlock Name="horizTextBlock" Style="{StaticResource textStyle}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Name="vertGrid" Canvas.Left="5">
|
||||
<Rectangle Style="{StaticResource outerBorderStyle}"/>
|
||||
<Border Style="{StaticResource innerBorderStyle}">
|
||||
<TextBlock Name="vertTextBlock" Style="{StaticResource textStyle}"/>
|
||||
</Border>
|
||||
</Grid>
|
||||
</Canvas>
|
||||
</d3:ContentGraph>
|
||||
470
Charts/Navigation/CursorCoordinateGraph.xaml.cs
Normal file
470
Charts/Navigation/CursorCoordinateGraph.xaml.cs
Normal file
@@ -0,0 +1,470 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds to ChartPlotter two crossed lines, bound to mouse cursor position, and two labels near axes with mouse position in its text.
|
||||
/// </summary>
|
||||
public partial class CursorCoordinateGraph : ContentGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CursorCoordinateGraph"/> class.
|
||||
/// </summary>
|
||||
public CursorCoordinateGraph()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
Vector blockShift = new Vector(3, 3);
|
||||
|
||||
#region Plotter
|
||||
|
||||
protected override void OnPlotterAttached()
|
||||
{
|
||||
UIElement parent = (UIElement)Parent;
|
||||
|
||||
parent.MouseMove += parent_MouseMove;
|
||||
parent.MouseEnter += Parent_MouseEnter;
|
||||
parent.MouseLeave += Parent_MouseLeave;
|
||||
|
||||
UpdateVisibility();
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
protected override void OnPlotterDetaching()
|
||||
{
|
||||
UIElement parent = (UIElement)Parent;
|
||||
|
||||
parent.MouseMove -= parent_MouseMove;
|
||||
parent.MouseEnter -= Parent_MouseEnter;
|
||||
parent.MouseLeave -= Parent_MouseLeave;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private bool autoHide = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to hide automatically cursor lines when mouse leaves plotter.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if auto hide; otherwise, <c>false</c>.</value>
|
||||
public bool AutoHide
|
||||
{
|
||||
get { return autoHide; }
|
||||
set { autoHide = value; }
|
||||
}
|
||||
|
||||
private void Parent_MouseEnter(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (autoHide)
|
||||
{
|
||||
UpdateVisibility();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateVisibility()
|
||||
{
|
||||
horizLine.Visibility = vertGrid.Visibility = GetHorizontalVisibility();
|
||||
vertLine.Visibility = horizGrid.Visibility = GetVerticalVisibility();
|
||||
}
|
||||
|
||||
private Visibility GetHorizontalVisibility()
|
||||
{
|
||||
return showHorizontalLine ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
private Visibility GetVerticalVisibility()
|
||||
{
|
||||
return showVerticalLine ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void Parent_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (autoHide)
|
||||
{
|
||||
horizLine.Visibility = Visibility.Hidden;
|
||||
vertLine.Visibility = Visibility.Hidden;
|
||||
horizGrid.Visibility = Visibility.Hidden;
|
||||
vertGrid.Visibility = Visibility.Hidden;
|
||||
}
|
||||
}
|
||||
|
||||
private bool followMouse = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether lines are following mouse cursor position.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if lines are following mouse cursor position; otherwise, <c>false</c>.</value>
|
||||
public bool FollowMouse
|
||||
{
|
||||
get { return followMouse; }
|
||||
set
|
||||
{
|
||||
followMouse = value;
|
||||
|
||||
if (!followMouse)
|
||||
{
|
||||
AutoHide = false;
|
||||
}
|
||||
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
|
||||
private void parent_MouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (followMouse)
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnViewportPropertyChanged(ExtendedPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
private string customXFormat = null;
|
||||
/// <summary>
|
||||
/// Gets or sets the custom format string of x label.
|
||||
/// </summary>
|
||||
/// <value>The custom X format.</value>
|
||||
public string CustomXFormat
|
||||
{
|
||||
get { return customXFormat; }
|
||||
set
|
||||
{
|
||||
if (customXFormat != value)
|
||||
{
|
||||
customXFormat = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string customYFormat = null;
|
||||
/// <summary>
|
||||
/// Gets or sets the custom format string of y label.
|
||||
/// </summary>
|
||||
/// <value>The custom Y format.</value>
|
||||
public string CustomYFormat
|
||||
{
|
||||
get { return customYFormat; }
|
||||
set
|
||||
{
|
||||
if (customYFormat != value)
|
||||
{
|
||||
customYFormat = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Func<double, string> xTextMapping = null;
|
||||
/// <summary>
|
||||
/// Gets or sets the text mapping of x label - function that builds text from x-coordinate of mouse in data.
|
||||
/// </summary>
|
||||
/// <value>The X text mapping.</value>
|
||||
public Func<double, string> XTextMapping
|
||||
{
|
||||
get { return xTextMapping; }
|
||||
set
|
||||
{
|
||||
if (xTextMapping != value)
|
||||
{
|
||||
xTextMapping = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Func<double, string> yTextMapping = null;
|
||||
/// <summary>
|
||||
/// Gets or sets the text mapping of y label - function that builds text from y-coordinate of mouse in data.
|
||||
/// </summary>
|
||||
/// <value>The Y text mapping.</value>
|
||||
public Func<double, string> YTextMapping
|
||||
{
|
||||
get { return yTextMapping; }
|
||||
set
|
||||
{
|
||||
if (yTextMapping != value)
|
||||
{
|
||||
yTextMapping = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool showHorizontalLine = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show horizontal line.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if horizontal line is shown; otherwise, <c>false</c>.</value>
|
||||
public bool ShowHorizontalLine
|
||||
{
|
||||
get { return showHorizontalLine; }
|
||||
set
|
||||
{
|
||||
if (showHorizontalLine != value)
|
||||
{
|
||||
showHorizontalLine = value;
|
||||
UpdateVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool showVerticalLine = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to show vertical line.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if vertical line is shown; otherwise, <c>false</c>.</value>
|
||||
public bool ShowVerticalLine
|
||||
{
|
||||
get { return showVerticalLine; }
|
||||
set
|
||||
{
|
||||
if (showVerticalLine != value)
|
||||
{
|
||||
showVerticalLine = value;
|
||||
UpdateVisibility();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateUIRepresentation()
|
||||
{
|
||||
Point position = followMouse ? Mouse.GetPosition(this) : Position;
|
||||
UpdateUIRepresentation(position);
|
||||
}
|
||||
|
||||
private void UpdateUIRepresentation(Point mousePos)
|
||||
{
|
||||
if (Plotter2D == null) return;
|
||||
|
||||
var transform = Plotter2D.Viewport.Transform;
|
||||
DataRect visible = Plotter2D.Viewport.Visible;
|
||||
Rect output = Plotter2D.Viewport.Output;
|
||||
|
||||
if (!output.Contains(mousePos))
|
||||
{
|
||||
if (autoHide)
|
||||
{
|
||||
horizGrid.Visibility = horizLine.Visibility = vertGrid.Visibility = vertLine.Visibility = Visibility.Hidden;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!followMouse)
|
||||
{
|
||||
mousePos = mousePos.DataToScreen(transform);
|
||||
}
|
||||
|
||||
horizLine.X1 = output.Left;
|
||||
horizLine.X2 = output.Right;
|
||||
horizLine.Y1 = mousePos.Y;
|
||||
horizLine.Y2 = mousePos.Y;
|
||||
|
||||
vertLine.X1 = mousePos.X;
|
||||
vertLine.X2 = mousePos.X;
|
||||
vertLine.Y1 = output.Top;
|
||||
vertLine.Y2 = output.Bottom;
|
||||
|
||||
if (UseDashOffset)
|
||||
{
|
||||
horizLine.StrokeDashOffset = (output.Right - mousePos.X) / 2;
|
||||
vertLine.StrokeDashOffset = (output.Bottom - mousePos.Y) / 2;
|
||||
}
|
||||
|
||||
Point mousePosInData = mousePos.ScreenToData(transform);
|
||||
|
||||
string text = null;
|
||||
|
||||
if (showVerticalLine)
|
||||
{
|
||||
double xValue = mousePosInData.X;
|
||||
if (xTextMapping != null)
|
||||
text = xTextMapping(xValue);
|
||||
|
||||
// doesnot have xTextMapping or it returned null
|
||||
if (text == null)
|
||||
text = GetRoundedValue(visible.XMin, visible.XMax, xValue);
|
||||
|
||||
if (!String.IsNullOrEmpty(customXFormat))
|
||||
text = String.Format(customXFormat, text);
|
||||
horizTextBlock.Text = text;
|
||||
}
|
||||
|
||||
double width = horizGrid.ActualWidth;
|
||||
double x = mousePos.X + blockShift.X;
|
||||
if (x + width > output.Right)
|
||||
{
|
||||
x = mousePos.X - blockShift.X - width;
|
||||
}
|
||||
Canvas.SetLeft(horizGrid, x);
|
||||
|
||||
if (showHorizontalLine)
|
||||
{
|
||||
double yValue = mousePosInData.Y;
|
||||
text = null;
|
||||
if (yTextMapping != null)
|
||||
text = yTextMapping(yValue);
|
||||
|
||||
if (text == null)
|
||||
text = GetRoundedValue(visible.YMin, visible.YMax, yValue);
|
||||
|
||||
if (!String.IsNullOrEmpty(customYFormat))
|
||||
text = String.Format(customYFormat, text);
|
||||
vertTextBlock.Text = text;
|
||||
}
|
||||
|
||||
// by default vertGrid is positioned on the top of line.
|
||||
double height = vertGrid.ActualHeight;
|
||||
double y = mousePos.Y - blockShift.Y - height;
|
||||
if (y < output.Top)
|
||||
{
|
||||
y = mousePos.Y + blockShift.Y;
|
||||
}
|
||||
Canvas.SetTop(vertGrid, y);
|
||||
|
||||
if (followMouse)
|
||||
Position = mousePos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the mouse position in screen coordinates.
|
||||
/// </summary>
|
||||
/// <value>The position.</value>
|
||||
public Point Position
|
||||
{
|
||||
get { return (Point)GetValue(PositionProperty); }
|
||||
set { SetValue(PositionProperty, value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies Position dependency property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PositionProperty = DependencyProperty.Register(
|
||||
"Position",
|
||||
typeof(Point),
|
||||
typeof(CursorCoordinateGraph),
|
||||
new UIPropertyMetadata(new Point(), OnPositionChanged));
|
||||
|
||||
private static void OnPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
CursorCoordinateGraph graph = (CursorCoordinateGraph)d;
|
||||
graph.UpdateUIRepresentation((Point)e.NewValue);
|
||||
}
|
||||
|
||||
private string GetRoundedValue(double min, double max, double value)
|
||||
{
|
||||
double roundedValue = value;
|
||||
var log = RoundingHelper.GetDifferenceLog(min, max);
|
||||
string format = "G3";
|
||||
double diff = Math.Abs(max - min);
|
||||
if (1E3 < diff && diff < 1E6)
|
||||
{
|
||||
format = "F0";
|
||||
}
|
||||
if (log < 0)
|
||||
format = "G" + (-log + 2).ToString();
|
||||
|
||||
return roundedValue.ToString(format);
|
||||
}
|
||||
|
||||
#region UseDashOffset property
|
||||
|
||||
public bool UseDashOffset
|
||||
{
|
||||
get { return (bool)GetValue(UseDashOffsetProperty); }
|
||||
set { SetValue(UseDashOffsetProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty UseDashOffsetProperty = DependencyProperty.Register(
|
||||
"UseDashOffset",
|
||||
typeof(bool),
|
||||
typeof(CursorCoordinateGraph),
|
||||
new FrameworkPropertyMetadata(true, UpdateUIRepresentation));
|
||||
|
||||
private static void UpdateUIRepresentation(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
CursorCoordinateGraph graph = (CursorCoordinateGraph)d;
|
||||
if ((bool)e.NewValue)
|
||||
{
|
||||
graph.UpdateUIRepresentation();
|
||||
}
|
||||
else
|
||||
{
|
||||
graph.vertLine.ClearValue(Line.StrokeDashOffsetProperty);
|
||||
graph.horizLine.ClearValue(Line.StrokeDashOffsetProperty);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LineStroke property
|
||||
|
||||
public Brush LineStroke
|
||||
{
|
||||
get { return (Brush)GetValue(LineStrokeProperty); }
|
||||
set { SetValue(LineStrokeProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LineStrokeProperty = DependencyProperty.Register(
|
||||
"LineStroke",
|
||||
typeof(Brush),
|
||||
typeof(CursorCoordinateGraph),
|
||||
new PropertyMetadata(new SolidColorBrush(Color.FromArgb(170, 86, 86, 86))));
|
||||
|
||||
#endregion
|
||||
|
||||
#region LineStrokeThickness property
|
||||
|
||||
public double LineStrokeThickness
|
||||
{
|
||||
get { return (double)GetValue(LineStrokeThicknessProperty); }
|
||||
set { SetValue(LineStrokeThicknessProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LineStrokeThicknessProperty = DependencyProperty.Register(
|
||||
"LineStrokeThickness",
|
||||
typeof(double),
|
||||
typeof(CursorCoordinateGraph),
|
||||
new PropertyMetadata(2.0));
|
||||
|
||||
#endregion
|
||||
|
||||
#region LineStrokeDashArray property
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
|
||||
public DoubleCollection LineStrokeDashArray
|
||||
{
|
||||
get { return (DoubleCollection)GetValue(LineStrokeDashArrayProperty); }
|
||||
set { SetValue(LineStrokeDashArrayProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LineStrokeDashArrayProperty = DependencyProperty.Register(
|
||||
"LineStrokeDashArray",
|
||||
typeof(DoubleCollection),
|
||||
typeof(CursorCoordinateGraph),
|
||||
new FrameworkPropertyMetadata(DoubleCollectionHelper.Create(3, 3)));
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
331
Charts/Navigation/DefaultContextMenu.cs
Normal file
331
Charts/Navigation/DefaultContextMenu.cs
Normal file
@@ -0,0 +1,331 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Input;
|
||||
using System.Linq;
|
||||
using System.Diagnostics;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections;
|
||||
using System.ComponentModel;
|
||||
using System.Windows.Data;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Navigation;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Is responsible for displaying and populating default context menu of ChartPlotter
|
||||
/// </summary>
|
||||
public class DefaultContextMenu : IPlotterElement
|
||||
{
|
||||
private static readonly BitmapImage helpIcon;
|
||||
private static readonly BitmapImage copyScreenshotIcon;
|
||||
private static readonly BitmapImage saveScreenshotIcon;
|
||||
private static readonly BitmapImage fitToViewIcon;
|
||||
static DefaultContextMenu()
|
||||
{
|
||||
helpIcon = LoadIcon("HelpIcon");
|
||||
saveScreenshotIcon = LoadIcon("SaveIcon");
|
||||
copyScreenshotIcon = LoadIcon("CopyScreenshotIcon");
|
||||
fitToViewIcon = LoadIcon("FitToViewIcon");
|
||||
}
|
||||
|
||||
private static BitmapImage LoadIcon(string name)
|
||||
{
|
||||
Assembly currentAssembly = typeof(DefaultContextMenu).Assembly;
|
||||
|
||||
BitmapImage icon = new BitmapImage();
|
||||
icon.BeginInit();
|
||||
icon.StreamSource = currentAssembly.GetManifestResourceStream("Microsoft.Research.DynamicDataDisplay.Resources." + name + ".png");
|
||||
icon.EndInit();
|
||||
icon.Freeze();
|
||||
|
||||
return icon;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DefaultContextMenu"/> class.
|
||||
/// </summary>
|
||||
public DefaultContextMenu() { }
|
||||
|
||||
protected ContextMenu PopulateContextMenu(Plotter target)
|
||||
{
|
||||
ContextMenu menu = new ContextMenu();
|
||||
MenuItem fitToViewMenuItem = new MenuItem
|
||||
{
|
||||
Header = Strings.UIResources.ContextMenuFitToView,
|
||||
ToolTip = Strings.UIResources.ContextMenuFitToViewTooltip,
|
||||
Icon = new Image { Source = fitToViewIcon },
|
||||
Command = ChartCommands.FitToView,
|
||||
CommandTarget = target
|
||||
};
|
||||
|
||||
MenuItem savePictureMenuItem = new MenuItem
|
||||
{
|
||||
Header = Strings.UIResources.ContextMenuSaveScreenshot,
|
||||
ToolTip = Strings.UIResources.ContextMenuSaveScreenshotTooltip,
|
||||
Icon = new Image { Source = saveScreenshotIcon },
|
||||
Command = ChartCommands.SaveScreenshot,
|
||||
CommandTarget = target
|
||||
};
|
||||
|
||||
MenuItem copyPictureMenuItem = new MenuItem
|
||||
{
|
||||
Header = Strings.UIResources.ContextMenuCopyScreenshot,
|
||||
ToolTip = Strings.UIResources.ContextMenuCopyScreenshotTooltip,
|
||||
Icon = new Image { Source = copyScreenshotIcon },
|
||||
Command = ChartCommands.CopyScreenshot,
|
||||
CommandTarget = target
|
||||
};
|
||||
|
||||
MenuItem quickHelpMenuItem = new MenuItem
|
||||
{
|
||||
Header = Strings.UIResources.ContextMenuQuickHelp,
|
||||
ToolTip = Strings.UIResources.ContextMenuQuickHelpTooltip,
|
||||
Command = ChartCommands.ShowHelp,
|
||||
Icon = new Image { Source = helpIcon },
|
||||
CommandTarget = target
|
||||
};
|
||||
|
||||
//MenuItem reportFeedback = new MenuItem
|
||||
//{
|
||||
// Header = Strings.UIResources.ContextMenuReportFeedback,
|
||||
// ToolTip = Strings.UIResources.ContextMenuReportFeedbackTooltip,
|
||||
// Icon = (Image)plotter.Resources["SendFeedbackIcon"]
|
||||
//};
|
||||
//reportFeedback.Click += reportFeedback_Click;
|
||||
|
||||
staticMenuItems.Add(fitToViewMenuItem);
|
||||
staticMenuItems.Add(copyPictureMenuItem);
|
||||
staticMenuItems.Add(savePictureMenuItem);
|
||||
staticMenuItems.Add(quickHelpMenuItem);
|
||||
//staticMenuItems.Add(reportFeedback);
|
||||
|
||||
menu.ItemsSource = staticMenuItems;
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
//private void reportFeedback_Click(object sender, RoutedEventArgs e)
|
||||
//{
|
||||
// try
|
||||
// {
|
||||
// using (Process.Start("mailto:" + Strings.UIResources.SendFeedbackEmail + "?Subject=[D3]%20" + typeof(DefaultContextMenu).Assembly.GetName().Version)) { }
|
||||
// }
|
||||
// catch (Exception)
|
||||
// {
|
||||
// MessageBox.Show(Strings.UIResources.SendFeedbackError + Strings.UIResources.SendFeedbackEmail, "Error while sending feedback", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
// }
|
||||
//}
|
||||
|
||||
private readonly ObservableCollection<object> staticMenuItems = new ObservableCollection<object>();
|
||||
|
||||
// hidden because default menu items' command target is plotter, and serializing this will
|
||||
// cause circular reference
|
||||
|
||||
/// <summary>
|
||||
/// Gets the static menu items.
|
||||
/// </summary>
|
||||
/// <value>The static menu items.</value>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public ObservableCollection<object> StaticMenuItems
|
||||
{
|
||||
get { return staticMenuItems; }
|
||||
}
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
private Plotter2D plotter;
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = (Plotter2D)plotter;
|
||||
|
||||
ContextMenu menu = PopulateContextMenu(plotter);
|
||||
plotter.ContextMenu = menu;
|
||||
|
||||
plotter.PreviewMouseRightButtonDown += plotter_PreviewMouseRightButtonDown;
|
||||
plotter.PreviewMouseRightButtonUp += plotter_PreviewMouseRightButtonUp;
|
||||
plotter.PreviewMouseLeftButtonDown += plotter_PreviewMouseLeftButtonDown;
|
||||
|
||||
plotter.ContextMenu.Closed += ContextMenu_Closed;
|
||||
}
|
||||
|
||||
private void plotter_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// this will prevent other tools like PointSelector from
|
||||
// wrong actuations
|
||||
if (contextMenuOpen)
|
||||
{
|
||||
plotter.Focus();
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
plotter.ContextMenu.Closed -= ContextMenu_Closed;
|
||||
|
||||
plotter.ContextMenu = null;
|
||||
plotter.PreviewMouseRightButtonDown -= plotter_PreviewMouseRightButtonDown;
|
||||
plotter.PreviewMouseRightButtonUp -= plotter_PreviewMouseRightButtonUp;
|
||||
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
private void ContextMenu_Closed(object sender, RoutedEventArgs e)
|
||||
{
|
||||
contextMenuOpen = false;
|
||||
foreach (var item in dynamicMenuItems)
|
||||
{
|
||||
staticMenuItems.Remove(item);
|
||||
}
|
||||
}
|
||||
|
||||
private Point mousePos;
|
||||
/// <summary>
|
||||
/// Gets the mouse position when right mouse button was clicked.
|
||||
/// </summary>
|
||||
/// <value>The mouse position on click.</value>
|
||||
public Point MousePositionOnClick
|
||||
{
|
||||
get { return mousePos; }
|
||||
}
|
||||
|
||||
private void plotter_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
contextMenuOpen = false;
|
||||
mousePos = e.GetPosition(plotter);
|
||||
}
|
||||
|
||||
private bool contextMenuOpen = false;
|
||||
private readonly ObservableCollection<object> dynamicMenuItems = new ObservableCollection<object>();
|
||||
private void plotter_PreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
Point position = e.GetPosition(plotter);
|
||||
if (mousePos == position)
|
||||
{
|
||||
hitResults.Clear();
|
||||
VisualTreeHelper.HitTest(plotter, null, CollectAllVisuals_Callback, new PointHitTestParameters(position));
|
||||
|
||||
foreach (var item in dynamicMenuItems)
|
||||
{
|
||||
staticMenuItems.Remove(item);
|
||||
}
|
||||
dynamicMenuItems.Clear();
|
||||
var dynamicItems = (hitResults.Where(r =>
|
||||
{
|
||||
IPlotterContextMenuSource menuSource = r as IPlotterContextMenuSource;
|
||||
if (menuSource != null)
|
||||
{
|
||||
menuSource.BuildMenu();
|
||||
}
|
||||
|
||||
var items = GetPlotterContextMenu(r);
|
||||
return items != null && items.Count > 0;
|
||||
}).SelectMany(r =>
|
||||
{
|
||||
var menuItems = GetPlotterContextMenu(r);
|
||||
|
||||
FrameworkElement chart = r as FrameworkElement;
|
||||
if (chart != null)
|
||||
{
|
||||
foreach (var menuItem in menuItems.OfType<MenuItem>())
|
||||
{
|
||||
menuItem.DataContext = chart.DataContext;
|
||||
}
|
||||
}
|
||||
return menuItems;
|
||||
})).ToList();
|
||||
|
||||
foreach (var item in dynamicItems)
|
||||
{
|
||||
dynamicMenuItems.Add(item);
|
||||
//MenuItem menuItem = item as MenuItem;
|
||||
//if (menuItem != null)
|
||||
//{
|
||||
// menuItem.CommandTarget = plotter;
|
||||
//}
|
||||
}
|
||||
|
||||
staticMenuItems.AddMany(dynamicMenuItems);
|
||||
|
||||
plotter.Focus();
|
||||
contextMenuOpen = true;
|
||||
|
||||
// in XBAP applications these lines throws a SecurityException, as (as I think)
|
||||
// we are opening "new window" here. So in XBAP we are not opening context menu manually, but
|
||||
// it will be opened by itself as this is right click.
|
||||
#if !RELEASEXBAP
|
||||
plotter.ContextMenu.IsOpen = true;
|
||||
e.Handled = true;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is to prevent showing menu when RMB was pressed, then moved and now is releasing.
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#region PlotterContextMenu property
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plotter context menu.
|
||||
/// </summary>
|
||||
/// <param name="obj">The obj.</param>
|
||||
/// <returns></returns>
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public static ObjectCollection GetPlotterContextMenu(DependencyObject obj)
|
||||
{
|
||||
return (ObjectCollection)obj.GetValue(PlotterContextMenuProperty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the plotter context menu.
|
||||
/// </summary>
|
||||
/// <param name="obj">The obj.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public static void SetPlotterContextMenu(DependencyObject obj, ObjectCollection value)
|
||||
{
|
||||
obj.SetValue(PlotterContextMenuProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Identifies the PlotterContextMenu attached property.
|
||||
/// </summary>
|
||||
public static readonly DependencyProperty PlotterContextMenuProperty = DependencyProperty.RegisterAttached(
|
||||
"PlotterContextMenu",
|
||||
typeof(ObjectCollection),
|
||||
typeof(DefaultContextMenu),
|
||||
new FrameworkPropertyMetadata(null));
|
||||
|
||||
#endregion
|
||||
|
||||
private List<DependencyObject> hitResults = new List<DependencyObject>();
|
||||
private HitTestResultBehavior CollectAllVisuals_Callback(HitTestResult result)
|
||||
{
|
||||
if (result == null || result.VisualHit == null)
|
||||
return HitTestResultBehavior.Stop;
|
||||
|
||||
hitResults.Add(result.VisualHit);
|
||||
return HitTestResultBehavior.Continue;
|
||||
}
|
||||
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of additional menu items in ChartPlotter's context menu.
|
||||
/// </summary>
|
||||
public sealed class ObjectCollection : ObservableCollection<Object> { }
|
||||
}
|
||||
49
Charts/Navigation/EndlessRectAnimation.cs
Normal file
49
Charts/Navigation/EndlessRectAnimation.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
// todo commented because base class is 'Rect'AnimationBase, but animated value is of DataRect type.
|
||||
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
//using System.Text;
|
||||
//using System.Windows.Media.Animation;
|
||||
//using System.Windows;
|
||||
//using System.Diagnostics;
|
||||
//using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
//namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
//{
|
||||
// internal sealed class EndlessPanningRectAnimation : RectAnimationBase
|
||||
// {
|
||||
// private DataRect from;
|
||||
// public DataRect From
|
||||
// {
|
||||
// get { return from; }
|
||||
// set { from = value; }
|
||||
// }
|
||||
|
||||
// private Vector speed;
|
||||
// public Vector Speed
|
||||
// {
|
||||
// get { return speed; }
|
||||
// set { speed = value; }
|
||||
// }
|
||||
|
||||
// public EndlessPanningRectAnimation(DataRect from, Vector speed)
|
||||
// {
|
||||
// Duration = Duration.Forever;
|
||||
// this.from = from;
|
||||
// this.speed = speed;
|
||||
// }
|
||||
|
||||
// protected override Rect GetCurrentValueCore(Rect defaultOriginValue, Rect defaultDestinationValue, AnimationClock animationClock)
|
||||
// {
|
||||
// double time = animationClock.CurrentTime.Value.TotalSeconds;
|
||||
// Rect currentValue = Rect.Offset(from, speed * time);
|
||||
// return currentValue;
|
||||
// }
|
||||
|
||||
// protected override Freezable CreateInstanceCore()
|
||||
// {
|
||||
// return this;
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
59
Charts/Navigation/HorizontalScrollBar.cs
Normal file
59
Charts/Navigation/HorizontalScrollBar.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a horizontal scroll bar on the borrom of <see cref="Plotter"/>.
|
||||
/// Uses ChartPlotter.Plotter.Viewport.DataDomain property as a source of data about current position and position limits.
|
||||
/// </summary>
|
||||
public sealed class HorizontalScrollBar : PlotterScrollBar
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HorizontalScrollBar"/> class.
|
||||
/// </summary>
|
||||
public HorizontalScrollBar()
|
||||
{
|
||||
ScrollBar.Orientation = Orientation.Horizontal;
|
||||
}
|
||||
|
||||
protected override void UpdateScrollBar(Viewport2D viewport)
|
||||
{
|
||||
if (viewport != null && !viewport.Domain.IsEmpty)
|
||||
{
|
||||
var visibleRange = new Range<double>(viewport.Visible.XMin, viewport.Visible.XMax);
|
||||
|
||||
double size = visibleRange.Max - visibleRange.Min;
|
||||
ScrollBar.ViewportSize = size;
|
||||
|
||||
var domainRange = new Range<double>(viewport.Domain.XMin, viewport.Domain.XMax);
|
||||
ScrollBar.Minimum = domainRange.Min;
|
||||
ScrollBar.Maximum = domainRange.Max - size;
|
||||
|
||||
ScrollBar.Value = visibleRange.Min;
|
||||
|
||||
ScrollBar.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScrollBar.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
protected override DataRect CreateVisibleRect(DataRect rect, double value)
|
||||
{
|
||||
rect.XMin = value;
|
||||
return rect;
|
||||
}
|
||||
|
||||
protected override Panel GetHostPanel(Plotter plotter)
|
||||
{
|
||||
return plotter.BottomPanel;
|
||||
}
|
||||
}
|
||||
}
|
||||
12
Charts/Navigation/IPlotterContextMenuSource.cs
Normal file
12
Charts/Navigation/IPlotterContextMenuSource.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
public interface IPlotterContextMenuSource
|
||||
{
|
||||
void BuildMenu();
|
||||
}
|
||||
}
|
||||
94
Charts/Navigation/InertialMouseNavigation.cs
Normal file
94
Charts/Navigation/InertialMouseNavigation.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
// todo look to comment for EndlessAnimation
|
||||
|
||||
//using System;
|
||||
//using System.Collections.Generic;
|
||||
//using System.Linq;
|
||||
//using System.Text;
|
||||
//using Microsoft.Research.DynamicDataDisplay.Navigation;
|
||||
//using System.Windows.Input;
|
||||
//using System.Windows;
|
||||
//using System.Windows.Media.Animation;
|
||||
//using System.Windows.Media;
|
||||
//using System.Diagnostics;
|
||||
|
||||
//namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
//{
|
||||
// [Obsolete("Unready because Viewport became non-visual and no longer has BeginAnimation method.", true)]
|
||||
// public class InertialMouseNavigation : MouseNavigation
|
||||
// {
|
||||
// public InertialMouseNavigation()
|
||||
// {
|
||||
// }
|
||||
|
||||
// Point visibleStart;
|
||||
// Point panningStartPointInScreen;
|
||||
// int panningStartTime;
|
||||
// protected override void StartPanning(MouseButtonEventArgs e)
|
||||
// {
|
||||
// visibleStart = Viewport.Visible.Location;
|
||||
// panningStartPointInScreen = e.GetPosition(this);
|
||||
// base.StartPanning(e);
|
||||
// panningStartTime = e.Timestamp;
|
||||
// }
|
||||
|
||||
// bool runningAnimation = false;
|
||||
// protected override void StopPanning(MouseButtonEventArgs e)
|
||||
// {
|
||||
// Point panningStopPointInScreen = e.GetPosition(this);
|
||||
|
||||
// base.StopPanning(e);
|
||||
|
||||
|
||||
// // mouse click with no panning
|
||||
// if (panningStartPointInScreen == panningStopPointInScreen)
|
||||
// {
|
||||
// // remove all animations on Viewport.Visible
|
||||
// BeginVisibleAnimation(null);
|
||||
// return;
|
||||
// }
|
||||
|
||||
// double panningDuration = (e.Timestamp - panningStartTime) / 1000.0;
|
||||
|
||||
// double animationStartMinDuration = 0.5; // seconds
|
||||
// double animationStartMinMouseShift = 50; // pixels
|
||||
|
||||
// // mouse moved short enough and for rather long distance.
|
||||
// if (panningDuration < animationStartMinDuration && (panningStopPointInScreen - panningStartPointInScreen).Length > animationStartMinMouseShift)
|
||||
// {
|
||||
// Vector visibleShift = Viewport.Visible.Location - visibleStart;
|
||||
|
||||
// EndlessPanningRectAnimation panningAnimation = new EndlessPanningRectAnimation(
|
||||
// Viewport.Visible,
|
||||
// visibleShift / panningDuration);
|
||||
|
||||
// BeginVisibleAnimation(panningAnimation);
|
||||
// }
|
||||
// }
|
||||
|
||||
// private void BeginVisibleAnimation(AnimationTimeline animation)
|
||||
// {
|
||||
// if (animation != null)
|
||||
// {
|
||||
// if (runningAnimation)
|
||||
// {
|
||||
// BeginVisibleAnimation(null);
|
||||
// }
|
||||
|
||||
// //Plotter2D.Viewport.BeginAnimation(Viewport2D.VisibleProperty, animation);
|
||||
// runningAnimation = true;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// if (!runningAnimation)
|
||||
// return;
|
||||
|
||||
// Debug.WriteLine("Removing animation");
|
||||
|
||||
// Rect visible = Plotter2D.Viewport.Visible;
|
||||
// //Plotter2D.Viewport.BeginAnimation(Viewport2D.VisibleProperty, null);
|
||||
// Plotter2D.Viewport.Visible = visible;
|
||||
// runningAnimation = false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
454
Charts/Navigation/KeyboardNavigation.cs
Normal file
454
Charts/Navigation/KeyboardNavigation.cs
Normal file
@@ -0,0 +1,454 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Win32;
|
||||
using System;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.UndoSystem;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Navigation;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Provides keyboard navigation around viewport of ChartPlotter.</summary>
|
||||
public class KeyboardNavigation : IPlotterElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="KeyboardNavigation"/> class.
|
||||
/// </summary>
|
||||
public KeyboardNavigation() { }
|
||||
|
||||
private bool isReversed = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether panning directions are reversed.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if panning directions are reversed; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool IsReversed
|
||||
{
|
||||
get { return isReversed; }
|
||||
set { isReversed = value; }
|
||||
}
|
||||
|
||||
private readonly List<CommandBinding> addedBindings = new List<CommandBinding>();
|
||||
private void AddBinding(CommandBinding binding)
|
||||
{
|
||||
plotter2D.CommandBindings.Add(binding);
|
||||
addedBindings.Add(binding);
|
||||
}
|
||||
|
||||
private void InitCommands()
|
||||
{
|
||||
if (plotter2D == null)
|
||||
return;
|
||||
|
||||
var zoomOutToMouseCommandBinding = new CommandBinding(
|
||||
ChartCommands.ZoomOutToMouse,
|
||||
ZoomOutToMouseExecute,
|
||||
ZoomOutToMouseCanExecute);
|
||||
AddBinding(zoomOutToMouseCommandBinding);
|
||||
|
||||
var zoomInToMouseCommandBinding = new CommandBinding(
|
||||
ChartCommands.ZoomInToMouse,
|
||||
ZoomInToMouseExecute,
|
||||
ZoomInToMouseCanExecute);
|
||||
AddBinding(zoomInToMouseCommandBinding);
|
||||
|
||||
var zoomWithParamCommandBinding = new CommandBinding(
|
||||
ChartCommands.ZoomWithParameter,
|
||||
ZoomWithParamExecute,
|
||||
ZoomWithParamCanExecute);
|
||||
AddBinding(zoomWithParamCommandBinding);
|
||||
|
||||
var zoomInCommandBinding = new CommandBinding(
|
||||
ChartCommands.ZoomIn,
|
||||
ZoomInExecute,
|
||||
ZoomInCanExecute);
|
||||
AddBinding(zoomInCommandBinding);
|
||||
|
||||
var zoomOutCommandBinding = new CommandBinding(
|
||||
ChartCommands.ZoomOut,
|
||||
ZoomOutExecute,
|
||||
ZoomOutCanExecute);
|
||||
AddBinding(zoomOutCommandBinding);
|
||||
|
||||
var fitToViewCommandBinding = new CommandBinding(
|
||||
ChartCommands.FitToView,
|
||||
FitToViewExecute,
|
||||
FitToViewCanExecute);
|
||||
AddBinding(fitToViewCommandBinding);
|
||||
|
||||
var ScrollLeftCommandBinding = new CommandBinding(
|
||||
ChartCommands.ScrollLeft,
|
||||
ScrollLeftExecute,
|
||||
ScrollLeftCanExecute);
|
||||
AddBinding(ScrollLeftCommandBinding);
|
||||
|
||||
var ScrollRightCommandBinding = new CommandBinding(
|
||||
ChartCommands.ScrollRight,
|
||||
ScrollRightExecute,
|
||||
ScrollRightCanExecute);
|
||||
AddBinding(ScrollRightCommandBinding);
|
||||
|
||||
var ScrollUpCommandBinding = new CommandBinding(
|
||||
ChartCommands.ScrollUp,
|
||||
ScrollUpExecute,
|
||||
ScrollUpCanExecute);
|
||||
AddBinding(ScrollUpCommandBinding);
|
||||
|
||||
var ScrollDownCommandBinding = new CommandBinding(
|
||||
ChartCommands.ScrollDown,
|
||||
ScrollDownExecute,
|
||||
ScrollDownCanExecute);
|
||||
AddBinding(ScrollDownCommandBinding);
|
||||
|
||||
var SaveScreenshotCommandBinding = new CommandBinding(
|
||||
ChartCommands.SaveScreenshot,
|
||||
SaveScreenshotExecute,
|
||||
SaveScreenshotCanExecute);
|
||||
AddBinding(SaveScreenshotCommandBinding);
|
||||
|
||||
var CopyScreenshotCommandBinding = new CommandBinding(
|
||||
ChartCommands.CopyScreenshot,
|
||||
CopyScreenshotExecute,
|
||||
CopyScreenshotCanExecute);
|
||||
AddBinding(CopyScreenshotCommandBinding);
|
||||
|
||||
var ShowHelpCommandBinding = new CommandBinding(
|
||||
ChartCommands.ShowHelp,
|
||||
ShowHelpExecute,
|
||||
ShowHelpCanExecute);
|
||||
AddBinding(ShowHelpCommandBinding);
|
||||
|
||||
var UndoCommandBinding = new CommandBinding(
|
||||
ApplicationCommands.Undo,
|
||||
UndoExecute,
|
||||
UndoCanExecute);
|
||||
AddBinding(UndoCommandBinding);
|
||||
|
||||
var RedoCommandBinding = new CommandBinding(
|
||||
ApplicationCommands.Redo,
|
||||
RedoExecute,
|
||||
RedoCanExecute);
|
||||
AddBinding(RedoCommandBinding);
|
||||
}
|
||||
|
||||
#region Zoom Out To Mouse
|
||||
|
||||
private void ZoomToPoint(double coeff)
|
||||
{
|
||||
Point pt = Mouse.GetPosition(plotter2D.CentralGrid);
|
||||
Point dataPoint = Viewport.Transform.ScreenToData(pt);
|
||||
DataRect visible = Viewport.Visible;
|
||||
|
||||
Viewport.Visible = visible.Zoom(dataPoint, coeff);
|
||||
}
|
||||
|
||||
private void ZoomOutToMouseExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ZoomToPoint(zoomOutCoeff);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ZoomOutToMouseCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Zoom In To Mouse
|
||||
|
||||
private void ZoomInToMouseExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ZoomToPoint(zoomInCoeff);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ZoomInToMouseCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Zoom With param
|
||||
|
||||
private void ZoomWithParamExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
double zoomParam = (double)e.Parameter;
|
||||
plotter2D.Viewport.Zoom(zoomParam);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ZoomWithParamCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Zoom in
|
||||
|
||||
private const double zoomInCoeff = 0.9;
|
||||
private void ZoomInExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
Viewport.Zoom(zoomInCoeff);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ZoomInCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Zoom out
|
||||
|
||||
private const double zoomOutCoeff = 1 / zoomInCoeff;
|
||||
private void ZoomOutExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
Viewport.Zoom(zoomOutCoeff);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ZoomOutCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fit to view
|
||||
|
||||
private void FitToViewExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
// todo сделать нормально.
|
||||
(Viewport as Viewport2D).FitToView();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void FitToViewCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
// todo add a check if viewport is already fitted to view.
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scroll
|
||||
|
||||
private double scrollCoeff = 0.05;
|
||||
private void ScrollVisibleProportionally(double xShiftCoeff, double yShiftCoeff)
|
||||
{
|
||||
DataRect visible = Viewport.Visible;
|
||||
DataRect oldVisible = visible;
|
||||
double width = visible.Width;
|
||||
double height = visible.Height;
|
||||
|
||||
double reverseCoeff = isReversed ? -1 : 1;
|
||||
visible.Offset(reverseCoeff * xShiftCoeff * width, reverseCoeff * yShiftCoeff * height);
|
||||
|
||||
Viewport.Visible = visible;
|
||||
plotter2D.UndoProvider.AddAction(new DependencyPropertyChangedUndoAction(Viewport, Viewport2D.VisibleProperty, oldVisible, visible));
|
||||
}
|
||||
|
||||
#region ScrollLeft
|
||||
|
||||
private void ScrollLeftExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ScrollVisibleProportionally(scrollCoeff, 0);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollLeftCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScrollRight
|
||||
|
||||
private void ScrollRightExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ScrollVisibleProportionally(-scrollCoeff, 0);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollRightCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScrollUp
|
||||
|
||||
private void ScrollUpExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ScrollVisibleProportionally(0, -scrollCoeff);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollUpCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScrollDown
|
||||
|
||||
private void ScrollDownExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ScrollVisibleProportionally(0, scrollCoeff);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollDownCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region SaveScreenshot
|
||||
|
||||
private void SaveScreenshotExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
SaveFileDialog dlg = new SaveFileDialog();
|
||||
dlg.Filter = "PNG (*.png)|*.png|JPEG (*.jpg)|*.jpg|BMP (*.bmp)|*.bmp|GIF (*.gif)|*.gif";
|
||||
dlg.FilterIndex = 1;
|
||||
dlg.AddExtension = true;
|
||||
if (dlg.ShowDialog().GetValueOrDefault(false))
|
||||
{
|
||||
string filePath = dlg.FileName;
|
||||
plotter2D.SaveScreenshot(filePath);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveScreenshotCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CopyScreenshot
|
||||
|
||||
private void CopyScreenshotExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
plotter2D.CopyScreenshotToClipboard();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void CopyScreenshotCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ShowHelp
|
||||
|
||||
private bool aboutWindowOpened = false;
|
||||
private void ShowHelpExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
if (!aboutWindowOpened)
|
||||
{
|
||||
AboutWindow window = new AboutWindow();
|
||||
window.Closed += new EventHandler(aboutWindow_Closed);
|
||||
window.DataContext = plotter2D;
|
||||
|
||||
window.Owner = Window.GetWindow(plotter2D);
|
||||
|
||||
aboutWindowOpened = true;
|
||||
window.Show();
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
void aboutWindow_Closed(object sender, EventArgs e)
|
||||
{
|
||||
Window window = (Window)sender;
|
||||
window.Closed -= aboutWindow_Closed;
|
||||
aboutWindowOpened = false;
|
||||
}
|
||||
|
||||
private void ShowHelpCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = !aboutWindowOpened;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Undo
|
||||
|
||||
private void UndoExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
plotter2D.UndoProvider.Undo();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void UndoCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = plotter2D.UndoProvider.CanUndo;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Redo
|
||||
|
||||
private void RedoExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
plotter2D.UndoProvider.Redo();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void RedoCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = plotter2D.UndoProvider.CanRedo;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
private Viewport2D Viewport
|
||||
{
|
||||
get { return plotter2D.Viewport; }
|
||||
}
|
||||
|
||||
private Plotter2D plotter2D;
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
plotter2D = (Plotter2D)plotter;
|
||||
|
||||
InitCommands();
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
foreach (var commandBinding in addedBindings)
|
||||
{
|
||||
plotter.CommandBindings.Remove(commandBinding);
|
||||
}
|
||||
addedBindings.Clear();
|
||||
|
||||
this.plotter2D = null;
|
||||
}
|
||||
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter2D; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
144
Charts/Navigation/LongOperationsIndicator.cs
Normal file
144
Charts/Navigation/LongOperationsIndicator.cs
Normal file
@@ -0,0 +1,144 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
public sealed class LongOperationsIndicator : IPlotterElement
|
||||
{
|
||||
public LongOperationsIndicator()
|
||||
{
|
||||
timer.Tick += new EventHandler(timer_Tick);
|
||||
}
|
||||
|
||||
private void timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
UpdateWaitIndicator();
|
||||
timer.Stop();
|
||||
}
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = plotter;
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
private Plotter plotter;
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region LongOperationRunning
|
||||
|
||||
public static void BeginLongOperation(DependencyObject obj)
|
||||
{
|
||||
obj.SetValue(LongOperationRunningProperty, true);
|
||||
}
|
||||
|
||||
public static void EndLongOperation(DependencyObject obj)
|
||||
{
|
||||
obj.SetValue(LongOperationRunningProperty, false);
|
||||
}
|
||||
|
||||
public static bool GetLongOperationRunning(DependencyObject obj)
|
||||
{
|
||||
return (bool)obj.GetValue(LongOperationRunningProperty);
|
||||
}
|
||||
|
||||
public static void SetLongOperationRunning(DependencyObject obj, bool value)
|
||||
{
|
||||
obj.SetValue(LongOperationRunningProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LongOperationRunningProperty = DependencyProperty.RegisterAttached(
|
||||
"LongOperationRunning",
|
||||
typeof(bool),
|
||||
typeof(LongOperationsIndicator),
|
||||
new FrameworkPropertyMetadata(false, OnLongOperationRunningChanged));
|
||||
|
||||
private static void OnLongOperationRunningChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
Plotter plotter = null;
|
||||
IPlotterElement element = d as IPlotterElement;
|
||||
if (element == null)
|
||||
{
|
||||
plotter = Plotter.GetPlotter(d);
|
||||
}
|
||||
else
|
||||
{
|
||||
plotter = element.Plotter;
|
||||
}
|
||||
|
||||
if (plotter != null)
|
||||
{
|
||||
var indicator = plotter.Children.OfType<LongOperationsIndicator>().FirstOrDefault();
|
||||
if (indicator != null)
|
||||
{
|
||||
indicator.OnLongOperationRunningChanged(element, (bool)e.NewValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UIElement indicator = LoadIndicator();
|
||||
|
||||
private static UIElement LoadIndicator()
|
||||
{
|
||||
var resources = (ResourceDictionary)Application.LoadComponent(new Uri("/DynamicDataDisplay;component/Charts/Navigation/LongOperationsIndicatorResources.xaml", UriKind.Relative));
|
||||
UIElement indicator = (UIElement)resources["Indicator"];
|
||||
return indicator;
|
||||
}
|
||||
|
||||
private readonly DispatcherTimer timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
|
||||
private int operationsCounter = 0;
|
||||
private void OnLongOperationRunningChanged(IPlotterElement element, bool longOperationRunning)
|
||||
{
|
||||
int change = longOperationRunning ? +1 : -1;
|
||||
operationsCounter += change;
|
||||
|
||||
if (plotter == null) return;
|
||||
|
||||
if (operationsCounter == 1)
|
||||
{
|
||||
timer.Start();
|
||||
}
|
||||
else if (operationsCounter == 0)
|
||||
{
|
||||
timer.Stop();
|
||||
UpdateWaitIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateWaitIndicator()
|
||||
{
|
||||
if (operationsCounter == 1)
|
||||
{
|
||||
if (!plotter.MainCanvas.Children.Contains(indicator))
|
||||
{
|
||||
plotter.MainCanvas.Children.Add(indicator);
|
||||
}
|
||||
plotter.Cursor = Cursors.Wait;
|
||||
}
|
||||
else if (operationsCounter == 0)
|
||||
{
|
||||
plotter.MainCanvas.Children.Remove(indicator);
|
||||
plotter.ClearValue(Plotter.CursorProperty);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion // end of LongOperationRunning
|
||||
}
|
||||
}
|
||||
11
Charts/Navigation/LongOperationsIndicatorResources.xaml
Normal file
11
Charts/Navigation/LongOperationsIndicatorResources.xaml
Normal file
@@ -0,0 +1,11 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:str="clr-namespace:Microsoft.Research.DynamicDataDisplay.Strings"
|
||||
xmlns:m="clr-namespace:Microsoft.Research.DynamicDataDisplay.MarkupExtensions">
|
||||
|
||||
<Grid Canvas.Bottom="10" Canvas.Left="10" Panel.ZIndex="2" x:Key="Indicator">
|
||||
<Rectangle RadiusX="5" RadiusY="5" Fill="#B0FFFFFF" Stroke="LightGray" StrokeThickness="1"/>
|
||||
<TextBlock Text="{m:Resource Loading}" Margin="3"/>
|
||||
</Grid>
|
||||
|
||||
</ResourceDictionary>
|
||||
32
Charts/Navigation/MessagesHelper.cs
Normal file
32
Charts/Navigation/MessagesHelper.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Helper class to parse Windows messages</summary>
|
||||
internal static class MessagesHelper {
|
||||
internal static int GetXFromLParam(IntPtr lParam) {
|
||||
return LOWORD(lParam.ToInt32());
|
||||
}
|
||||
|
||||
internal static int GetYFromLParam(IntPtr lParam) {
|
||||
return HIWORD(lParam.ToInt32());
|
||||
}
|
||||
|
||||
internal static Point GetMousePosFromLParam(IntPtr lParam) {
|
||||
return new Point(GetXFromLParam(lParam), GetYFromLParam(lParam));
|
||||
}
|
||||
|
||||
internal static int GetWheelDataFromWParam(IntPtr wParam) {
|
||||
return HIWORD(wParam.ToInt32());
|
||||
}
|
||||
|
||||
internal static short HIWORD(int i) {
|
||||
return (short)((i & 0xFFFF0000) >> 16);
|
||||
}
|
||||
|
||||
internal static short LOWORD(int i) {
|
||||
return (short)(i & 0x0000FFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
406
Charts/Navigation/MouseNavigation.cs
Normal file
406
Charts/Navigation/MouseNavigation.cs
Normal file
@@ -0,0 +1,406 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Diagnostics;
|
||||
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Provides common methods of mouse navigation around viewport</summary>
|
||||
public class MouseNavigation : NavigationBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MouseNavigation"/> class.
|
||||
/// </summary>
|
||||
public MouseNavigation() { }
|
||||
|
||||
private AdornerLayer adornerLayer;
|
||||
protected AdornerLayer AdornerLayer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (adornerLayer == null)
|
||||
{
|
||||
adornerLayer = AdornerLayer.GetAdornerLayer(this);
|
||||
if (adornerLayer != null)
|
||||
{
|
||||
adornerLayer.IsHitTestVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
return adornerLayer;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
base.OnPlotterAttached(plotter);
|
||||
|
||||
Mouse.AddMouseDownHandler(Parent, OnMouseDown);
|
||||
Mouse.AddMouseMoveHandler(Parent, OnMouseMove);
|
||||
Mouse.AddMouseUpHandler(Parent, OnMouseUp);
|
||||
Mouse.AddMouseWheelHandler(Parent, OnMouseWheel);
|
||||
|
||||
plotter.KeyDown += new KeyEventHandler(OnParentKeyDown);
|
||||
}
|
||||
|
||||
protected override void OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
plotter.KeyDown -= new KeyEventHandler(OnParentKeyDown);
|
||||
|
||||
Mouse.RemoveMouseDownHandler(Parent, OnMouseDown);
|
||||
Mouse.RemoveMouseMoveHandler(Parent, OnMouseMove);
|
||||
Mouse.RemoveMouseUpHandler(Parent, OnMouseUp);
|
||||
Mouse.RemoveMouseWheelHandler(Parent, OnMouseWheel);
|
||||
|
||||
base.OnPlotterDetaching(plotter);
|
||||
}
|
||||
|
||||
private void OnParentKeyDown(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (e.Key == Key.Escape || e.Key == Key.Back)
|
||||
{
|
||||
if (isZooming)
|
||||
{
|
||||
isZooming = false;
|
||||
zoomRect = null;
|
||||
ReleaseMouseCapture();
|
||||
RemoveSelectionAdorner();
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
if (!e.Handled)
|
||||
{
|
||||
Point mousePos = e.GetPosition(this);
|
||||
int delta = -e.Delta;
|
||||
MouseWheelZoom(mousePos, delta);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override string ToString()
|
||||
{
|
||||
if (!String.IsNullOrEmpty(Name))
|
||||
{
|
||||
return Name;
|
||||
}
|
||||
return base.ToString();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool adornerAdded;
|
||||
RectangleSelectionAdorner selectionAdorner;
|
||||
private void AddSelectionAdorner()
|
||||
{
|
||||
if (!adornerAdded)
|
||||
{
|
||||
AdornerLayer layer = AdornerLayer;
|
||||
if (layer != null)
|
||||
{
|
||||
selectionAdorner = new RectangleSelectionAdorner(this) { Border = zoomRect };
|
||||
|
||||
layer.Add(selectionAdorner);
|
||||
adornerAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSelectionAdorner()
|
||||
{
|
||||
AdornerLayer layer = AdornerLayer;
|
||||
if (layer != null)
|
||||
{
|
||||
layer.Remove(selectionAdorner);
|
||||
adornerAdded = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelectionAdorner()
|
||||
{
|
||||
selectionAdorner.Border = zoomRect;
|
||||
selectionAdorner.InvalidateVisual();
|
||||
}
|
||||
|
||||
Rect? zoomRect = null;
|
||||
private const double wheelZoomSpeed = 1.2;
|
||||
private bool shouldKeepRatioWhileZooming;
|
||||
|
||||
private bool isZooming = false;
|
||||
protected bool IsZooming
|
||||
{
|
||||
get { return isZooming; }
|
||||
}
|
||||
|
||||
private bool isPanning = false;
|
||||
protected bool IsPanning
|
||||
{
|
||||
get { return isPanning; }
|
||||
}
|
||||
|
||||
private Point panningStartPointInViewport;
|
||||
protected Point PanningStartPointInViewport
|
||||
{
|
||||
get { return panningStartPointInViewport; }
|
||||
}
|
||||
|
||||
private Point zoomStartPoint;
|
||||
|
||||
private static bool IsShiftOrCtrl
|
||||
{
|
||||
get
|
||||
{
|
||||
ModifierKeys currKeys = Keyboard.Modifiers;
|
||||
return (currKeys | ModifierKeys.Shift) == currKeys ||
|
||||
(currKeys | ModifierKeys.Control) == currKeys;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool ShouldStartPanning(MouseButtonEventArgs e)
|
||||
{
|
||||
return e.ChangedButton == MouseButton.Left && Keyboard.Modifiers == ModifierKeys.None;
|
||||
}
|
||||
|
||||
protected virtual bool ShouldStartZoom(MouseButtonEventArgs e)
|
||||
{
|
||||
return e.ChangedButton == MouseButton.Left && IsShiftOrCtrl;
|
||||
}
|
||||
|
||||
Point panningStartPointInScreen;
|
||||
protected virtual void StartPanning(MouseButtonEventArgs e)
|
||||
{
|
||||
panningStartPointInScreen = e.GetPosition(this);
|
||||
panningStartPointInViewport = panningStartPointInScreen.ScreenToViewport(Viewport.Transform);
|
||||
|
||||
Plotter2D.UndoProvider.CaptureOldValue(Viewport, Viewport2D.VisibleProperty, Viewport.Visible);
|
||||
|
||||
isPanning = true;
|
||||
|
||||
// not capturing mouse because this made some tools like PointSelector not
|
||||
// receive MouseUp events on markers;
|
||||
// Mouse will be captured later, in the first MouseMove handler call.
|
||||
// CaptureMouse();
|
||||
|
||||
Viewport.PanningState = Viewport2DPanningState.Panning;
|
||||
|
||||
//e.Handled = true;
|
||||
}
|
||||
|
||||
protected virtual void StartZoom(MouseButtonEventArgs e)
|
||||
{
|
||||
zoomStartPoint = e.GetPosition(this);
|
||||
if (Viewport.Output.Contains(zoomStartPoint))
|
||||
{
|
||||
isZooming = true;
|
||||
AddSelectionAdorner();
|
||||
CaptureMouse();
|
||||
shouldKeepRatioWhileZooming = Keyboard.Modifiers == ModifierKeys.Shift;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
// dragging
|
||||
bool shouldStartDrag = ShouldStartPanning(e);
|
||||
if (shouldStartDrag)
|
||||
StartPanning(e);
|
||||
|
||||
// zooming
|
||||
bool shouldStartZoom = ShouldStartZoom(e);
|
||||
if (shouldStartZoom)
|
||||
StartZoom(e);
|
||||
|
||||
if (!Plotter.IsFocused)
|
||||
{
|
||||
//var window = Window.GetWindow(Plotter);
|
||||
//var focusWithinWindow = FocusManager.GetFocusedElement(window) != null;
|
||||
|
||||
Plotter.Focus();
|
||||
|
||||
//if (!focusWithinWindow)
|
||||
//{
|
||||
|
||||
// this is done to prevent other tools like PointSelector from getting mouse click event when clicking on plotter
|
||||
// to activate window it's contained within
|
||||
e.Handled = true;
|
||||
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
if (!isPanning && !isZooming) return;
|
||||
|
||||
// dragging
|
||||
if (isPanning && e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
if (!IsMouseCaptured)
|
||||
{
|
||||
CaptureMouse();
|
||||
}
|
||||
|
||||
Point endPoint = e.GetPosition(this).ScreenToViewport(Viewport.Transform);
|
||||
|
||||
Point loc = Viewport.Visible.Location;
|
||||
Vector shift = panningStartPointInViewport - endPoint;
|
||||
loc += shift;
|
||||
|
||||
// preventing unnecessary changes, if actually visible hasn't change.
|
||||
if (shift.X != 0 || shift.Y != 0)
|
||||
{
|
||||
Cursor = Cursors.ScrollAll;
|
||||
|
||||
DataRect visible = Viewport.Visible;
|
||||
|
||||
visible.Location = loc;
|
||||
Viewport.Visible = visible;
|
||||
}
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
// zooming
|
||||
else if (isZooming && e.LeftButton == MouseButtonState.Pressed)
|
||||
{
|
||||
Point zoomEndPoint = e.GetPosition(this);
|
||||
UpdateZoomRect(zoomEndPoint);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsShiftPressed()
|
||||
{
|
||||
return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
}
|
||||
|
||||
private void UpdateZoomRect(Point zoomEndPoint)
|
||||
{
|
||||
Rect output = Viewport.Output;
|
||||
Rect tmpZoomRect = new Rect(zoomStartPoint, zoomEndPoint);
|
||||
tmpZoomRect = Rect.Intersect(tmpZoomRect, output);
|
||||
|
||||
shouldKeepRatioWhileZooming = IsShiftPressed();
|
||||
if (shouldKeepRatioWhileZooming)
|
||||
{
|
||||
double currZoomRatio = tmpZoomRect.Width / tmpZoomRect.Height;
|
||||
double zoomRatio = output.Width / output.Height;
|
||||
if (currZoomRatio < zoomRatio)
|
||||
{
|
||||
double oldHeight = tmpZoomRect.Height;
|
||||
double height = tmpZoomRect.Width / zoomRatio;
|
||||
tmpZoomRect.Height = height;
|
||||
if (!tmpZoomRect.Contains(zoomStartPoint))
|
||||
{
|
||||
tmpZoomRect.Offset(0, oldHeight - height);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
double oldWidth = tmpZoomRect.Width;
|
||||
double width = tmpZoomRect.Height * zoomRatio;
|
||||
tmpZoomRect.Width = width;
|
||||
if (!tmpZoomRect.Contains(zoomStartPoint))
|
||||
{
|
||||
tmpZoomRect.Offset(oldWidth - width, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoomRect = tmpZoomRect;
|
||||
UpdateSelectionAdorner();
|
||||
}
|
||||
|
||||
private void OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
OnParentMouseUp(e);
|
||||
}
|
||||
|
||||
protected virtual void OnParentMouseUp(MouseButtonEventArgs e)
|
||||
{
|
||||
if (isPanning && e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
isPanning = false;
|
||||
StopPanning(e);
|
||||
}
|
||||
else if (isZooming && e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
isZooming = false;
|
||||
StopZooming();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void StopZooming()
|
||||
{
|
||||
if (zoomRect.HasValue)
|
||||
{
|
||||
Rect output = Viewport.Output;
|
||||
|
||||
Point p1 = zoomRect.Value.TopLeft.ScreenToViewport(Viewport.Transform);
|
||||
Point p2 = zoomRect.Value.BottomRight.ScreenToViewport(Viewport.Transform);
|
||||
DataRect newVisible = new DataRect(p1, p2);
|
||||
Viewport.Visible = newVisible;
|
||||
|
||||
zoomRect = null;
|
||||
ReleaseMouseCapture();
|
||||
RemoveSelectionAdorner();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void StopPanning(MouseButtonEventArgs e)
|
||||
{
|
||||
Plotter2D.UndoProvider.CaptureNewValue(Plotter2D.Viewport, Viewport2D.VisibleProperty, Viewport.Visible);
|
||||
|
||||
if (!Plotter.IsFocused)
|
||||
{
|
||||
Plotter2D.Focus();
|
||||
}
|
||||
|
||||
Plotter2D.Viewport.PanningState = Viewport2DPanningState.NotPanning;
|
||||
|
||||
ReleaseMouseCapture();
|
||||
ClearValue(CursorProperty);
|
||||
}
|
||||
|
||||
protected override void OnLostFocus(RoutedEventArgs e)
|
||||
{
|
||||
if (isZooming)
|
||||
{
|
||||
RemoveSelectionAdorner();
|
||||
isZooming = false;
|
||||
}
|
||||
if (isPanning)
|
||||
{
|
||||
Plotter2D.Viewport.PanningState = Viewport2DPanningState.NotPanning;
|
||||
isPanning = false;
|
||||
}
|
||||
ReleaseMouseCapture();
|
||||
base.OnLostFocus(e);
|
||||
}
|
||||
|
||||
private void MouseWheelZoom(Point mousePos, double wheelRotationDelta)
|
||||
{
|
||||
Point zoomTo = mousePos.ScreenToViewport(Viewport.Transform);
|
||||
|
||||
double zoomSpeed = Math.Abs(wheelRotationDelta / Mouse.MouseWheelDeltaForOneLine);
|
||||
zoomSpeed *= wheelZoomSpeed;
|
||||
if (wheelRotationDelta < 0)
|
||||
{
|
||||
zoomSpeed = 1 / zoomSpeed;
|
||||
}
|
||||
Viewport.Visible = Viewport.Visible.Zoom(zoomTo, zoomSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
63
Charts/Navigation/MouseNavigationBase.cs
Normal file
63
Charts/Navigation/MouseNavigationBase.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
// todo проверить, как происходит работа когда мышь не над плоттером, а над его ребенком
|
||||
// todo если все ОК, то перевести все маус навигейшн контролы на этот класс как базовый
|
||||
public abstract class MouseNavigationBase : NavigationBase
|
||||
{
|
||||
protected override void OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
base.OnPlotterAttached(plotter);
|
||||
|
||||
Mouse.AddMouseDownHandler(Parent, OnMouseDown);
|
||||
Mouse.AddMouseMoveHandler(Parent, OnMouseMove);
|
||||
Mouse.AddMouseUpHandler(Parent, OnMouseUp);
|
||||
Mouse.AddMouseWheelHandler(Parent, OnMouseWheel);
|
||||
}
|
||||
|
||||
protected override void OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
Mouse.RemoveMouseDownHandler(Parent, OnMouseDown);
|
||||
Mouse.RemoveMouseMoveHandler(Parent, OnMouseMove);
|
||||
Mouse.RemoveMouseUpHandler(Parent, OnMouseUp);
|
||||
Mouse.RemoveMouseWheelHandler(Parent, OnMouseWheel);
|
||||
|
||||
base.OnPlotterDetaching(plotter);
|
||||
}
|
||||
|
||||
private void OnMouseWheel(object sender, MouseWheelEventArgs e)
|
||||
{
|
||||
OnPlotterMouseWheel(e);
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterMouseWheel(MouseWheelEventArgs e) { }
|
||||
|
||||
private void OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
OnPlotterMouseUp(e);
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterMouseUp(MouseButtonEventArgs e) { }
|
||||
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
OnPlotterMouseDown(e);
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterMouseDown(MouseButtonEventArgs e) { }
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e)
|
||||
{
|
||||
OnPlotterMouseMove(e);
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterMouseMove(MouseEventArgs e) { }
|
||||
|
||||
}
|
||||
}
|
||||
103
Charts/Navigation/Navigation/ChartCommands.cs
Normal file
103
Charts/Navigation/Navigation/ChartCommands.cs
Normal file
@@ -0,0 +1,103 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
// TODO: probably optimize memory usage by replacing implicit creation of
|
||||
// all commands on first usage of this class -
|
||||
// create each command on first access directly to it.
|
||||
|
||||
/// <summary>This class holds keyboard shortcuts to common viewport navigation commands</summary>
|
||||
public static class ChartCommands
|
||||
{
|
||||
|
||||
#region Auxiliary code for creation of commands
|
||||
|
||||
private static RoutedUICommand CreateCommand(string name)
|
||||
{
|
||||
return new RoutedUICommand(name, name, typeof(ChartCommands));
|
||||
}
|
||||
|
||||
private static RoutedUICommand CreateCommand(string name, params Key[] keys)
|
||||
{
|
||||
InputGestureCollection gestures = new InputGestureCollection();
|
||||
foreach (var key in keys)
|
||||
{
|
||||
gestures.Add(new KeyGesture(key));
|
||||
}
|
||||
return new RoutedUICommand(name, name, typeof(ChartCommands), gestures);
|
||||
}
|
||||
|
||||
private static RoutedUICommand CreateCommand(string name, params InputGesture[] gestures)
|
||||
{
|
||||
return new RoutedUICommand(name, name, typeof(ChartCommands), new InputGestureCollection(gestures));
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly RoutedUICommand zoomWithParam = CreateCommand("ZoomWithParam");
|
||||
public static RoutedUICommand ZoomWithParameter
|
||||
{
|
||||
get { return zoomWithParam; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand zoomIn = CreateCommand("ZoomIn", Key.OemPlus);
|
||||
public static RoutedUICommand ZoomIn
|
||||
{
|
||||
get { return zoomIn; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand zoomOut = CreateCommand("ZoomOut", Key.OemMinus);
|
||||
public static RoutedUICommand ZoomOut
|
||||
{
|
||||
get { return zoomOut; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand fitToView = CreateCommand("FitToView", Key.Home);
|
||||
public static RoutedUICommand FitToView
|
||||
{
|
||||
get { return ChartCommands.fitToView; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand scrollLeft = CreateCommand("ScrollLeft", Key.Left);
|
||||
public static RoutedUICommand ScrollLeft
|
||||
{
|
||||
get { return ChartCommands.scrollLeft; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand scrollRight = CreateCommand("ScrollRight", Key.Right);
|
||||
public static RoutedUICommand ScrollRight
|
||||
{
|
||||
get { return ChartCommands.scrollRight; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand scrollUp = CreateCommand("ScrollUp", Key.Up);
|
||||
public static RoutedUICommand ScrollUp
|
||||
{
|
||||
get { return ChartCommands.scrollUp; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand scrollDown = CreateCommand("ScrollDown", Key.Down);
|
||||
public static RoutedUICommand ScrollDown
|
||||
{
|
||||
get { return ChartCommands.scrollDown; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand saveScreenshot = CreateCommand("SaveScreenshot", new KeyGesture(Key.S, ModifierKeys.Control));
|
||||
public static RoutedUICommand SaveScreenshot
|
||||
{
|
||||
get { return ChartCommands.saveScreenshot; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand copyScreenshot = CreateCommand("CopyScreenshot", Key.F11);
|
||||
public static RoutedUICommand CopyScreenshot
|
||||
{
|
||||
get { return ChartCommands.copyScreenshot; }
|
||||
}
|
||||
|
||||
private static readonly RoutedUICommand showHelp = CreateCommand("ShowHelp", Key.F1);
|
||||
public static RoutedUICommand ShowHelp
|
||||
{
|
||||
get { return ChartCommands.showHelp; }
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Charts/Navigation/Navigation/DefaultContextMenu.cs
Normal file
91
Charts/Navigation/Navigation/DefaultContextMenu.cs
Normal file
@@ -0,0 +1,91 @@
|
||||
#define _template
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Win32;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Media;
|
||||
using System.Reflection;
|
||||
using System;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>This class is responsible for displaying and populating default context menu</summary>
|
||||
public partial class DefaultContextMenu : NavigationBase
|
||||
{
|
||||
static BitmapImage helpIcon;
|
||||
static BitmapImage copyScreenshotIcon;
|
||||
static BitmapImage saveScreenshotIcon;
|
||||
static BitmapImage fitToViewIcon;
|
||||
static DefaultContextMenu()
|
||||
{
|
||||
helpIcon = LoadIcon("HelpIcon");
|
||||
saveScreenshotIcon = LoadIcon("SaveIcon");
|
||||
copyScreenshotIcon = LoadIcon("CopyScreenshotIcon");
|
||||
fitToViewIcon = LoadIcon("FitToViewIcon");
|
||||
}
|
||||
|
||||
private static BitmapImage LoadIcon(string name)
|
||||
{
|
||||
Assembly currentAssembly = typeof(DefaultContextMenu).Assembly;
|
||||
|
||||
BitmapImage icon = new BitmapImage();
|
||||
icon.BeginInit();
|
||||
icon.StreamSource = currentAssembly.GetManifestResourceStream("Microsoft.Research.DynamicDataDisplay.Resources." + name + ".png");
|
||||
icon.EndInit();
|
||||
return icon;
|
||||
}
|
||||
|
||||
public DefaultContextMenu()
|
||||
{
|
||||
InitContextMenu();
|
||||
}
|
||||
|
||||
public void InitContextMenu()
|
||||
{
|
||||
ContextMenu menu = new ContextMenu();
|
||||
MenuItem fitToViewMenuItem = new MenuItem
|
||||
{
|
||||
Header = "Fit to view",
|
||||
ToolTip = "Make visible area fit to display entire contents",
|
||||
Icon = new Image { Source = fitToViewIcon },
|
||||
Command = ChartCommands.FitToView
|
||||
};
|
||||
|
||||
MenuItem savePictureMenuItem = new MenuItem
|
||||
{
|
||||
Header = "Save picture",
|
||||
ToolTip = "Specify name of image file to save screenshot to",
|
||||
Icon = new Image { Source = saveScreenshotIcon },
|
||||
Command = ChartCommands.SaveScreenshot
|
||||
};
|
||||
|
||||
MenuItem copyPictureMenuItem = new MenuItem
|
||||
{
|
||||
Header = "Copy picture",
|
||||
ToolTip = "Copy screenshot of charts to clipboard",
|
||||
Icon = new Image { Source = copyScreenshotIcon },
|
||||
Command = ChartCommands.CopyScreenshot
|
||||
};
|
||||
|
||||
MenuItem quickHelpMenuItem = new MenuItem
|
||||
{
|
||||
Header = "Quick Help",
|
||||
ToolTip = "View brief instructions",
|
||||
Command = ChartCommands.ShowHelp,
|
||||
Icon = new Image { Source = helpIcon }
|
||||
};
|
||||
|
||||
List<MenuItem> menuItems = new List<MenuItem> {
|
||||
fitToViewMenuItem,
|
||||
copyPictureMenuItem,
|
||||
savePictureMenuItem,
|
||||
quickHelpMenuItem,
|
||||
};
|
||||
menu.ItemsSource = menuItems;
|
||||
|
||||
ContextMenu = menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
317
Charts/Navigation/Navigation/InertialMouseNavigation.cs
Normal file
317
Charts/Navigation/Navigation/InertialMouseNavigation.cs
Normal file
@@ -0,0 +1,317 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media.Animation;
|
||||
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Adds some inertia to content of 2D viewport to make it more material</summary>
|
||||
/// <remarks>This is experimental code</remarks>
|
||||
public class InertialMouseNavigation : NavigationBase {
|
||||
protected override void OnLoaded(RoutedEventArgs e) {
|
||||
adornerLayer = AdornerLayer.GetAdornerLayer(this);
|
||||
adornerLayer.IsHitTestVisible = false;
|
||||
}
|
||||
|
||||
protected override void OnViewportChanged() {
|
||||
base.OnViewportChanged();
|
||||
|
||||
//Mouse.AddPreviewMouseDownHandler(Parent, (MouseButtonEventHandler)OnMouseDown);
|
||||
//Mouse.AddPreviewMouseMoveHandler(Parent, (MouseEventHandler)OnMouseMove);
|
||||
//Mouse.AddPreviewMouseUpHandler(Parent, (MouseButtonEventHandler)OnMouseUp);
|
||||
|
||||
Mouse.AddMouseDownHandler(Parent, (MouseButtonEventHandler)OnMouseDown);
|
||||
Mouse.AddMouseMoveHandler(Parent, (MouseEventHandler)OnMouseMove);
|
||||
Mouse.AddMouseUpHandler(Parent, (MouseButtonEventHandler)OnMouseUp);
|
||||
|
||||
Mouse.AddMouseWheelHandler(Parent, (MouseWheelEventHandler)OnMouseWheel);
|
||||
}
|
||||
|
||||
protected Rect VisibleRect {
|
||||
get { return (Rect)GetValue(VisibleRectProperty); }
|
||||
set { SetValue(VisibleRectProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty VisibleRectProperty =
|
||||
DependencyProperty.Register(
|
||||
"VisibleRect",
|
||||
typeof(Rect),
|
||||
typeof(InertialMouseNavigation),
|
||||
new FrameworkPropertyMetadata(new Rect(),
|
||||
OnVisibleRectChanged));
|
||||
|
||||
private static void OnVisibleRectChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
|
||||
InertialMouseNavigation nav = (InertialMouseNavigation)d;
|
||||
Rect newVisible = (Rect)e.NewValue;
|
||||
|
||||
if (nav.shouldApplyAnimation) {
|
||||
nav.Viewport.Visible = newVisible;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnVisibleChanged(Rect newRect, Rect oldRect) {
|
||||
// todo was uncommented
|
||||
// VisibleRect = newRect;
|
||||
}
|
||||
|
||||
private void OnMouseWheel(object sender, MouseWheelEventArgs e) {
|
||||
if (!e.Handled) {
|
||||
Point mousePos = e.GetPosition(this);
|
||||
int delta = -e.Delta;
|
||||
MouseWheelZoom(mousePos, delta);
|
||||
}
|
||||
}
|
||||
|
||||
bool adornerAdded;
|
||||
RectangleSelectionAdorner selectionAdorner;
|
||||
AdornerLayer adornerLayer;
|
||||
private void AddSelectionAdorner() {
|
||||
if (!adornerAdded) {
|
||||
selectionAdorner = new RectangleSelectionAdorner(this);
|
||||
selectionAdorner.Border = zoomRect;
|
||||
adornerLayer.Add(selectionAdorner);
|
||||
}
|
||||
adornerAdded = true;
|
||||
}
|
||||
|
||||
private void RemoveSelectionAdorner() {
|
||||
adornerLayer.Remove(selectionAdorner);
|
||||
Debug.Assert(adornerAdded);
|
||||
adornerAdded = false;
|
||||
}
|
||||
|
||||
private void UpdateSelectionAdorner() {
|
||||
selectionAdorner.Border = zoomRect;
|
||||
selectionAdorner.InvalidateVisual();
|
||||
}
|
||||
|
||||
Rect? zoomRect = null;
|
||||
double wheelZoomSpeed = 1.2;
|
||||
private bool shouldKeepRatioWhileZooming;
|
||||
private bool isZooming = false;
|
||||
private bool isDragging = false;
|
||||
private Point dragStartPointInVisible;
|
||||
private Point zoomStartPoint;
|
||||
private Point dragStartPointInOutput;
|
||||
|
||||
private static bool IsShiftOrCtrl {
|
||||
get {
|
||||
ModifierKeys currKeys = Keyboard.Modifiers;
|
||||
return (currKeys | ModifierKeys.Shift) == currKeys ||
|
||||
(currKeys | ModifierKeys.Control) == currKeys;
|
||||
}
|
||||
}
|
||||
|
||||
private int dragStartTimestamp;
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
// dragging
|
||||
bool shouldStartDrag = e.ChangedButton == MouseButton.Left && Keyboard.Modifiers == ModifierKeys.None;
|
||||
|
||||
if (shouldStartDrag) {
|
||||
dragStartPointInOutput = e.GetPosition(this);
|
||||
dragStartPointInVisible = dragStartPointInOutput.Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
dragStartTimestamp = e.Timestamp;
|
||||
|
||||
isDragging = true;
|
||||
CaptureMouse();
|
||||
}
|
||||
|
||||
bool shouldStartZoom = e.ChangedButton == MouseButton.Left && IsShiftOrCtrl;
|
||||
if (shouldStartZoom) {
|
||||
// zooming
|
||||
|
||||
zoomStartPoint = e.GetPosition(this);
|
||||
if (Viewport.Output.Contains(zoomStartPoint)) {
|
||||
isZooming = true;
|
||||
AddSelectionAdorner();
|
||||
CaptureMouse();
|
||||
shouldKeepRatioWhileZooming = Keyboard.Modifiers == ModifierKeys.Shift;
|
||||
}
|
||||
}
|
||||
|
||||
(Parent as IInputElement).Focus();
|
||||
}
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e) {
|
||||
if (!isDragging && !isZooming) return;
|
||||
// dragging
|
||||
if (isDragging && e.LeftButton == MouseButtonState.Pressed) {
|
||||
|
||||
|
||||
Point endPoint = e.GetPosition(this).Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
Point loc = Viewport.Visible.Location;
|
||||
Vector shift = dragStartPointInVisible - endPoint;
|
||||
loc += shift;
|
||||
|
||||
//Debug.WriteLine("Shift = " + shift);
|
||||
|
||||
if (shift.X != 0 || shift.Y != 0) {
|
||||
StopDragAnimation();
|
||||
Rect visible = Viewport.Visible;
|
||||
|
||||
visible.Location = loc;
|
||||
Viewport.Visible = visible;
|
||||
if (Viewport.Visible != visible) { }
|
||||
}
|
||||
else {
|
||||
|
||||
}
|
||||
}
|
||||
// zooming
|
||||
else if (isZooming && e.LeftButton == MouseButtonState.Pressed) {
|
||||
Point zoomEndPoint = e.GetPosition(this);
|
||||
UpdateZoomRect(zoomEndPoint);
|
||||
}
|
||||
}
|
||||
|
||||
private bool shouldApplyAnimation = true;
|
||||
private void StopDragAnimation() {
|
||||
shouldApplyAnimation = false;
|
||||
if (dragAnimation != null && HasAnimatedProperties) {
|
||||
Debug.WriteLine("Stopping animation");
|
||||
//BeginAnimation(VisibleRectProperty, null, HandoffBehavior.SnapshotAndReplace);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsShiftPressed() {
|
||||
return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
}
|
||||
|
||||
private void UpdateZoomRect(Point zoomEndPoint) {
|
||||
Rect output = Viewport.Output;
|
||||
Rect tmpZoomRect = new Rect(zoomStartPoint, zoomEndPoint);
|
||||
tmpZoomRect = Rect.Intersect(tmpZoomRect, output);
|
||||
|
||||
shouldKeepRatioWhileZooming = IsShiftPressed();
|
||||
if (shouldKeepRatioWhileZooming) {
|
||||
double currZoomRatio = tmpZoomRect.Width / tmpZoomRect.Height;
|
||||
double zoomRatio = output.Width / output.Height;
|
||||
if (currZoomRatio < zoomRatio) {
|
||||
double oldHeight = tmpZoomRect.Height;
|
||||
double height = tmpZoomRect.Width / zoomRatio;
|
||||
tmpZoomRect.Height = height;
|
||||
if (!tmpZoomRect.Contains(zoomStartPoint)) {
|
||||
tmpZoomRect.Offset(0, oldHeight - height);
|
||||
}
|
||||
}
|
||||
else {
|
||||
double oldWidth = tmpZoomRect.Width;
|
||||
double width = tmpZoomRect.Height * zoomRatio;
|
||||
tmpZoomRect.Width = width;
|
||||
if (!tmpZoomRect.Contains(zoomStartPoint)) {
|
||||
tmpZoomRect.Offset(oldWidth - width, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoomRect = tmpZoomRect;
|
||||
UpdateSelectionAdorner();
|
||||
}
|
||||
|
||||
RectAnimation dragAnimation;
|
||||
private void OnMouseUp(object sender, MouseButtonEventArgs e) {
|
||||
if (isDragging && e.ChangedButton == MouseButton.Left) {
|
||||
isDragging = false;
|
||||
ReleaseMouseCapture();
|
||||
Point endPointInOutput = e.GetPosition(this);
|
||||
Point endPointInVisible = endPointInOutput.Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
Vector mouseShift = -(endPointInVisible - dragStartPointInVisible);
|
||||
Vector outputShift = endPointInOutput - dragStartPointInOutput;
|
||||
|
||||
int time = e.Timestamp;
|
||||
int deltaTime = time - dragStartTimestamp;
|
||||
|
||||
double seconds = deltaTime / 1000.0;
|
||||
|
||||
// mouse moved short enough and for rather long distance.
|
||||
bool shouldStartAnimation = seconds < 1 && outputShift.Length > 20;
|
||||
if (shouldStartAnimation) {
|
||||
Rect moveTo = Viewport.Visible;
|
||||
moveTo.Offset(mouseShift * 2);
|
||||
|
||||
//if (dragAnimation == null) {
|
||||
dragAnimation = new RectAnimation(moveTo,
|
||||
TimeSpan.FromSeconds(1));
|
||||
dragAnimation.DecelerationRatio = 0.2;
|
||||
//}
|
||||
//else {
|
||||
// dragAnimation.To = moveTo;
|
||||
// dragAnimation.Duration = TimeSpan.FromSeconds(1);
|
||||
// AnimationClock clock = dragAnimation.CreateClock();
|
||||
// clock.Controller.Begin();
|
||||
//}
|
||||
|
||||
BeginAnimation(VisibleRectProperty, dragAnimation);
|
||||
shouldApplyAnimation = true;
|
||||
Debug.WriteLine("Starting animation");
|
||||
}
|
||||
else if (dragAnimation != null) {
|
||||
StopDragAnimation();
|
||||
}
|
||||
|
||||
// focusing on LMB click
|
||||
if (endPointInVisible == dragStartPointInVisible) {
|
||||
//Keyboard.Focus(Parent as IInputElement);
|
||||
}
|
||||
}
|
||||
else if (isZooming && e.ChangedButton == MouseButton.Left) {
|
||||
isZooming = false;
|
||||
if (zoomRect.HasValue) {
|
||||
Rect output = Viewport.Output;
|
||||
|
||||
Point p1 = zoomRect.Value.TopLeft.Transform(output, Viewport.Visible);
|
||||
Point p2 = zoomRect.Value.BottomRight.Transform(output, Viewport.Visible);
|
||||
Rect newVisible = new Rect(p1, p2);
|
||||
Viewport.Visible = newVisible;
|
||||
|
||||
zoomRect = null;
|
||||
ReleaseMouseCapture();
|
||||
RemoveSelectionAdorner();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
class MyRectAnimation : RectAnimationBase {
|
||||
public MyRectAnimation() {
|
||||
Duration = new Duration(TimeSpan.FromSeconds(2));
|
||||
}
|
||||
|
||||
protected override Rect GetCurrentValueCore(Rect defaultOriginValue, Rect defaultDestinationValue, AnimationClock animationClock) {
|
||||
Debug.WriteLine("In animation");
|
||||
return new Rect();
|
||||
}
|
||||
|
||||
protected override Freezable CreateInstanceCore() {
|
||||
return new MyRectAnimation();
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
protected override void OnLostFocus(RoutedEventArgs e) {
|
||||
if (isZooming) {
|
||||
RemoveSelectionAdorner();
|
||||
}
|
||||
ReleaseMouseCapture();
|
||||
base.OnLostFocus(e);
|
||||
}
|
||||
|
||||
private void MouseWheelZoom(Point mousePos, int wheelRotationDelta) {
|
||||
Point zoomTo = mousePos.Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
double zoomSpeed = Math.Abs(wheelRotationDelta / Mouse.MouseWheelDeltaForOneLine);
|
||||
zoomSpeed *= wheelZoomSpeed;
|
||||
if (wheelRotationDelta < 0) {
|
||||
zoomSpeed = 1 / zoomSpeed;
|
||||
}
|
||||
Viewport.Visible = Viewport.Visible.Zoom(zoomTo, zoomSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
300
Charts/Navigation/Navigation/KeyboardNavigation.cs
Normal file
300
Charts/Navigation/Navigation/KeyboardNavigation.cs
Normal file
@@ -0,0 +1,300 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Win32;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Provides keyboard navigation around viewport</summary>
|
||||
public class KeyboardNavigation : NavigationBase
|
||||
{
|
||||
|
||||
public KeyboardNavigation()
|
||||
{
|
||||
Focusable = true;
|
||||
Loaded += KeyboardNavigation_Loaded;
|
||||
}
|
||||
|
||||
private void KeyboardNavigation_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
Keyboard.Focus(this);
|
||||
InitCommands();
|
||||
}
|
||||
|
||||
public void InitCommands()
|
||||
{
|
||||
var zoomWithParamCommandBinding = new CommandBinding(
|
||||
ChartCommands.ZoomWithParameter,
|
||||
ZoomWithParamExecute,
|
||||
ZoomWithParamCanExecute);
|
||||
Plotter2D.CommandBindings.Add(zoomWithParamCommandBinding);
|
||||
|
||||
var zoomInCommandBinding = new CommandBinding(
|
||||
ChartCommands.ZoomIn,
|
||||
ZoomInExecute,
|
||||
ZoomInCanExecute);
|
||||
Plotter2D.CommandBindings.Add(zoomInCommandBinding);
|
||||
|
||||
var zoomOutCommandBinding = new CommandBinding(
|
||||
ChartCommands.ZoomOut,
|
||||
ZoomOutExecute,
|
||||
ZoomOutCanExecute);
|
||||
Plotter2D.CommandBindings.Add(zoomOutCommandBinding);
|
||||
|
||||
var fitToViewCommandBinding = new CommandBinding(
|
||||
ChartCommands.FitToView,
|
||||
FitToViewExecute,
|
||||
FitToViewCanExecute);
|
||||
Plotter2D.CommandBindings.Add(fitToViewCommandBinding);
|
||||
|
||||
var ScrollLeftCommandBinding = new CommandBinding(
|
||||
ChartCommands.ScrollLeft,
|
||||
ScrollLeftExecute,
|
||||
ScrollLeftCanExecute);
|
||||
Plotter2D.CommandBindings.Add(ScrollLeftCommandBinding);
|
||||
|
||||
var ScrollRightCommandBinding = new CommandBinding(
|
||||
ChartCommands.ScrollRight,
|
||||
ScrollRightExecute,
|
||||
ScrollRightCanExecute);
|
||||
Plotter2D.CommandBindings.Add(ScrollRightCommandBinding);
|
||||
|
||||
var ScrollUpCommandBinding = new CommandBinding(
|
||||
ChartCommands.ScrollUp,
|
||||
ScrollUpExecute,
|
||||
ScrollUpCanExecute);
|
||||
Plotter2D.CommandBindings.Add(ScrollUpCommandBinding);
|
||||
|
||||
var ScrollDownCommandBinding = new CommandBinding(
|
||||
ChartCommands.ScrollDown,
|
||||
ScrollDownExecute,
|
||||
ScrollDownCanExecute);
|
||||
Plotter2D.CommandBindings.Add(ScrollDownCommandBinding);
|
||||
|
||||
var SaveScreenshotCommandBinding = new CommandBinding(
|
||||
ChartCommands.SaveScreenshot,
|
||||
SaveScreenshotExecute,
|
||||
SaveScreenshotCanExecute);
|
||||
Plotter2D.CommandBindings.Add(SaveScreenshotCommandBinding);
|
||||
|
||||
var CopyScreenshotCommandBinding = new CommandBinding(
|
||||
ChartCommands.CopyScreenshot,
|
||||
CopyScreenshotExecute,
|
||||
CopyScreenshotCanExecute);
|
||||
Plotter2D.CommandBindings.Add(CopyScreenshotCommandBinding);
|
||||
|
||||
var ShowHelpCommandBinding = new CommandBinding(
|
||||
ChartCommands.ShowHelp,
|
||||
ShowHelpExecute,
|
||||
ShowHelpCanExecute);
|
||||
Plotter2D.CommandBindings.Add(ShowHelpCommandBinding);
|
||||
}
|
||||
|
||||
#region Zoom With param
|
||||
|
||||
private void ZoomWithParamExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
// TODO: add working code
|
||||
//double zoomParam = (double)e.Parameter;
|
||||
//Plotter2d.ZoomVisible(zoomParam);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ZoomWithParamCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Zoom in
|
||||
|
||||
private void ZoomInExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
Viewport.Zoom(0.9);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ZoomInCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Zoom out
|
||||
|
||||
private void ZoomOutExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
Viewport.Zoom(1.1);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ZoomOutCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fit to view
|
||||
|
||||
private void FitToViewExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
Viewport.FitToView();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void FitToViewCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Scroll
|
||||
|
||||
private double scrollCoeff = 0.05;
|
||||
private void ScrollVisibleProportionally(double xShiftCoeff, double yShiftCoeff)
|
||||
{
|
||||
Rect visible = Viewport.Visible;
|
||||
double width = visible.Width;
|
||||
double height = visible.Height;
|
||||
|
||||
visible.Offset(xShiftCoeff * width, yShiftCoeff * height);
|
||||
|
||||
Viewport.Visible = visible;
|
||||
}
|
||||
|
||||
#region ScrollLeft
|
||||
|
||||
private void ScrollLeftExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ScrollVisibleProportionally(scrollCoeff, 0);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollLeftCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScrollRight
|
||||
|
||||
private void ScrollRightExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ScrollVisibleProportionally(-scrollCoeff, 0);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollRightCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScrollUp
|
||||
|
||||
private void ScrollUpExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ScrollVisibleProportionally(0, -scrollCoeff);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollUpCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ScrollDown
|
||||
|
||||
private void ScrollDownExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
ScrollVisibleProportionally(0, scrollCoeff);
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ScrollDownCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
#region SaveScreenshot
|
||||
|
||||
private void SaveScreenshotExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
SaveFileDialog dlg = new SaveFileDialog();
|
||||
dlg.Filter = "JPEG(*.jpg)|*.jpg|BMP(*.bmp)|*.bmp|GIF(*.gif)|.gif|All files (*.*)|*.*";
|
||||
dlg.FilterIndex = 1;
|
||||
if (dlg.ShowDialog().GetValueOrDefault(false))
|
||||
{
|
||||
string filePath = dlg.FileName;
|
||||
Plotter2D.SaveScreenshot(filePath);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveScreenshotCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region CopyScreenshot
|
||||
|
||||
private void CopyScreenshotExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
Plotter2D.CopyScreenshotToClipboard();
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void CopyScreenshotCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ShowHelp
|
||||
|
||||
private void ShowHelpExecute(object target, ExecutedRoutedEventArgs e)
|
||||
{
|
||||
string helpText =
|
||||
@"Dynamic Data Display is in ""Auto Fit"" mode by default.
|
||||
By using the mouse you can manually select an area to display:
|
||||
|
||||
Mouse wheel, '+' and '-' — Zoom in/Zoom out
|
||||
Mouse drag or arrow keys — Pan the display area
|
||||
Ctrl + Mouse drag — Select an area to zoom in
|
||||
Shift + Mouse drag — Select an area with fixed aspect ratio
|
||||
Home — Fit to view
|
||||
F11 — Copy chart to clipboard";
|
||||
|
||||
MessageBox.Show(helpText, "Quick Help", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
private void ShowHelpCanExecute(object target, CanExecuteRoutedEventArgs e)
|
||||
{
|
||||
e.CanExecute = true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
protected override void OnRenderCore(DrawingContext dc, RenderState state)
|
||||
{
|
||||
// do nothing here
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Charts/Navigation/Navigation/MessagesHelper.cs
Normal file
32
Charts/Navigation/Navigation/MessagesHelper.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Helper class to parse Windows messages</summary>
|
||||
internal static class MessagesHelper {
|
||||
internal static int GetXFromLParam(IntPtr lParam) {
|
||||
return LOWORD(lParam.ToInt32());
|
||||
}
|
||||
|
||||
internal static int GetYFromLParam(IntPtr lParam) {
|
||||
return HIWORD(lParam.ToInt32());
|
||||
}
|
||||
|
||||
internal static Point GetMousePosFromLParam(IntPtr lParam) {
|
||||
return new Point(GetXFromLParam(lParam), GetYFromLParam(lParam));
|
||||
}
|
||||
|
||||
internal static int GetWheelDataFromWParam(IntPtr wParam) {
|
||||
return HIWORD(wParam.ToInt32());
|
||||
}
|
||||
|
||||
internal static short HIWORD(int i) {
|
||||
return (short)((i & 0xFFFF0000) >> 16);
|
||||
}
|
||||
|
||||
internal static short LOWORD(int i) {
|
||||
return (short)(i & 0x0000FFFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
235
Charts/Navigation/Navigation/MouseNavigation.cs
Normal file
235
Charts/Navigation/Navigation/MouseNavigation.cs
Normal file
@@ -0,0 +1,235 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Provides common methods of mouse navigation around viewport</summary>
|
||||
public class MouseNavigation : NavigationBase {
|
||||
private AdornerLayer adornerLayer;
|
||||
protected AdornerLayer AdornerLayer {
|
||||
get {
|
||||
if (adornerLayer == null) {
|
||||
adornerLayer = AdornerLayer.GetAdornerLayer(this);
|
||||
if (adornerLayer != null) {
|
||||
adornerLayer.IsHitTestVisible = false;
|
||||
}
|
||||
}
|
||||
|
||||
return adornerLayer;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnViewportChanged() {
|
||||
base.OnViewportChanged();
|
||||
|
||||
//Mouse.AddPreviewMouseDownHandler(Parent, (MouseButtonEventHandler)OnMouseDown);
|
||||
//Mouse.AddPreviewMouseMoveHandler(Parent, (MouseEventHandler)OnMouseMove);
|
||||
//Mouse.AddPreviewMouseUpHandler(Parent, (MouseButtonEventHandler)OnMouseUp);
|
||||
|
||||
Mouse.AddMouseDownHandler(Parent, OnMouseDown);
|
||||
Mouse.AddMouseMoveHandler(Parent, OnMouseMove);
|
||||
Mouse.AddMouseUpHandler(Parent, OnMouseUp);
|
||||
|
||||
Mouse.AddMouseWheelHandler(Parent, OnMouseWheel);
|
||||
}
|
||||
|
||||
private void OnMouseWheel(object sender, MouseWheelEventArgs e) {
|
||||
if (!e.Handled) {
|
||||
Point mousePos = e.GetPosition(this);
|
||||
int delta = -e.Delta;
|
||||
MouseWheelZoom(mousePos, delta);
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
public override string ToString()
|
||||
{
|
||||
if (!String.IsNullOrEmpty(Name)) {
|
||||
return Name;
|
||||
}
|
||||
return base.ToString();
|
||||
}
|
||||
#endif
|
||||
|
||||
bool adornerAdded;
|
||||
RectangleSelectionAdorner selectionAdorner;
|
||||
private void AddSelectionAdorner() {
|
||||
if (!adornerAdded) {
|
||||
AdornerLayer layer = AdornerLayer;
|
||||
if (layer != null) {
|
||||
selectionAdorner = new RectangleSelectionAdorner(this) { Border = zoomRect };
|
||||
|
||||
layer.Add(selectionAdorner);
|
||||
adornerAdded = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveSelectionAdorner() {
|
||||
AdornerLayer layer = AdornerLayer;
|
||||
if (layer != null) {
|
||||
layer.Remove(selectionAdorner);
|
||||
adornerAdded = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateSelectionAdorner() {
|
||||
selectionAdorner.Border = zoomRect;
|
||||
selectionAdorner.InvalidateVisual();
|
||||
}
|
||||
|
||||
Rect? zoomRect = null;
|
||||
private const double wheelZoomSpeed = 1.2;
|
||||
private bool shouldKeepRatioWhileZooming;
|
||||
private bool isZooming;
|
||||
private bool isDragging;
|
||||
private Point dragStartPointInVisible;
|
||||
private Point zoomStartPoint;
|
||||
|
||||
private static bool IsShiftOrCtrl {
|
||||
get {
|
||||
ModifierKeys currKeys = Keyboard.Modifiers;
|
||||
return (currKeys | ModifierKeys.Shift) == currKeys ||
|
||||
(currKeys | ModifierKeys.Control) == currKeys;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
// dragging
|
||||
bool shouldStartDrag = e.ChangedButton == MouseButton.Left && Keyboard.Modifiers == ModifierKeys.None;
|
||||
|
||||
if (shouldStartDrag) {
|
||||
Point dragStartPointInOutput = e.GetPosition(this);
|
||||
dragStartPointInVisible = dragStartPointInOutput.Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
isDragging = true;
|
||||
CaptureMouse();
|
||||
}
|
||||
|
||||
bool shouldStartZoom = e.ChangedButton == MouseButton.Left && IsShiftOrCtrl;
|
||||
if (shouldStartZoom) {
|
||||
// zooming
|
||||
|
||||
zoomStartPoint = e.GetPosition(this);
|
||||
if (Viewport.Output.Contains(zoomStartPoint)) {
|
||||
isZooming = true;
|
||||
AddSelectionAdorner();
|
||||
CaptureMouse();
|
||||
shouldKeepRatioWhileZooming = Keyboard.Modifiers == ModifierKeys.Shift;
|
||||
}
|
||||
}
|
||||
|
||||
((IInputElement) Parent).Focus();
|
||||
}
|
||||
|
||||
private void OnMouseMove(object sender, MouseEventArgs e) {
|
||||
if (!isDragging && !isZooming) return;
|
||||
|
||||
// dragging
|
||||
if (isDragging && e.LeftButton == MouseButtonState.Pressed) {
|
||||
Point endPoint = e.GetPosition(this).Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
Point loc = Viewport.Visible.Location;
|
||||
Vector shift = dragStartPointInVisible - endPoint;
|
||||
loc += shift;
|
||||
|
||||
if (shift.X != 0 || shift.Y != 0) {
|
||||
Rect visible = Viewport.Visible;
|
||||
|
||||
visible.Location = loc;
|
||||
Viewport.Visible = visible;
|
||||
}
|
||||
}
|
||||
// zooming
|
||||
else if (isZooming && e.LeftButton == MouseButtonState.Pressed) {
|
||||
Point zoomEndPoint = e.GetPosition(this);
|
||||
UpdateZoomRect(zoomEndPoint);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsShiftPressed() {
|
||||
return Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
|
||||
}
|
||||
|
||||
private void UpdateZoomRect(Point zoomEndPoint) {
|
||||
Rect output = Viewport.Output;
|
||||
Rect tmpZoomRect = new Rect(zoomStartPoint, zoomEndPoint);
|
||||
tmpZoomRect = Rect.Intersect(tmpZoomRect, output);
|
||||
|
||||
shouldKeepRatioWhileZooming = IsShiftPressed();
|
||||
if (shouldKeepRatioWhileZooming) {
|
||||
double currZoomRatio = tmpZoomRect.Width / tmpZoomRect.Height;
|
||||
double zoomRatio = output.Width / output.Height;
|
||||
if (currZoomRatio < zoomRatio) {
|
||||
double oldHeight = tmpZoomRect.Height;
|
||||
double height = tmpZoomRect.Width / zoomRatio;
|
||||
tmpZoomRect.Height = height;
|
||||
if (!tmpZoomRect.Contains(zoomStartPoint)) {
|
||||
tmpZoomRect.Offset(0, oldHeight - height);
|
||||
}
|
||||
}
|
||||
else {
|
||||
double oldWidth = tmpZoomRect.Width;
|
||||
double width = tmpZoomRect.Height * zoomRatio;
|
||||
tmpZoomRect.Width = width;
|
||||
if (!tmpZoomRect.Contains(zoomStartPoint)) {
|
||||
tmpZoomRect.Offset(oldWidth - width, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
zoomRect = tmpZoomRect;
|
||||
UpdateSelectionAdorner();
|
||||
}
|
||||
|
||||
private void OnMouseUp(object sender, MouseButtonEventArgs e) {
|
||||
if (isDragging && e.ChangedButton == MouseButton.Left) {
|
||||
isDragging = false;
|
||||
ReleaseMouseCapture();
|
||||
Point endPoint = e.GetPosition(this).Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
// focusing on LMB click
|
||||
if (endPoint == dragStartPointInVisible) {
|
||||
//Keyboard.Focus(Parent as IInputElement);
|
||||
}
|
||||
}
|
||||
else if (isZooming && e.ChangedButton == MouseButton.Left) {
|
||||
isZooming = false;
|
||||
if (zoomRect.HasValue) {
|
||||
Rect output = Viewport.Output;
|
||||
|
||||
Point p1 = zoomRect.Value.TopLeft.Transform(output, Viewport.Visible);
|
||||
Point p2 = zoomRect.Value.BottomRight.Transform(output, Viewport.Visible);
|
||||
Rect newVisible = new Rect(p1, p2);
|
||||
Viewport.Visible = newVisible;
|
||||
|
||||
zoomRect = null;
|
||||
ReleaseMouseCapture();
|
||||
RemoveSelectionAdorner();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnLostFocus(RoutedEventArgs e) {
|
||||
if (isZooming) {
|
||||
RemoveSelectionAdorner();
|
||||
}
|
||||
ReleaseMouseCapture();
|
||||
base.OnLostFocus(e);
|
||||
}
|
||||
|
||||
private void MouseWheelZoom(Point mousePos, int wheelRotationDelta) {
|
||||
Point zoomTo = mousePos.Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
double zoomSpeed = Math.Abs(wheelRotationDelta / Mouse.MouseWheelDeltaForOneLine);
|
||||
zoomSpeed *= wheelZoomSpeed;
|
||||
if (wheelRotationDelta < 0) {
|
||||
zoomSpeed = 1 / zoomSpeed;
|
||||
}
|
||||
Viewport.Visible = Viewport.Visible.Zoom(zoomTo, zoomSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Charts/Navigation/Navigation/NavigationBase.cs
Normal file
25
Charts/Navigation/Navigation/NavigationBase.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Base class for all navigation providers</summary>
|
||||
public abstract class NavigationBase : ViewportElement2D {
|
||||
protected NavigationBase() {
|
||||
ManualTranslate = true;
|
||||
ManualClip = true;
|
||||
Loaded += NavigationBase_Loaded;
|
||||
}
|
||||
|
||||
private void NavigationBase_Loaded(object sender, RoutedEventArgs e) {
|
||||
OnLoaded(e);
|
||||
}
|
||||
|
||||
protected virtual void OnLoaded(RoutedEventArgs e) { }
|
||||
|
||||
protected override void OnRenderCore(DrawingContext dc, RenderState state) {
|
||||
Color color = Color.FromArgb(0, 0, 0, 0);
|
||||
dc.DrawRectangle(new SolidColorBrush(color), null, state.Output);
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Charts/Navigation/Navigation/RectangleSelectionAdorner.cs
Normal file
48
Charts/Navigation/Navigation/RectangleSelectionAdorner.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Helper class to draw semitransparent rectangle over the
|
||||
/// selection area</summary>
|
||||
public sealed class RectangleSelectionAdorner : Adorner {
|
||||
|
||||
private Rect? border = null;
|
||||
public Rect? Border {
|
||||
get { return border; }
|
||||
set { border = value; }
|
||||
}
|
||||
|
||||
public Brush Fill {
|
||||
get { return (Brush)GetValue(FillProperty); }
|
||||
set { SetValue(FillProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FillProperty =
|
||||
DependencyProperty.Register(
|
||||
"Fill",
|
||||
typeof(Brush),
|
||||
typeof(RectangleSelectionAdorner),
|
||||
new FrameworkPropertyMetadata(
|
||||
new SolidColorBrush(Color.FromArgb(60, 100, 100, 100)),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
private Pen pen;
|
||||
public Pen Pen {
|
||||
get { return pen; }
|
||||
set { pen = value; }
|
||||
}
|
||||
|
||||
public RectangleSelectionAdorner(UIElement element)
|
||||
: base(element) {
|
||||
pen = new Pen(Brushes.Black, 1.0);
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
if (border.HasValue) {
|
||||
dc.DrawRectangle(Fill, pen, border.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Charts/Navigation/Navigation/TouchPadScroll.cs
Normal file
52
Charts/Navigation/Navigation/TouchPadScroll.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>This class allows convenient navigation around viewport using touchpad on
|
||||
/// some notebooks</summary>
|
||||
public sealed class TouchpadScroll : NavigationBase {
|
||||
public TouchpadScroll() {
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e) {
|
||||
WindowInteropHelper helper = new WindowInteropHelper(Window.GetWindow(this));
|
||||
HwndSource source = HwndSource.FromHwnd(helper.Handle);
|
||||
source.AddHook(OnMessageAppeared);
|
||||
}
|
||||
|
||||
private IntPtr OnMessageAppeared(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
|
||||
if (msg == WindowsMessages.WM_MOUSEWHEEL) {
|
||||
Point mousePos = MessagesHelper.GetMousePosFromLParam(lParam);
|
||||
mousePos = TranslatePoint(mousePos, this);
|
||||
|
||||
if (Viewport.Output.Contains(mousePos)) {
|
||||
MouseWheelZoom(MessagesHelper.GetMousePosFromLParam(lParam), MessagesHelper.GetWheelDataFromWParam(wParam));
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
double wheelZoomSpeed = 1.2;
|
||||
public double WheelZoomSpeed {
|
||||
get { return wheelZoomSpeed; }
|
||||
set { wheelZoomSpeed = value; }
|
||||
}
|
||||
|
||||
private void MouseWheelZoom(Point mousePos, int wheelRotationDelta) {
|
||||
Point zoomTo = mousePos.Transform(Viewport.Output, Viewport.Visible);
|
||||
|
||||
double zoomSpeed = Math.Abs(wheelRotationDelta / Mouse.MouseWheelDeltaForOneLine);
|
||||
zoomSpeed *= wheelZoomSpeed;
|
||||
if (wheelRotationDelta < 0) {
|
||||
zoomSpeed = 1 / zoomSpeed;
|
||||
}
|
||||
Viewport.Visible = Viewport.Visible.Zoom(zoomTo, zoomSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
7
Charts/Navigation/Navigation/WindowsMessages.cs
Normal file
7
Charts/Navigation/Navigation/WindowsMessages.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
internal static class WindowsMessages {
|
||||
internal static int WM_MOUSEWHEEL = 0x20A;
|
||||
}
|
||||
}
|
||||
34
Charts/Navigation/NavigationBase.cs
Normal file
34
Charts/Navigation/NavigationBase.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Base class for all navigation providers</summary>
|
||||
public abstract class NavigationBase : ViewportElement2D
|
||||
{
|
||||
protected NavigationBase()
|
||||
{
|
||||
ManualTranslate = true;
|
||||
ManualClip = true;
|
||||
Loaded += NavigationBase_Loaded;
|
||||
}
|
||||
|
||||
private void NavigationBase_Loaded(object sender, RoutedEventArgs e)
|
||||
{
|
||||
OnLoaded(e);
|
||||
}
|
||||
|
||||
protected virtual void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
// this call enables contextMenu to be shown after loading and
|
||||
// before any changes to Viewport - without this call
|
||||
// context menu was not shown.
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnRenderCore(DrawingContext dc, RenderState state)
|
||||
{
|
||||
dc.DrawRectangle(Brushes.Transparent, null, state.Output);
|
||||
}
|
||||
}
|
||||
}
|
||||
205
Charts/Navigation/OldAxisNavigation.cs
Normal file
205
Charts/Navigation/OldAxisNavigation.cs
Normal file
@@ -0,0 +1,205 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Input;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a navigation methods upon one axis - mouse panning and zooming.
|
||||
/// </summary>
|
||||
[Obsolete("Will be removed soon. Use AxisNavigation instead.")]
|
||||
public class OldAxisNavigation : ContentGraph
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AxisNavigation"/> class.
|
||||
/// </summary>
|
||||
public OldAxisNavigation()
|
||||
{
|
||||
Focusable = false;
|
||||
|
||||
SetHorizontalOrientation();
|
||||
Content = content;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AxisNavigation"/> class.
|
||||
/// </summary>
|
||||
/// <param name="orientation">The orientation.</param>
|
||||
public OldAxisNavigation(Orientation orientation)
|
||||
{
|
||||
Orientation = orientation;
|
||||
Content = content;
|
||||
}
|
||||
|
||||
private void SetHorizontalOrientation()
|
||||
{
|
||||
Grid.SetColumn(this, 1);
|
||||
Grid.SetRow(this, 2);
|
||||
}
|
||||
|
||||
private void SetVerticalOrientation()
|
||||
{
|
||||
// todo should automatically search for location of axes as they can be
|
||||
// not only from the left or bottom.
|
||||
Grid.SetColumn(this, 0);
|
||||
Grid.SetRow(this, 1);
|
||||
}
|
||||
|
||||
private Orientation orientation = Orientation.Horizontal;
|
||||
/// <summary>
|
||||
/// Gets or sets the orientation of AxisNavigation.
|
||||
/// </summary>
|
||||
/// <value>The orientation.</value>
|
||||
public Orientation Orientation
|
||||
{
|
||||
get { return orientation; }
|
||||
set
|
||||
{
|
||||
if (orientation != value)
|
||||
{
|
||||
orientation = value;
|
||||
OnOrientationChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnOrientationChanged()
|
||||
{
|
||||
switch (orientation)
|
||||
{
|
||||
case Orientation.Horizontal:
|
||||
SetHorizontalOrientation();
|
||||
break;
|
||||
case Orientation.Vertical:
|
||||
SetVerticalOrientation();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private bool lmbPressed = false;
|
||||
private Point dragStart;
|
||||
|
||||
private CoordinateTransform Transform
|
||||
{
|
||||
get { return Plotter2D.Viewport.Transform; }
|
||||
}
|
||||
|
||||
protected override Panel HostPanel
|
||||
{
|
||||
get { return Plotter2D.MainGrid; }
|
||||
}
|
||||
|
||||
private readonly Panel content = new Canvas { Background = Brushes.Transparent };
|
||||
private readonly SolidColorBrush fillBrush = new SolidColorBrush(Color.FromRgb(255, 228, 209)).MakeTransparent(0.2);
|
||||
|
||||
private Point lmbInitialPosition;
|
||||
protected Point LmbInitialPosition
|
||||
{
|
||||
get { return lmbInitialPosition; }
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonDown(e);
|
||||
|
||||
lmbInitialPosition = e.GetPosition(this);
|
||||
|
||||
dragStart = lmbInitialPosition.ScreenToViewport(Transform);
|
||||
lmbPressed = true;
|
||||
|
||||
content.Background = fillBrush;
|
||||
|
||||
CaptureMouse();
|
||||
}
|
||||
|
||||
protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
|
||||
{
|
||||
base.OnMouseLeftButtonUp(e);
|
||||
lmbPressed = false;
|
||||
|
||||
ClearValue(CursorProperty);
|
||||
content.Background = Brushes.Transparent;
|
||||
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
|
||||
protected override void OnMouseMove(MouseEventArgs e)
|
||||
{
|
||||
if (lmbPressed)
|
||||
{
|
||||
Point mousePos = e.GetPosition(this).ScreenToViewport(Transform);
|
||||
|
||||
DataRect visible = Plotter2D.Viewport.Visible;
|
||||
double delta;
|
||||
if (orientation == Orientation.Horizontal)
|
||||
{
|
||||
delta = (mousePos - dragStart).X;
|
||||
visible.XMin -= delta;
|
||||
}
|
||||
else
|
||||
{
|
||||
delta = (mousePos - dragStart).Y;
|
||||
visible.YMin -= delta;
|
||||
}
|
||||
|
||||
if (e.GetPosition(this) != lmbInitialPosition)
|
||||
{
|
||||
Cursor = orientation == Orientation.Horizontal ? Cursors.ScrollWE : Cursors.ScrollNS;
|
||||
}
|
||||
|
||||
Plotter2D.Viewport.Visible = visible;
|
||||
}
|
||||
}
|
||||
|
||||
private const double wheelZoomSpeed = 1.2;
|
||||
protected override void OnMouseWheel(MouseWheelEventArgs e)
|
||||
{
|
||||
Point mousePos = e.GetPosition(this);
|
||||
int delta = -e.Delta;
|
||||
|
||||
Point zoomTo = mousePos.ScreenToViewport(Transform);
|
||||
|
||||
double zoomSpeed = Math.Abs(delta / Mouse.MouseWheelDeltaForOneLine);
|
||||
zoomSpeed *= wheelZoomSpeed;
|
||||
if (delta < 0)
|
||||
{
|
||||
zoomSpeed = 1 / zoomSpeed;
|
||||
}
|
||||
|
||||
DataRect visible = Plotter2D.Viewport.Visible.Zoom(zoomTo, zoomSpeed);
|
||||
DataRect oldVisible = Plotter2D.Viewport.Visible;
|
||||
if (orientation == Orientation.Horizontal)
|
||||
{
|
||||
visible.YMin = oldVisible.YMin;
|
||||
visible.Height = oldVisible.Height;
|
||||
}
|
||||
else
|
||||
{
|
||||
visible.XMin = oldVisible.XMin;
|
||||
visible.Width = oldVisible.Width;
|
||||
}
|
||||
Plotter2D.Viewport.Visible = visible;
|
||||
|
||||
e.Handled = true;
|
||||
}
|
||||
|
||||
protected override void OnLostFocus(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLostFocus(e);
|
||||
|
||||
ClearValue(CursorProperty);
|
||||
content.Background = Brushes.Transparent;
|
||||
|
||||
ReleaseMouseCapture();
|
||||
}
|
||||
}
|
||||
}
|
||||
120
Charts/Navigation/PhysicalNavigation.cs
Normal file
120
Charts/Navigation/PhysicalNavigation.cs
Normal file
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Navigation;
|
||||
using System.Windows.Input;
|
||||
using System.Diagnostics;
|
||||
using System.Windows;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
public class PhysicalNavigation : IPlotterElement
|
||||
{
|
||||
private readonly DispatcherTimer timer;
|
||||
|
||||
public PhysicalNavigation()
|
||||
{
|
||||
timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(30) };
|
||||
timer.Tick += timer_Tick;
|
||||
}
|
||||
|
||||
DateTime startTime;
|
||||
private void timer_Tick(object sender, EventArgs e)
|
||||
{
|
||||
TimeSpan time = DateTime.Now - startTime;
|
||||
animation.UseMouse = isMouseDown;
|
||||
if (!isMouseDown)
|
||||
{
|
||||
animation.LiquidFrictionQuadraticCoeff = 1;
|
||||
}
|
||||
|
||||
plotter.Viewport.Visible = animation.GetValue(time);
|
||||
if (animation.IsFinished && !isMouseDown)
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private bool isMouseDown = false;
|
||||
private PhysicalRectAnimation animation;
|
||||
private Point clickPos;
|
||||
private void OnMouseDown(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
clickPos = e.GetPosition(plotter.CentralGrid);
|
||||
|
||||
isMouseDown = true;
|
||||
startTime = DateTime.Now;
|
||||
|
||||
animation = new PhysicalRectAnimation(
|
||||
plotter.Viewport,
|
||||
e.GetPosition(plotter.ViewportPanel));
|
||||
|
||||
timer.Start();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseUp(object sender, MouseButtonEventArgs e)
|
||||
{
|
||||
if (e.ChangedButton == MouseButton.Left)
|
||||
{
|
||||
isMouseDown = false;
|
||||
if (clickPos == e.GetPosition(plotter.CentralGrid))
|
||||
{
|
||||
timer.Stop();
|
||||
animation = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (animation.IsFinished)
|
||||
{
|
||||
timer.Stop();
|
||||
animation = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
animation.UseMouse = false;
|
||||
animation.LiquidFrictionQuadraticCoeff = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
private Plotter2D plotter;
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = (Plotter2D)plotter;
|
||||
|
||||
Mouse.AddPreviewMouseDownHandler(plotter.CentralGrid, OnMouseDown);
|
||||
Mouse.AddPreviewMouseUpHandler(plotter.CentralGrid, OnMouseUp);
|
||||
plotter.CentralGrid.MouseLeave += CentralGrid_MouseLeave;
|
||||
}
|
||||
|
||||
private void CentralGrid_MouseLeave(object sender, MouseEventArgs e)
|
||||
{
|
||||
isMouseDown = false;
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
plotter.CentralGrid.MouseLeave -= CentralGrid_MouseLeave;
|
||||
Mouse.RemovePreviewMouseDownHandler(plotter.CentralGrid, OnMouseDown);
|
||||
Mouse.RemovePreviewMouseUpHandler(plotter.CentralGrid, OnMouseUp);
|
||||
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
173
Charts/Navigation/PhysicalRectAnimation.cs
Normal file
173
Charts/Navigation/PhysicalRectAnimation.cs
Normal file
@@ -0,0 +1,173 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media.Animation;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
internal sealed class PhysicalRectAnimation
|
||||
{
|
||||
Vector position = new Vector();
|
||||
Vector velocity = new Vector();
|
||||
public Vector Velocity
|
||||
{
|
||||
get { return velocity; }
|
||||
set { velocity = value; }
|
||||
}
|
||||
|
||||
Vector acceleration = new Vector();
|
||||
private double mass = 1; // kilogramms
|
||||
public double Mass
|
||||
{
|
||||
get { return mass; }
|
||||
set { mass = value; }
|
||||
}
|
||||
|
||||
private double frictionCalmCoeff = 0.0;
|
||||
public double FrictionCalmCoeff
|
||||
{
|
||||
get { return frictionCalmCoeff; }
|
||||
set { frictionCalmCoeff = value; }
|
||||
}
|
||||
|
||||
double frictionMovementCoeff = 0.1;
|
||||
public double FrictionMovementCoeff
|
||||
{
|
||||
get { return frictionMovementCoeff; }
|
||||
set { frictionMovementCoeff = value; }
|
||||
}
|
||||
|
||||
double springCoeff = 50;
|
||||
public double SpringCoeff
|
||||
{
|
||||
get { return springCoeff; }
|
||||
set { springCoeff = value; }
|
||||
}
|
||||
|
||||
double liquidFrictionCoeff = 1;
|
||||
public double LiquidFrictionCoeff
|
||||
{
|
||||
get { return liquidFrictionCoeff; }
|
||||
set { liquidFrictionCoeff = value; }
|
||||
}
|
||||
|
||||
double liquidFrictionQuadraticCoeff = 10;
|
||||
public double LiquidFrictionQuadraticCoeff
|
||||
{
|
||||
get { return liquidFrictionQuadraticCoeff; }
|
||||
set { liquidFrictionQuadraticCoeff = value; }
|
||||
}
|
||||
|
||||
const double G = 9.81;
|
||||
|
||||
DataRect from;
|
||||
Viewport2D viewport;
|
||||
Point initialMousePos;
|
||||
CoordinateTransform initialTransform;
|
||||
|
||||
public PhysicalRectAnimation(Viewport2D viewport, Point initialMousePos)
|
||||
{
|
||||
this.from = viewport.Visible;
|
||||
this.viewport = viewport;
|
||||
this.initialMousePos = initialMousePos;
|
||||
|
||||
initialTransform = viewport.Transform;
|
||||
|
||||
position = from.Location.ToVector();
|
||||
}
|
||||
|
||||
double prevTime;
|
||||
|
||||
private bool isFinished = false;
|
||||
public bool IsFinished
|
||||
{
|
||||
get { return isFinished; }
|
||||
}
|
||||
|
||||
private bool useMouse = true;
|
||||
public bool UseMouse
|
||||
{
|
||||
get { return useMouse; }
|
||||
set { useMouse = value; }
|
||||
}
|
||||
|
||||
public DataRect GetValue(TimeSpan timeSpan)
|
||||
{
|
||||
double time = timeSpan.TotalSeconds;
|
||||
|
||||
double dtime = time - prevTime;
|
||||
|
||||
acceleration = GetForces() / mass;
|
||||
|
||||
velocity += acceleration * dtime;
|
||||
var shift = velocity * dtime;
|
||||
|
||||
double viewportSize = Math.Sqrt(from.Width * from.Width + from.Height * from.Height);
|
||||
if (!(shift.Length < viewportSize * 0.002 && time > 0.5))
|
||||
{
|
||||
position += shift;
|
||||
}
|
||||
else
|
||||
{
|
||||
isFinished = true;
|
||||
}
|
||||
|
||||
prevTime = time;
|
||||
|
||||
Point pos = new Point(position.X, position.Y);
|
||||
DataRect bounds = new DataRect(pos, from.Size);
|
||||
|
||||
return bounds;
|
||||
}
|
||||
|
||||
private Vector GetForces()
|
||||
{
|
||||
Vector springForce = new Vector();
|
||||
if (useMouse)
|
||||
{
|
||||
Point mousePos = GetMousePosition();
|
||||
if (!mousePos.IsFinite()) { }
|
||||
|
||||
Point p1 = initialMousePos.ScreenToData(initialTransform);
|
||||
Point p2 = mousePos.ScreenToData(viewport.Transform);
|
||||
|
||||
var transform = viewport.Transform;
|
||||
|
||||
Vector diff = p2 - p1;
|
||||
springForce = -diff * springCoeff;
|
||||
}
|
||||
|
||||
Vector frictionForce = GetFrictionForce(springForce);
|
||||
|
||||
Vector liquidFriction = -liquidFrictionCoeff * velocity - liquidFrictionQuadraticCoeff * velocity * velocity.Length;
|
||||
|
||||
Vector result = springForce + frictionForce + liquidFriction;
|
||||
return result;
|
||||
}
|
||||
|
||||
private Vector GetFrictionForce(Vector springForce)
|
||||
{
|
||||
double maxCalmFriction = frictionCalmCoeff * mass * G;
|
||||
if (maxCalmFriction >= springForce.Length)
|
||||
return -springForce;
|
||||
|
||||
if (velocity.Length == 0)
|
||||
return new Vector();
|
||||
|
||||
return -velocity / velocity.Length * frictionMovementCoeff * mass * G;
|
||||
}
|
||||
|
||||
private Point GetMousePosition()
|
||||
{
|
||||
return Mouse.GetPosition(viewport.Plotter.ViewportPanel);
|
||||
}
|
||||
}
|
||||
}
|
||||
96
Charts/Navigation/PlotterScrollBar.cs
Normal file
96
Charts/Navigation/PlotterScrollBar.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls.Primitives;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
public abstract class PlotterScrollBar : IPlotterElement
|
||||
{
|
||||
protected PlotterScrollBar()
|
||||
{
|
||||
scrollBar.Scroll += OnScroll;
|
||||
}
|
||||
|
||||
private void OnScroll(object sender, ScrollEventArgs e)
|
||||
{
|
||||
DataRect visible = plotter.Viewport.Visible;
|
||||
visible = CreateVisibleRect(visible, scrollBar.Value);
|
||||
plotter.Viewport.Visible = visible;
|
||||
}
|
||||
|
||||
private readonly ScrollBar scrollBar = new ScrollBar();
|
||||
protected ScrollBar ScrollBar
|
||||
{
|
||||
get { return scrollBar; }
|
||||
}
|
||||
|
||||
private Plotter2D plotter;
|
||||
protected Plotter2D Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
private void OnViewportPropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == "Visible" || e.PropertyName == "Output")
|
||||
{
|
||||
UpdateScrollBar((Viewport2D)sender);
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
GetHostPanel(plotter).Children.Add(scrollBar);
|
||||
|
||||
Plotter2D plotter2d = (Plotter2D)plotter;
|
||||
this.plotter = plotter2d;
|
||||
var viewport = plotter2d.Viewport;
|
||||
viewport.PropertyChanged += OnViewportPropertyChanged;
|
||||
viewport.DomainChanged += OnViewportDomainChanged;
|
||||
|
||||
UpdateScrollBar(viewport);
|
||||
}
|
||||
|
||||
protected abstract void UpdateScrollBar(Viewport2D viewport);
|
||||
|
||||
protected virtual void SetValue(Range<double> visibleRange, Range<double> domainRange)
|
||||
{
|
||||
scrollBar.Value = visibleRange.Min;
|
||||
}
|
||||
|
||||
private void OnViewportDomainChanged(object sender, EventArgs e)
|
||||
{
|
||||
UpdateScrollBar((Viewport2D)sender);
|
||||
}
|
||||
|
||||
protected abstract DataRect CreateVisibleRect(DataRect rect, double value);
|
||||
protected abstract Panel GetHostPanel(Plotter plotter);
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
this.plotter.Viewport.PropertyChanged -= OnViewportPropertyChanged;
|
||||
this.plotter.Viewport.DomainChanged -= OnViewportDomainChanged;
|
||||
|
||||
GetHostPanel(plotter).Children.Remove(scrollBar);
|
||||
|
||||
UpdateScrollBar(null);
|
||||
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
48
Charts/Navigation/RectangleSelectionAdorner.cs
Normal file
48
Charts/Navigation/RectangleSelectionAdorner.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>Helper class to draw semitransparent rectangle over the
|
||||
/// selection area</summary>
|
||||
public sealed class RectangleSelectionAdorner : Adorner {
|
||||
|
||||
private Rect? border = null;
|
||||
public Rect? Border {
|
||||
get { return border; }
|
||||
set { border = value; }
|
||||
}
|
||||
|
||||
public Brush Fill {
|
||||
get { return (Brush)GetValue(FillProperty); }
|
||||
set { SetValue(FillProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty FillProperty =
|
||||
DependencyProperty.Register(
|
||||
"Fill",
|
||||
typeof(Brush),
|
||||
typeof(RectangleSelectionAdorner),
|
||||
new FrameworkPropertyMetadata(
|
||||
new SolidColorBrush(Color.FromArgb(60, 100, 100, 100)),
|
||||
FrameworkPropertyMetadataOptions.AffectsRender));
|
||||
|
||||
private Pen pen;
|
||||
public Pen Pen {
|
||||
get { return pen; }
|
||||
set { pen = value; }
|
||||
}
|
||||
|
||||
public RectangleSelectionAdorner(UIElement element)
|
||||
: base(element) {
|
||||
pen = new Pen(Brushes.Black, 1.0);
|
||||
}
|
||||
|
||||
protected override void OnRender(DrawingContext dc) {
|
||||
if (border.HasValue) {
|
||||
dc.DrawRectangle(Fill, pen, border.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Charts/Navigation/SimpleNavigationBar.xaml
Normal file
21
Charts/Navigation/SimpleNavigationBar.xaml
Normal file
@@ -0,0 +1,21 @@
|
||||
<UserControl x:Class="Microsoft.Research.DynamicDataDisplay.Charts.Navigation.SimpleNavigationBar"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
Height="300" Width="300">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<RepeatButton Name="leftButton" Grid.Row="1" Grid.Column="0"/>
|
||||
<RepeatButton Name="upButton" Grid.Row="0" Grid.Column="1"/>
|
||||
<RepeatButton Name="rightButton" Grid.Row="1" Grid.Column="2"/>
|
||||
<RepeatButton Name="downButton" Grid.Column="1" Grid.Row="2"/>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
27
Charts/Navigation/SimpleNavigationBar.xaml.cs
Normal file
27
Charts/Navigation/SimpleNavigationBar.xaml.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
/// <summary>
|
||||
/// Interaction logic for SimpleNavigationBar.xaml
|
||||
/// </summary>
|
||||
public partial class SimpleNavigationBar : UserControl
|
||||
{
|
||||
public SimpleNavigationBar()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
52
Charts/Navigation/TouchPadScroll.cs
Normal file
52
Charts/Navigation/TouchPadScroll.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Interop;
|
||||
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
/// <summary>This class allows convenient navigation around viewport using touchpad on
|
||||
/// some notebooks</summary>
|
||||
public sealed class TouchpadScroll : NavigationBase {
|
||||
public TouchpadScroll() {
|
||||
Loaded += OnLoaded;
|
||||
}
|
||||
|
||||
private void OnLoaded(object sender, RoutedEventArgs e) {
|
||||
WindowInteropHelper helper = new WindowInteropHelper(Window.GetWindow(this));
|
||||
HwndSource source = HwndSource.FromHwnd(helper.Handle);
|
||||
source.AddHook(OnMessageAppeared);
|
||||
}
|
||||
|
||||
private IntPtr OnMessageAppeared(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
|
||||
if (msg == WindowsMessages.WM_MOUSEWHEEL) {
|
||||
Point mousePos = MessagesHelper.GetMousePosFromLParam(lParam);
|
||||
mousePos = TranslatePoint(mousePos, this);
|
||||
|
||||
if (Viewport.Output.Contains(mousePos)) {
|
||||
MouseWheelZoom(MessagesHelper.GetMousePosFromLParam(lParam), MessagesHelper.GetWheelDataFromWParam(wParam));
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
return IntPtr.Zero;
|
||||
}
|
||||
|
||||
double wheelZoomSpeed = 1.2;
|
||||
public double WheelZoomSpeed {
|
||||
get { return wheelZoomSpeed; }
|
||||
set { wheelZoomSpeed = value; }
|
||||
}
|
||||
|
||||
private void MouseWheelZoom(Point mousePos, int wheelRotationDelta) {
|
||||
Point zoomTo = mousePos.ScreenToData(Viewport.Transform);
|
||||
|
||||
double zoomSpeed = Math.Abs(wheelRotationDelta / Mouse.MouseWheelDeltaForOneLine);
|
||||
zoomSpeed *= wheelZoomSpeed;
|
||||
if (wheelRotationDelta < 0) {
|
||||
zoomSpeed = 1 / zoomSpeed;
|
||||
}
|
||||
Viewport.Visible = Viewport.Visible.Zoom(zoomTo, zoomSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
68
Charts/Navigation/VerticalScrollBar.cs
Normal file
68
Charts/Navigation/VerticalScrollBar.cs
Normal file
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Navigation
|
||||
{
|
||||
[Obsolete("Working wrongly.", true)]
|
||||
public sealed class VerticalScrollBar : PlotterScrollBar
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VerticalScrollBar"/> class.
|
||||
/// </summary>
|
||||
public VerticalScrollBar()
|
||||
{
|
||||
ScrollBar.Orientation = Orientation.Vertical;
|
||||
}
|
||||
|
||||
private Range<double> GetRange(Rect domain)
|
||||
{
|
||||
return new Range<double>(domain.Top, domain.Bottom);
|
||||
}
|
||||
|
||||
protected override DataRect CreateVisibleRect(DataRect rect, double scrollValue)
|
||||
{
|
||||
rect.YMin = scrollValue;
|
||||
return rect;
|
||||
}
|
||||
|
||||
protected override Panel GetHostPanel(Plotter plotter)
|
||||
{
|
||||
return plotter.LeftPanel;
|
||||
}
|
||||
|
||||
protected override void UpdateScrollBar(Viewport2D viewport)
|
||||
{
|
||||
if (viewport != null && !viewport.Domain.IsEmpty)
|
||||
{
|
||||
if (ScrollBar.Track != null)
|
||||
{
|
||||
//ScrollBar.Track.IsDirectionReversed = true;
|
||||
}
|
||||
|
||||
visibleRange = new Range<double>(viewport.Visible.YMin, viewport.Visible.YMax);
|
||||
domainRange = new Range<double>(viewport.Domain.YMin, viewport.Domain.YMax);
|
||||
|
||||
double size = visibleRange.Max - visibleRange.Min;
|
||||
ScrollBar.ViewportSize = size;
|
||||
|
||||
ScrollBar.Minimum = domainRange.Min + size;
|
||||
ScrollBar.Maximum = domainRange.Max;
|
||||
|
||||
ScrollBar.Value = visibleRange.Min;
|
||||
ScrollBar.Visibility = Visibility.Visible;
|
||||
}
|
||||
else
|
||||
{
|
||||
ScrollBar.Visibility = Visibility.Collapsed;
|
||||
}
|
||||
}
|
||||
|
||||
private Range<double> visibleRange;
|
||||
private Range<double> domainRange;
|
||||
}
|
||||
}
|
||||
7
Charts/Navigation/WindowsMessages.cs
Normal file
7
Charts/Navigation/WindowsMessages.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Navigation
|
||||
{
|
||||
internal static class WindowsMessages {
|
||||
internal static int WM_MOUSEWHEEL = 0x20A;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user