PageRenderTime 58ms CodeModel.GetById 14ms app.highlight 35ms RepoModel.GetById 1ms app.codeStats 0ms

/Main/src/DynamicDataDisplay/Viewport2D.cs

#
C# | 697 lines | 499 code | 109 blank | 89 comment | 75 complexity | 198a54162d01ed2081a86ea6e63548a7 MD5 | raw file
  1using System;
  2using System.Collections.ObjectModel;
  3using System.Collections.Specialized;
  4using System.ComponentModel;
  5using System.Diagnostics;
  6using System.Diagnostics.CodeAnalysis;
  7using System.Windows;
  8using System.Windows.Data;
  9using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
 10using Microsoft.Research.DynamicDataDisplay.ViewportConstraints;
 11using Microsoft.Research.DynamicDataDisplay.Common;
 12using System.Windows.Threading;
 13
 14namespace Microsoft.Research.DynamicDataDisplay
 15{
 16	/// <summary>
 17	/// Viewport2D provides virtual coordinates.
 18	/// </summary>
 19	public partial class Viewport2D : DependencyObject
 20	{
 21		private DispatcherOperation updateVisibleOperation = null;
 22		private int updateVisibleCounter = 0;
 23		private readonly RingDictionary<DataRect> prevVisibles = new RingDictionary<DataRect>(2);
 24		private bool fromContentBounds = false;
 25		private readonly DispatcherPriority invocationPriority = DispatcherPriority.Send;
 26
 27		public const string VisiblePropertyName = "Visible";
 28
 29		public bool FromContentBounds
 30		{
 31			get { return fromContentBounds; }
 32			set { fromContentBounds = value; }
 33		}
 34
 35		private readonly Plotter2D plotter;
 36		internal Plotter2D Plotter2D
 37		{
 38			get { return plotter; }
 39		}
 40
 41		private readonly FrameworkElement hostElement;
 42		internal FrameworkElement HostElement
 43		{
 44			get { return hostElement; }
 45		}
 46
 47		protected internal Viewport2D(FrameworkElement host, Plotter2D plotter)
 48		{
 49			hostElement = host;
 50			host.ClipToBounds = true;
 51			host.SizeChanged += OnHostElementSizeChanged;
 52
 53			this.plotter = plotter;
 54			plotter.Children.CollectionChanged += OnPlotterChildrenChanged;
 55
 56			constraints = new ConstraintCollection(this);
 57			constraints.Add(new MinimalSizeConstraint());
 58			constraints.CollectionChanged += constraints_CollectionChanged;
 59
 60			fitToViewConstraints = new ConstraintCollection(this);
 61			fitToViewConstraints.CollectionChanged += fitToViewConstraints_CollectionChanged;
 62
 63			readonlyContentBoundsHosts = new ReadOnlyObservableCollection<DependencyObject>(contentBoundsHosts);
 64
 65			UpdateVisible();
 66			UpdateTransform();
 67		}
 68
 69		private void OnHostElementSizeChanged(object sender, SizeChangedEventArgs e)
 70		{
 71			SetValue(OutputPropertyKey, new Rect(e.NewSize));
 72			CoerceValue(VisibleProperty);
 73		}
 74
 75		private void fitToViewConstraints_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
 76		{
 77			if (IsFittedToView)
 78			{
 79				CoerceValue(VisibleProperty);
 80			}
 81		}
 82
 83		private void constraints_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
 84		{
 85			CoerceValue(VisibleProperty);
 86		}
 87
 88		private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
 89		{
 90			Viewport2D viewport = (Viewport2D)d;
 91			viewport.UpdateTransform();
 92			viewport.RaisePropertyChangedEvent(e);
 93		}
 94
 95		public BindingExpressionBase SetBinding(DependencyProperty property, BindingBase binding)
 96		{
 97			return BindingOperations.SetBinding(this, property, binding);
 98		}
 99
100		/// <summary>
101		/// Forces viewport to go to fit to view mode - clears locally set value of <see cref="Visible"/> property
102		/// and sets it during the coercion process to a value of united content bounds of all charts inside of <see cref="Plotter"/>.
103		/// </summary>
104		public void FitToView()
105		{
106			if (!IsFittedToView)
107			{
108				ClearValue(VisibleProperty);
109				CoerceValue(VisibleProperty);
110				FittedToView.Raise(this);
111			}
112		}
113
114		public event EventHandler FittedToView;
115
116		/// <summary>
117		/// Gets a value indicating whether Viewport is fitted to view.
118		/// </summary>
119		/// <value>
120		/// 	<c>true</c> if Viewport is fitted to view; otherwise, <c>false</c>.
121		/// </value>
122		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
123		public bool IsFittedToView
124		{
125			get { return ReadLocalValue(VisibleProperty) == DependencyProperty.UnsetValue; }
126		}
127
128		internal void UpdateVisible()
129		{
130			if (updateVisibleCounter == 0)
131			{
132				UpdateVisibleBody();
133			}
134			else if (updateVisibleOperation == null)
135			{
136				updateVisibleOperation = Dispatcher.BeginInvoke(() => UpdateVisibleBody(), invocationPriority);
137				return;
138			}
139			else if (updateVisibleOperation.Status == DispatcherOperationStatus.Pending)
140			{
141				updateVisibleOperation.Abort();
142				updateVisibleOperation = Dispatcher.BeginInvoke(() => UpdateVisibleBody(), invocationPriority);
143			}
144		}
145
146		private void UpdateVisibleBody()
147		{
148			if (updateVisibleCounter > 0)
149				return;
150
151			updateVisibleCounter++;
152			if (IsFittedToView)
153			{
154				CoerceValue(VisibleProperty);
155			}
156			updateVisibleOperation = null;
157			updateVisibleCounter--;
158		}
159
160		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
161		[EditorBrowsable(EditorBrowsableState.Never)]
162		public Plotter2D Plotter
163		{
164			get { return plotter; }
165		}
166
167		private readonly ConstraintCollection constraints;
168		/// <summary>
169		/// Gets the collection of <see cref="ViewportConstraint"/>s that are applied each time <see cref="Visible"/> is updated.
170		/// </summary>
171		/// <value>The constraints.</value>
172		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
173		public ConstraintCollection Constraints
174		{
175			get { return constraints; }
176		}
177
178
179		private readonly ConstraintCollection fitToViewConstraints;
180
181		/// <summary>
182		/// Gets the collection of <see cref="ViewportConstraint"/>s that are applied only when Viewport is fitted to view.
183		/// </summary>
184		/// <value>The fit to view constraints.</value>
185		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
186		public ConstraintCollection FitToViewConstraints
187		{
188			get { return fitToViewConstraints; }
189		}
190
191		#region Output property
192
193		/// <summary>
194		/// Gets the rectangle in screen coordinates that is output.
195		/// </summary>
196		/// <value>The output.</value>
197		public Rect Output
198		{
199			get { return (Rect)GetValue(OutputProperty); }
200		}
201
202		[SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")]
203		private static readonly DependencyPropertyKey OutputPropertyKey = DependencyProperty.RegisterReadOnly(
204			"Output",
205			typeof(Rect),
206			typeof(Viewport2D),
207			new FrameworkPropertyMetadata(new Rect(0, 0, 1, 1), OnPropertyChanged));
208
209		/// <summary>
210		/// Identifies the <see cref="Output"/> dependency property.
211		/// </summary>
212		public static readonly DependencyProperty OutputProperty = OutputPropertyKey.DependencyProperty;
213
214		#endregion
215
216		#region UnitedContentBounds property
217
218		/// <summary>
219		/// Gets the united content bounds of all the charts.
220		/// </summary>
221		/// <value>The content bounds.</value>
222		public DataRect UnitedContentBounds
223		{
224			get { return (DataRect)GetValue(UnitedContentBoundsProperty); }
225			internal set { SetValue(UnitedContentBoundsProperty, value); }
226		}
227
228		/// <summary>
229		/// Identifies the <see cref="UnitedContentBounds"/> dependency property.
230		/// </summary>
231		public static readonly DependencyProperty UnitedContentBoundsProperty = DependencyProperty.Register(
232		  "UnitedContentBounds",
233		  typeof(DataRect),
234		  typeof(Viewport2D),
235		  new FrameworkPropertyMetadata(DataRect.Empty, OnUnitedContentBoundsChanged));
236
237		private static void OnUnitedContentBoundsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
238		{
239			Viewport2D owner = (Viewport2D)d;
240			owner.ContentBoundsChanged.Raise(owner);
241		}
242
243		public event EventHandler ContentBoundsChanged;
244
245		#endregion
246
247		#region Visible property
248
249		/// <summary>
250		/// Gets or sets the visible rectangle.
251		/// </summary>
252		/// <value>The visible.</value>
253		public DataRect Visible
254		{
255			get { return (DataRect)GetValue(VisibleProperty); }
256			set { SetValue(VisibleProperty, value); }
257		}
258
259		/// <summary>
260		/// Identifies the <see cref="Visible"/> dependency property.
261		/// </summary>
262		public static readonly DependencyProperty VisibleProperty =
263			DependencyProperty.Register("Visible", typeof(DataRect), typeof(Viewport2D),
264			new FrameworkPropertyMetadata(
265				new DataRect(0, 0, 1, 1),
266				OnPropertyChanged,
267				OnCoerceVisible),
268			ValidateVisibleCallback);
269
270		private static bool ValidateVisibleCallback(object value)
271		{
272			DataRect rect = (DataRect)value;
273
274			return !rect.IsNaN();
275		}
276
277		private void UpdateContentBoundsHosts()
278		{
279			contentBoundsHosts.Clear();
280			foreach (var item in plotter.Children)
281			{
282				DependencyObject dependencyObject = item as DependencyObject;
283				if (dependencyObject != null)
284				{
285					bool hasNonEmptyBounds = !Viewport2D.GetContentBounds(dependencyObject).IsEmpty;
286					if (hasNonEmptyBounds && Viewport2D.GetIsContentBoundsHost(dependencyObject))
287					{
288						contentBoundsHosts.Add(dependencyObject);
289					}
290				}
291			}
292
293			bool prevFromContentBounds = FromContentBounds;
294			FromContentBounds = true;
295			UpdateVisible();
296			FromContentBounds = prevFromContentBounds;
297		}
298
299		private readonly ObservableCollection<DependencyObject> contentBoundsHosts = new ObservableCollection<DependencyObject>();
300		private readonly ReadOnlyObservableCollection<DependencyObject> readonlyContentBoundsHosts;
301		/// <summary>
302		/// Gets the collection of all charts that can has its own content bounds.
303		/// </summary>
304		/// <value>The content bounds hosts.</value>
305		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
306		public ReadOnlyObservableCollection<DependencyObject> ContentBoundsHosts
307		{
308			get { return readonlyContentBoundsHosts; }
309		}
310
311		private bool useApproximateContentBoundsComparison = true;
312		/// <summary>
313		/// Gets or sets a value indicating whether to use approximate content bounds comparison.
314		/// This this property to true can increase performance, as Visible will change less often.
315		/// </summary>
316		/// <value>
317		/// 	<c>true</c> if approximate content bounds comparison is used; otherwise, <c>false</c>.
318		/// </value>
319		public bool UseApproximateContentBoundsComparison
320		{
321			get { return useApproximateContentBoundsComparison; }
322			set { useApproximateContentBoundsComparison = value; }
323		}
324
325		private double maxContentBoundsComparisonMistake = 0.02;
326		public double MaxContentBoundsComparisonMistake
327		{
328			get { return maxContentBoundsComparisonMistake; }
329			set { maxContentBoundsComparisonMistake = value; }
330		}
331
332		private DataRect prevContentBounds = DataRect.Empty;
333		protected virtual DataRect CoerceVisible(DataRect newVisible)
334		{
335			if (Plotter == null)
336			{
337				return newVisible;
338			}
339
340			bool isDefaultValue = newVisible == (DataRect)VisibleProperty.DefaultMetadata.DefaultValue;
341			if (isDefaultValue)
342			{
343				newVisible = DataRect.Empty;
344			}
345
346			if (isDefaultValue && IsFittedToView)
347			{
348				// determining content bounds
349				DataRect bounds = DataRect.Empty;
350
351				foreach (var item in contentBoundsHosts)
352				{
353					IPlotterElement plotterElement = item as IPlotterElement;
354					if (plotterElement == null)
355						continue;
356					if (plotterElement.Plotter == null)
357						continue;
358
359					DataRect contentBounds = Viewport2D.GetContentBounds(item);
360					if (contentBounds.Width.IsNaN() || contentBounds.Height.IsNaN())
361						continue;
362
363					bounds.UnionFinite(contentBounds);
364				}
365
366				if (useApproximateContentBoundsComparison)
367				{
368					var intersection = prevContentBounds;
369					intersection.Intersect(bounds);
370
371					double currSquare = bounds.GetSquare();
372					double prevSquare = prevContentBounds.GetSquare();
373					double intersectionSquare = intersection.GetSquare();
374
375					double squareTopLimit = 1 + maxContentBoundsComparisonMistake;
376					double squareBottomLimit = 1 - maxContentBoundsComparisonMistake;
377
378					if (intersectionSquare != 0)
379					{
380						double currRatio = currSquare / intersectionSquare;
381						double prevRatio = prevSquare / intersectionSquare;
382
383						if (squareBottomLimit < currRatio &&
384							currRatio < squareTopLimit &&
385							squareBottomLimit < prevRatio &&
386							prevRatio < squareTopLimit)
387						{
388							bounds = prevContentBounds;
389						}
390					}
391				}
392
393				prevContentBounds = bounds;
394				UnitedContentBounds = bounds;
395
396				// applying fit-to-view constraints
397				bounds = fitToViewConstraints.Apply(Visible, bounds, this);
398
399				// enlarging
400				if (!bounds.IsEmpty)
401				{
402					bounds = CoordinateUtilities.RectZoom(bounds, bounds.GetCenter(), clipToBoundsEnlargeFactor);
403				}
404				else
405				{
406					bounds = (DataRect)VisibleProperty.DefaultMetadata.DefaultValue;
407				}
408				newVisible.Union(bounds);
409			}
410
411			if (newVisible.IsEmpty)
412			{
413				newVisible = (DataRect)VisibleProperty.DefaultMetadata.DefaultValue;
414			}
415			else if (newVisible.Width == 0 || newVisible.Height == 0 || newVisible.IsEmptyX || newVisible.IsEmptyY)
416			{
417				DataRect defRect = (DataRect)VisibleProperty.DefaultMetadata.DefaultValue;
418				Size size = newVisible.Size;
419				Point location = newVisible.Location;
420
421				if (newVisible.Width == 0 || newVisible.IsEmptyX)
422				{
423					size.Width = defRect.Width;
424					location.X -= size.Width / 2;
425				}
426				if (newVisible.Height == 0 || newVisible.IsEmptyY)
427				{
428					size.Height = defRect.Height;
429					location.Y -= size.Height / 2;
430				}
431
432				newVisible = new DataRect(location, size);
433			}
434
435			// apply domain constraint
436			newVisible = domainConstraint.Apply(Visible, newVisible, this);
437
438			// apply other restrictions
439			newVisible = constraints.Apply(Visible, newVisible, this);
440
441			// applying transform's data domain constraint
442			if (!transform.DataTransform.DataDomain.IsEmpty)
443			{
444				var newDataRect = newVisible.ViewportToData(transform);
445				newDataRect = DataRect.Intersect(newDataRect, transform.DataTransform.DataDomain);
446				newVisible = newDataRect.DataToViewport(transform);
447			}
448
449			if (newVisible.IsEmpty)
450				newVisible = new Rect(0, 0, 1, 1);
451
452			return newVisible;
453		}
454
455		private static object OnCoerceVisible(DependencyObject d, object newValue)
456		{
457			Viewport2D viewport = (Viewport2D)d;
458			DataRect newVisible = (DataRect)newValue;
459
460			DataRect newRect = viewport.CoerceVisible(newVisible);
461
462			if (viewport.FromContentBounds && viewport.prevVisibles.ContainsValue(newRect))
463				return DependencyProperty.UnsetValue;
464			else
465				viewport.prevVisibles.AddValue(newRect);
466
467			if (newRect.Width == 0 || newRect.Height == 0)
468			{
469				// doesn't apply rects with zero square
470				return DependencyProperty.UnsetValue;
471			}
472			else
473			{
474				return newRect;
475			}
476		}
477
478		#endregion
479
480		#region Domain
481
482		private readonly DomainConstraint domainConstraint = new DomainConstraint { Domain = Rect.Empty };
483
484		/// <summary>
485		/// Gets or sets the domain - rectangle in viewport coordinates that limits maximal size of <see cref="Visible"/> rectangle.
486		/// </summary>
487		/// <value>The domain.</value>
488		public DataRect Domain
489		{
490			get { return (DataRect)GetValue(DomainProperty); }
491			set { SetValue(DomainProperty, value); }
492		}
493
494		public static readonly DependencyProperty DomainProperty = DependencyProperty.Register(
495		  "Domain",
496		  typeof(DataRect),
497		  typeof(Viewport2D),
498		  new FrameworkPropertyMetadata(DataRect.Empty, OnDomainReplaced));
499
500		private static void OnDomainReplaced(DependencyObject d, DependencyPropertyChangedEventArgs e)
501		{
502			Viewport2D owner = (Viewport2D)d;
503			owner.OnDomainChanged();
504		}
505
506		private void OnDomainChanged()
507		{
508			domainConstraint.Domain = Domain;
509			DomainChanged.Raise(this);
510			CoerceValue(VisibleProperty);
511		}
512
513		/// <summary>
514		/// Occurs when <see cref="Domain"/> property changes.
515		/// </summary>
516		public event EventHandler DomainChanged;
517
518		#endregion
519
520		private double clipToBoundsEnlargeFactor = 1.10;
521		/// <summary>
522		/// Gets or sets the viewport enlarge factor.
523		/// </summary>
524		/// <remarks>
525		/// Default value is 1.10.
526		/// </remarks>
527		/// <value>The clip to bounds factor.</value>
528		public double ClipToBoundsEnlargeFactor
529		{
530			get { return clipToBoundsEnlargeFactor; }
531			set
532			{
533				if (clipToBoundsEnlargeFactor != value)
534				{
535					clipToBoundsEnlargeFactor = value;
536					UpdateVisible();
537				}
538			}
539		}
540
541		private void UpdateTransform()
542		{
543			transform = transform.WithRects(Visible, Output);
544		}
545
546		private CoordinateTransform transform = CoordinateTransform.CreateDefault();
547		/// <summary>
548		/// Gets or sets the coordinate transform of Viewport.
549		/// </summary>
550		/// <value>The transform.</value>
551		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
552		[NotNull]
553		public virtual CoordinateTransform Transform
554		{
555			get { return transform; }
556			set
557			{
558				value.VerifyNotNull();
559
560				if (value != transform)
561				{
562					var oldTransform = transform;
563
564					transform = value;
565
566					RaisePropertyChangedEvent("Transform", oldTransform, transform);
567				}
568			}
569		}
570
571		/// <summary>
572		/// Occurs when viewport property changes.
573		/// </summary>
574		public event EventHandler<ExtendedPropertyChangedEventArgs> PropertyChanged;
575
576		private void RaisePropertyChangedEvent(string propertyName, object oldValue, object newValue)
577		{
578			if (PropertyChanged != null)
579			{
580				RaisePropertyChanged(new ExtendedPropertyChangedEventArgs { PropertyName = propertyName, OldValue = oldValue, NewValue = newValue });
581			}
582		}
583
584		private void RaisePropertyChangedEvent(string propertyName)
585		{
586			if (PropertyChanged != null)
587			{
588				RaisePropertyChanged(new ExtendedPropertyChangedEventArgs { PropertyName = propertyName });
589			}
590		}
591
592		/// <summary>
593		/// Gets or sets the type of viewport change.
594		/// </summary>
595		/// <value>The type of the change.</value>
596		public ChangeType ChangeType { get; internal set; }
597
598		/// <summary>
599		/// Sets the type of viewport change.
600		/// </summary>
601		/// <param name="changeType">Type of the change.</param>
602		public void SetChangeType(ChangeType changeType = ChangeType.None)
603		{
604			this.ChangeType = changeType;
605		}
606
607		int propertyChangedCounter = 0;
608		private DispatcherOperation notifyOperation = null;
609		private void RaisePropertyChangedEvent(DependencyPropertyChangedEventArgs e)
610		{
611			if (notifyOperation == null)
612			{
613				ChangeType changeType = ChangeType;
614				notifyOperation = Dispatcher.BeginInvoke(() =>
615				{
616					RaisePropertyChangedEventBody(e, changeType);
617				}, invocationPriority);
618				return;
619			}
620			else if (notifyOperation.Status == DispatcherOperationStatus.Pending)
621			{
622				notifyOperation.Abort();
623				ChangeType changeType = ChangeType;
624				notifyOperation = Dispatcher.BeginInvoke(() =>
625				{
626					RaisePropertyChangedEventBody(e, changeType);
627				}, invocationPriority);
628			}
629		}
630
631		private void RaisePropertyChangedEventBody(DependencyPropertyChangedEventArgs e, ChangeType changeType)
632		{
633			if (propertyChangedCounter > 0)
634				return;
635
636			propertyChangedCounter++;
637			RaisePropertyChanged(ExtendedPropertyChangedEventArgs.FromDependencyPropertyChanged(e), changeType);
638			propertyChangedCounter--;
639
640			notifyOperation = null;
641		}
642
643		protected virtual void RaisePropertyChanged(ExtendedPropertyChangedEventArgs args, ChangeType changeType = ChangeType.None)
644		{
645			args.ChangeType = changeType;
646			PropertyChanged.Raise(this, args);
647		}
648
649		private void OnPlotterChildrenChanged(object sender, NotifyCollectionChangedEventArgs e)
650		{
651			UpdateContentBoundsHosts();
652		}
653
654		#region Panning state
655
656		private Viewport2DPanningState panningState = Viewport2DPanningState.NotPanning;
657		public Viewport2DPanningState PanningState
658		{
659			get { return panningState; }
660			set
661			{
662				var prevState = panningState;
663
664				panningState = value;
665
666				OnPanningStateChanged(prevState, panningState);
667			}
668		}
669
670		private void OnPanningStateChanged(Viewport2DPanningState prevState, Viewport2DPanningState currState)
671		{
672			PanningStateChanged.Raise(this, prevState, currState);
673			if (currState == Viewport2DPanningState.Panning)
674				BeginPanning.Raise(this);
675			else if (currState == Viewport2DPanningState.NotPanning)
676				EndPanning.Raise(this);
677		}
678
679		internal event EventHandler<ValueChangedEventArgs<Viewport2DPanningState>> PanningStateChanged;
680
681		public event EventHandler BeginPanning;
682		public event EventHandler EndPanning;
683
684		#endregion // end of Panning state
685	}
686
687	public enum ChangeType
688	{
689		None = 0,
690		Pan,
691		PanX,
692		PanY,
693		Zoom,
694		ZoomX,
695		ZoomY
696	}
697}