123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565 |
- // --------------------------------------------------------------------------------------------------------------------
- // <copyright file="PlotBase.cs" company="OxyPlot">
- // Copyright (c) 2014 OxyPlot contributors
- // </copyright>
- // <summary>
- // Represents a control that displays a <see cref="PlotModel" />.
- // </summary>
- // --------------------------------------------------------------------------------------------------------------------
- using Avalonia.Reactive;
- namespace OxyPlot.Avalonia
- {
- using global::Avalonia;
- using global::Avalonia.Controls;
- using global::Avalonia.Controls.Presenters;
- using global::Avalonia.Controls.Primitives;
- using global::Avalonia.Input;
- using global::Avalonia.Threading;
- using global::Avalonia.VisualTree;
- using System;
- using System.Collections.ObjectModel;
- using System.Linq;
- using System.Threading;
- /// <summary>
- /// Represents a control that displays a <see cref="PlotModel" />.
- /// </summary>
- public abstract partial class PlotBase : TemplatedControl, IPlotView
- {
- /// <summary>
- /// The Grid PART constant.
- /// </summary>
- protected const string PartPanel = "PART_Panel";
- /// <summary>
- /// The tracker definitions.
- /// </summary>
- private readonly ObservableCollection<TrackerDefinition> trackerDefinitions;
- /// <summary>
- /// The render context
- /// </summary>
- private CanvasRenderContext renderContext;
- /// <summary>
- /// The canvas.
- /// </summary>
- private Canvas canvas;
- /// <summary>
- /// The current tracker.
- /// </summary>
- private Control currentTracker;
- /// <summary>
- /// The grid.
- /// </summary>
- private Panel panel;
- /// <summary>
- /// Invalidation flag (0: no update, 1: update, 2: update date).
- /// </summary>
- private int isUpdateRequired;
- /// <summary>
- /// Invalidation flag (0: no update, 1: update visual elements).
- /// </summary>
- private int isPlotInvalidated;
- /// <summary>
- /// The mouse down point.
- /// </summary>
- private ScreenPoint mouseDownPoint;
- /// <summary>
- /// The overlays.
- /// </summary>
- private Canvas overlays;
- /// <summary>
- /// The zoom control.
- /// </summary>
- private ContentControl zoomControl;
- /// <summary>
- /// The is visible to user cache.
- /// </summary>
- private bool isVisibleToUserCache;
- /// <summary>
- /// The cached parent.
- /// </summary>
- private Control containerCache;
- /// <summary>
- /// Initializes a new instance of the <see cref="PlotBase" /> class.
- /// </summary>
- protected PlotBase()
- {
- DisconnectCanvasWhileUpdating = true;
- trackerDefinitions = new ObservableCollection<TrackerDefinition>();
-
- this.GetObservable(BoundsProperty).Subscribe(new AnonymousObserver<Rect>(OnSizeChanged));
- }
- /// <summary>
- /// Gets or sets a value indicating whether to disconnect the canvas while updating.
- /// </summary>
- /// <value><c>true</c> if canvas should be disconnected while updating; otherwise, <c>false</c>.</value>
- public bool DisconnectCanvasWhileUpdating { get; set; }
- /// <summary>
- /// Gets the actual model in the view.
- /// </summary>
- /// <value>
- /// The actual model.
- /// </value>
- Model IView.ActualModel
- {
- get
- {
- return ActualModel;
- }
- }
- /// <summary>
- /// Gets the actual model.
- /// </summary>
- /// <value>The actual model.</value>
- public abstract PlotModel ActualModel { get; }
- /// <summary>
- /// Gets the actual controller.
- /// </summary>
- /// <value>
- /// The actual <see cref="IController" />.
- /// </value>
- IController IView.ActualController
- {
- get
- {
- return ActualController;
- }
- }
- /// <summary>
- /// Gets the actual PlotView controller.
- /// </summary>
- /// <value>The actual PlotView controller.</value>
- public abstract IPlotController ActualController { get; }
- /// <summary>
- /// Gets the coordinates of the client area of the view.
- /// </summary>
- public OxyRect ClientArea
- {
- get
- {
- return new OxyRect(0, 0, Bounds.Width, Bounds.Height);
- }
- }
- /// <summary>
- /// Gets the tracker definitions.
- /// </summary>
- /// <value>The tracker definitions.</value>
- public ObservableCollection<TrackerDefinition> TrackerDefinitions
- {
- get
- {
- return trackerDefinitions;
- }
- }
- /// <summary>
- /// Hides the tracker.
- /// </summary>
- public void HideTracker()
- {
- if (currentTracker != null)
- {
- overlays.Children.Remove(currentTracker);
- currentTracker = null;
- }
- }
- /// <summary>
- /// Hides the zoom rectangle.
- /// </summary>
- public void HideZoomRectangle()
- {
- zoomControl.IsVisible = false;
- }
- /// <summary>
- /// Pans all axes.
- /// </summary>
- /// <param name="delta">The delta.</param>
- public void PanAllAxes(Vector delta)
- {
- ActualModel?.PanAllAxes(delta.X, delta.Y);
- InvalidatePlot(false);
- }
- /// <summary>
- /// Zooms all axes.
- /// </summary>
- /// <param name="factor">The zoom factor.</param>
- public void ZoomAllAxes(double factor)
- {
- ActualModel?.ZoomAllAxes(factor);
- InvalidatePlot(false);
- }
- /// <summary>
- /// Resets all axes.
- /// </summary>
- public void ResetAllAxes()
- {
- ActualModel?.ResetAllAxes();
- InvalidatePlot(false);
- }
- /// <summary>
- /// Invalidate the PlotView (not blocking the UI thread)
- /// </summary>
- /// <param name="updateData">The update Data.</param>
- public void InvalidatePlot(bool updateData = true)
- {
- // perform update on UI thread
- var updateState = updateData ? 2 : 1;
- int currentState = isUpdateRequired;
- while (currentState < updateState)
- {
- if (Interlocked.CompareExchange(ref isUpdateRequired, updateState, currentState) == currentState)
- {
- BeginInvoke(() => UpdateModel(updateData));
- break;
- }
- else
- {
- currentState = isUpdateRequired;
- }
- }
- }
- /// <summary>
- /// When overridden in a derived class, is invoked whenever application code or internal processes (such as a rebuilding layout pass)
- /// call <see cref="M:System.Windows.Controls.Control.ApplyTemplate" /> . In simplest terms, this means the method is called
- /// just before a UI element displays in an application. For more information, see Remarks.
- /// </summary>
- /// <param name="e">Event data for applying the template.</param>
- protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
- {
- base.OnApplyTemplate(e);
- panel = e.NameScope.Find(PartPanel) as Panel;
- if (panel == null)
- {
- return;
- }
- canvas = new Canvas();
- panel.Children.Add(canvas);
- renderContext = new CanvasRenderContext(canvas);
- overlays = new Canvas { Name = "Overlays" };
- panel.Children.Add(overlays);
- zoomControl = new ContentControl();
- overlays.Children.Add(zoomControl);
- }
- /// <summary>
- /// Sets the cursor type.
- /// </summary>
- /// <param name="cursorType">The cursor type.</param>
- public void SetCursorType(CursorType cursorType)
- {
- switch (cursorType)
- {
- case CursorType.Pan:
- Cursor = PanCursor;
- break;
- case CursorType.ZoomRectangle:
- Cursor = ZoomRectangleCursor;
- break;
- case CursorType.ZoomHorizontal:
- Cursor = ZoomHorizontalCursor;
- break;
- case CursorType.ZoomVertical:
- Cursor = ZoomVerticalCursor;
- break;
- default:
- Cursor = Cursor.Default;
- break;
- }
- }
- /// <summary>
- /// Shows the tracker.
- /// </summary>
- /// <param name="trackerHitResult">The tracker data.</param>
- public void ShowTracker(TrackerHitResult trackerHitResult)
- {
- if (trackerHitResult == null)
- {
- HideTracker();
- return;
- }
- var trackerTemplate = DefaultTrackerTemplate;
- if (trackerHitResult.Series != null && !string.IsNullOrEmpty(trackerHitResult.Series.TrackerKey))
- {
- var match = TrackerDefinitions.FirstOrDefault(t => t.TrackerKey == trackerHitResult.Series.TrackerKey);
- if (match != null)
- {
- trackerTemplate = match.TrackerTemplate;
- }
- }
- if (trackerTemplate == null)
- {
- HideTracker();
- return;
- }
- var tracker = trackerTemplate.Build(new ContentControl());
- // ReSharper disable once RedundantNameQualifier
- if (!object.ReferenceEquals(tracker, currentTracker))
- {
- HideTracker();
- overlays.Children.Add(tracker.Result);
- currentTracker = tracker.Result;
- }
- if (currentTracker != null)
- {
- currentTracker.DataContext = trackerHitResult;
- }
- }
- /// <summary>
- /// Shows the zoom rectangle.
- /// </summary>
- /// <param name="r">The rectangle.</param>
- public void ShowZoomRectangle(OxyRect r)
- {
- zoomControl.Width = r.Width;
- zoomControl.Height = r.Height;
- Canvas.SetLeft(zoomControl, r.Left);
- Canvas.SetTop(zoomControl, r.Top);
- zoomControl.Template = ZoomRectangleTemplate;
- zoomControl.IsVisible = true;
- }
- /// <summary>
- /// Stores text on the clipboard.
- /// </summary>
- /// <param name="text">The text.</param>
- public async void SetClipboardText(string text)
- {
- if (TopLevel.GetTopLevel(this) is { Clipboard: { } clipboard })
- {
- await clipboard.SetTextAsync(text).ConfigureAwait(true);
- }
- }
- /// <summary>
- /// Provides the behavior for the Arrange pass of Silverlight layout. Classes can override this method to define their own Arrange pass behavior.
- /// </summary>
- /// <param name="finalSize">The final area within the parent that this object should use to arrange itself and its children.</param>
- /// <returns>The actual size that is used after the element is arranged in layout.</returns>
- protected override Size ArrangeOverride(Size finalSize)
- {
- var actualSize = base.ArrangeOverride(finalSize);
- if (actualSize.Width > 0 && actualSize.Height > 0)
- {
- if (Interlocked.CompareExchange(ref isPlotInvalidated, 0, 1) == 1)
- {
- UpdateVisuals();
- }
- }
- return actualSize;
- }
- /// <summary>
- /// Updates the model.
- /// </summary>
- /// <param name="updateData">The update Data.</param>
- protected void UpdateModel(bool updateData = true)
- {
- if (Width <= 0 || Height <= 0 || ActualModel == null)
- {
- isUpdateRequired = 0;
- return;
- }
- lock (this.ActualModel.SyncRoot)
- {
- var updateState = (Interlocked.Exchange(ref isUpdateRequired, 0));
- if (updateState > 0)
- {
- ((IPlotModel)ActualModel).Update(updateState == 2 || updateData);
- }
- }
- if (Interlocked.CompareExchange(ref isPlotInvalidated, 1, 0) == 0)
- {
- // Invalidate the arrange state for the element.
- // After the invalidation, the element will have its layout updated,
- // which will occur asynchronously unless subsequently forced by UpdateLayout.
- BeginInvoke(InvalidateArrange);
- }
- }
- /// <summary>
- /// Determines whether the plot is currently visible to the user.
- /// </summary>
- /// <returns><c>true</c> if the plot is currently visible to the user; otherwise, <c>false</c>.</returns>
- protected bool IsVisibleToUser()
- {
- return IsUserVisible(this);
- }
- /// <summary>
- /// Determines whether the specified element is currently visible to the user.
- /// </summary>
- /// <param name="element">The element.</param>
- /// <returns><c>true</c> if the specified element is currently visible to the user; otherwise, <c>false</c>.</returns>
- private static bool IsUserVisible(Control element)
- {
- return element.IsEffectivelyVisible;
- }
- /// <summary>
- /// Called when the size of the control is changed.
- /// </summary>
- /// <param name="sender">The sender.</param>
- /// <param name="size">The new size</param>
- private void OnSizeChanged(Rect size)
- {
- if (size.Height > 0 && size.Width > 0)
- {
- InvalidatePlot(false);
- }
- }
- /// <summary>
- /// Gets the relevant parent.
- /// </summary>
- /// <typeparam name="T">Type of the relevant parent</typeparam>
- /// <param name="obj">The object.</param>
- /// <returns>The relevant parent.</returns>
- private Control GetRelevantParent<T>(Visual obj)
- where T : Control
- {
- var container = obj.GetVisualParent();
- if (container is ContentPresenter contentPresenter)
- {
- container = GetRelevantParent<T>(contentPresenter);
- }
- if (container is Panel panel)
- {
- container = GetRelevantParent<ScrollViewer>(panel);
- }
- if (!(container is T) && (container != null))
- {
- container = GetRelevantParent<T>(container);
- }
- return (Control)container;
- }
- /// <summary>
- /// Updates the visuals.
- /// </summary>
- private void UpdateVisuals()
- {
- if (canvas == null || renderContext == null)
- {
- return;
- }
- if (!IsVisibleToUser())
- {
- return;
- }
- // Clear the canvas
- canvas.Children.Clear();
- if (ActualModel?.Background.IsVisible() == true)
- {
- canvas.Background = ActualModel.Background.ToBrush();
- }
- else
- {
- canvas.Background = null;
- }
- if (ActualModel != null)
- {
- lock (this.ActualModel.SyncRoot)
- {
- var updateState = (Interlocked.Exchange(ref isUpdateRequired, 0));
- if (updateState > 0)
- {
- ((IPlotModel)ActualModel).Update(updateState == 2);
- }
- if (DisconnectCanvasWhileUpdating)
- {
- // TODO: profile... not sure if this makes any difference
- var idx = panel.Children.IndexOf(canvas);
- if (idx != -1)
- {
- panel.Children.RemoveAt(idx);
- }
- ((IPlotModel)ActualModel).Render(renderContext, new OxyRect(0, 0, canvas.Bounds.Width, canvas.Bounds.Height));
- // reinsert the canvas again
- if (idx != -1)
- {
- panel.Children.Insert(idx, canvas);
- }
- }
- else
- {
- ((IPlotModel)ActualModel).Render(renderContext, new OxyRect(0, 0, canvas.Bounds.Width, canvas.Bounds.Height));
- }
- }
- }
- }
- /// <summary>
- /// Invokes the specified action on the dispatcher, if necessary.
- /// </summary>
- /// <param name="action">The action.</param>
- private static void BeginInvoke(Action action)
- {
- if (Dispatcher.UIThread.CheckAccess())
- {
- action?.Invoke();
- }
- else
- {
- Dispatcher.UIThread.InvokeAsync(action, DispatcherPriority.Loaded);
- }
- }
- }
- }
|