412 lines
12 KiB
C#
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<T>"/> 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;
|
|
}
|
|
}
|
|
}
|