[12503] | 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 |
|
---|
| 14 | namespace 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 | }
|
---|