PageRenderTime 25ms CodeModel.GetById 1ms RepoModel.GetById 0ms app.codeStats 0ms

/Main/src/DynamicDataDisplay/Viewport2D.cs

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