Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Problems.GrammaticalOptimization/DynamicDataDisplay/Viewport2D.cs @ 13808

Last change on this file since 13808 was 12503, checked in by aballeit, 9 years ago

#2283 added GUI and charts; fixed MCTS

File size: 20.2 KB
Line 
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}
Note: See TracBrowser for help on using the repository browser.