Files
DynamicDataDisplay/Charts/Axes/AxisBase.cs
2024-02-23 00:46:06 -05:00

412 lines
12 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Data;
using System.Diagnostics;
using System.ComponentModel;
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
using Microsoft.Research.DynamicDataDisplay.Common;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Windows.Threading;
namespace Microsoft.Research.DynamicDataDisplay.Charts
{
/// <summary>
/// Represents a base class for all axes in ChartPlotter.
/// Contains a real UI representation of axis - AxisControl, and means to adjust number of ticks, algorythms of their generating and
/// look of ticks' labels.
/// </summary>
/// <typeparam name="T">Type of each tick's value</typeparam>
public abstract class AxisBase<T> : GeneralAxis, ITypedAxis<T>, IValueConversion<T>
{
/// <summary>
/// Initializes a new instance of the <see cref="AxisBase&lt;T&gt;"/> class.
/// </summary>
/// <param name="axisControl">The axis control.</param>
/// <param name="convertFromDouble">The convert from double.</param>
/// <param name="convertToDouble">The convert to double.</param>
protected AxisBase(AxisControl<T> axisControl, Func<double, T> convertFromDouble, Func<T, double> convertToDouble)
{
if (axisControl == null)
throw new ArgumentNullException("axisControl");
if (convertFromDouble == null)
throw new ArgumentNullException("convertFromDouble");
if (convertToDouble == null)
throw new ArgumentNullException("convertToDouble");
this.convertToDouble = convertToDouble;
this.convertFromDouble = convertFromDouble;
this.axisControl = axisControl;
axisControl.MakeDependent();
axisControl.ConvertToDouble = convertToDouble;
axisControl.ScreenTicksChanged += axisControl_ScreenTicksChanged;
Content = axisControl;
axisControl.SetBinding(Control.BackgroundProperty, new Binding("Background") { Source = this });
Focusable = false;
Loaded += OnLoaded;
}
public override void ForceUpdate()
{
axisControl.UpdateUI();
}
private void axisControl_ScreenTicksChanged(object sender, EventArgs e)
{
RaiseTicksChanged();
}
/// <summary>
/// Gets or sets a value indicating whether this axis is default axis.
/// ChartPlotter's AxisGrid gets axis ticks to display from two default axes - horizontal and vertical.
/// </summary>
/// <value>
/// <c>true</c> if this instance is default axis; otherwise, <c>false</c>.
/// </value>
public bool IsDefaultAxis
{
get { return Microsoft.Research.DynamicDataDisplay.Plotter.GetIsDefaultAxis(this); }
set { Microsoft.Research.DynamicDataDisplay.Plotter.SetIsDefaultAxis(this, value); }
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
RaiseTicksChanged();
}
/// <summary>
/// Gets the screen coordinates of axis ticks.
/// </summary>
/// <value>The screen ticks.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override double[] ScreenTicks
{
get { return axisControl.ScreenTicks; }
}
/// <summary>
/// Gets the screen coordinates of minor ticks.
/// </summary>
/// <value>The minor screen ticks.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[EditorBrowsable(EditorBrowsableState.Never)]
public override MinorTickInfo<double>[] MinorScreenTicks
{
get { return axisControl.MinorScreenTicks; }
}
private AxisControl<T> axisControl;
/// <summary>
/// Gets the axis control - actual UI representation of axis.
/// </summary>
/// <value>The axis control.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public AxisControl<T> AxisControl
{
get { return axisControl; }
}
/// <summary>
/// Gets or sets the ticks provider, which is used to generate ticks in given range.
/// </summary>
/// <value>The ticks provider.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public ITicksProvider<T> TicksProvider
{
get { return axisControl.TicksProvider; }
set { axisControl.TicksProvider = value; }
}
/// <summary>
/// Gets or sets the label provider, that is used to create UI look of axis ticks.
///
/// Should not be null.
/// </summary>
/// <value>The label provider.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[NotNull]
public LabelProviderBase<T> LabelProvider
{
get { return axisControl.LabelProvider; }
set { axisControl.LabelProvider = value; }
}
/// <summary>
/// Gets or sets the major label provider, which creates labels for major ticks.
/// If null, major labels will not be shown.
/// </summary>
/// <value>The major label provider.</value>
public LabelProviderBase<T> MajorLabelProvider
{
get { return axisControl.MajorLabelProvider; }
set { axisControl.MajorLabelProvider = value; }
}
/// <summary>
/// Gets or sets the label string format, used to create simple formats of each tick's label, such as
/// changing tick label from "1.2" to "$1.2".
/// Should be in format "*{0}*", where '*' is any number of any chars.
///
/// If value is null, format string will not be used.
/// </summary>
/// <value>The label string format.</value>
public string LabelStringFormat
{
get { return LabelProvider.LabelStringFormat; }
set { LabelProvider.LabelStringFormat = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to show minor ticks.
/// </summary>
/// <value><c>true</c> if show minor ticks; otherwise, <c>false</c>.</value>
public bool ShowMinorTicks
{
get { return axisControl.DrawMinorTicks; }
set { axisControl.DrawMinorTicks = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to show major labels.
/// </summary>
/// <value><c>true</c> if show major labels; otherwise, <c>false</c>.</value>
public bool ShowMajorLabels
{
get { return axisControl.DrawMajorLabels; }
set { axisControl.DrawMajorLabels = value; }
}
protected override void OnPlotterAttached(Plotter2D plotter)
{
plotter.Viewport.PropertyChanged += OnViewportPropertyChanged;
Panel panel = GetPanelByPlacement(Placement);
if (panel != null)
{
int index = GetInsertionIndexByPlacement(Placement, panel);
panel.Children.Insert(index, this);
}
using (axisControl.OpenUpdateRegion(true))
{
UpdateAxisControl(plotter);
}
}
private void UpdateAxisControl(Plotter2D plotter2d)
{
axisControl.Transform = plotter2d.Viewport.Transform;
axisControl.Range = CreateRangeFromRect(plotter2d.Visible.ViewportToData(plotter2d.Viewport.Transform));
}
private int GetInsertionIndexByPlacement(AxisPlacement placement, Panel panel)
{
int index = panel.Children.Count;
switch (placement)
{
case AxisPlacement.Left:
index = 0;
break;
case AxisPlacement.Top:
index = 0;
break;
default:
break;
}
return index;
}
ExtendedPropertyChangedEventArgs visibleChangedEventArgs;
int viewportPropertyChangedEnters = 0;
DataRect prevDataRect = DataRect.Empty;
private void OnViewportPropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
{
if (viewportPropertyChangedEnters > 4)
{
if (e.PropertyName == "Visible")
{
visibleChangedEventArgs = e;
}
return;
}
viewportPropertyChangedEnters++;
Viewport2D viewport = (Viewport2D)sender;
DataRect visible = viewport.Visible;
DataRect dataRect = visible.ViewportToData(viewport.Transform);
bool forceUpdate = dataRect != prevDataRect;
prevDataRect = dataRect;
Range<T> range = CreateRangeFromRect(dataRect);
using (axisControl.OpenUpdateRegion(false)) // todo was forceUpdate
{
axisControl.Range = range;
axisControl.Transform = viewport.Transform;
}
Dispatcher.BeginInvoke(() =>
{
viewportPropertyChangedEnters--;
if (visibleChangedEventArgs != null)
{
OnViewportPropertyChanged(Plotter.Viewport, visibleChangedEventArgs);
}
visibleChangedEventArgs = null;
}, DispatcherPriority.Render);
}
private Func<double, T> convertFromDouble;
/// <summary>
/// Gets or sets the delegate that is used to create each tick from double.
/// Is used to create typed range to display for internal AxisControl.
/// If changed, ConvertToDouble should be changed appropriately, too.
/// Should not be null.
/// </summary>
/// <value>The convert from double.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[NotNull]
public Func<double, T> ConvertFromDouble
{
get { return convertFromDouble; }
set
{
if (value == null)
throw new ArgumentNullException("value");
if (convertFromDouble != value)
{
convertFromDouble = value;
if (ParentPlotter != null)
{
UpdateAxisControl(ParentPlotter);
}
}
}
}
private Func<T, double> convertToDouble;
/// <summary>
/// Gets or sets the delegate that is used to convert each tick to double.
/// Is used by internal AxisControl to convert tick to double to get tick's coordinates inside of viewport.
/// If changed, ConvertFromDouble should be changed appropriately, too.
/// Should not be null.
/// </summary>
/// <value>The convert to double.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
[NotNull]
public Func<T, double> ConvertToDouble
{
get { return convertToDouble; }
set
{
if (value == null)
throw new ArgumentNullException("value");
if (convertToDouble != value)
{
convertToDouble = value;
axisControl.ConvertToDouble = value;
}
}
}
/// <summary>
/// Sets conversions of axis - functions used to convert values of axis type to and from double values of viewport.
/// Sets both ConvertToDouble and ConvertFromDouble properties.
/// </summary>
/// <param name="min">The minimal viewport value.</param>
/// <param name="minValue">The value of axis type, corresponding to minimal viewport value.</param>
/// <param name="max">The maximal viewport value.</param>
/// <param name="maxValue">The value of axis type, corresponding to maximal viewport value.</param>
public virtual void SetConversion(double min, T minValue, double max, T maxValue)
{
throw new NotImplementedException();
}
private Range<T> CreateRangeFromRect(DataRect visible)
{
T min, max;
Range<T> range;
switch (Placement)
{
case AxisPlacement.Left:
case AxisPlacement.Right:
min = ConvertFromDouble(visible.YMin);
max = ConvertFromDouble(visible.YMax);
break;
case AxisPlacement.Top:
case AxisPlacement.Bottom:
min = ConvertFromDouble(visible.XMin);
max = ConvertFromDouble(visible.XMax);
break;
default:
throw new NotSupportedException();
}
TrySort(ref min, ref max);
range = new Range<T>(min, max);
return range;
}
private static void TrySort<TS>(ref TS min, ref TS max)
{
if (min is IComparable)
{
IComparable c1 = (IComparable)min;
// if min > max
if (c1.CompareTo(max) > 0)
{
TS temp = min;
min = max;
max = temp;
}
}
}
protected override void OnPlacementChanged(AxisPlacement oldPlacement, AxisPlacement newPlacement)
{
axisControl.Placement = Placement;
if (ParentPlotter != null)
{
Panel panel = GetPanelByPlacement(oldPlacement);
panel.Children.Remove(this);
Panel newPanel = GetPanelByPlacement(newPlacement);
int index = GetInsertionIndexByPlacement(newPlacement, newPanel);
newPanel.Children.Insert(index, this);
}
}
protected override void OnPlotterDetaching(Plotter2D plotter)
{
if (plotter == null)
return;
Panel panel = GetPanelByPlacement(Placement);
if (panel != null)
{
panel.Children.Remove(this);
}
plotter.Viewport.PropertyChanged -= OnViewportPropertyChanged;
axisControl.Transform = null;
}
}
}