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 { /// /// 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. /// /// Type of each tick's value public abstract class AxisBase : GeneralAxis, ITypedAxis, IValueConversion { /// /// Initializes a new instance of the class. /// /// The axis control. /// The convert from double. /// The convert to double. protected AxisBase(AxisControl axisControl, Func convertFromDouble, Func 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(); } /// /// 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. /// /// /// true if this instance is default axis; otherwise, false. /// 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(); } /// /// Gets the screen coordinates of axis ticks. /// /// The screen ticks. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [EditorBrowsable(EditorBrowsableState.Never)] public override double[] ScreenTicks { get { return axisControl.ScreenTicks; } } /// /// Gets the screen coordinates of minor ticks. /// /// The minor screen ticks. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [EditorBrowsable(EditorBrowsableState.Never)] public override MinorTickInfo[] MinorScreenTicks { get { return axisControl.MinorScreenTicks; } } private AxisControl axisControl; /// /// Gets the axis control - actual UI representation of axis. /// /// The axis control. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public AxisControl AxisControl { get { return axisControl; } } /// /// Gets or sets the ticks provider, which is used to generate ticks in given range. /// /// The ticks provider. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ITicksProvider TicksProvider { get { return axisControl.TicksProvider; } set { axisControl.TicksProvider = value; } } /// /// Gets or sets the label provider, that is used to create UI look of axis ticks. /// /// Should not be null. /// /// The label provider. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [NotNull] public LabelProviderBase LabelProvider { get { return axisControl.LabelProvider; } set { axisControl.LabelProvider = value; } } /// /// Gets or sets the major label provider, which creates labels for major ticks. /// If null, major labels will not be shown. /// /// The major label provider. public LabelProviderBase MajorLabelProvider { get { return axisControl.MajorLabelProvider; } set { axisControl.MajorLabelProvider = value; } } /// /// 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. /// /// The label string format. public string LabelStringFormat { get { return LabelProvider.LabelStringFormat; } set { LabelProvider.LabelStringFormat = value; } } /// /// Gets or sets a value indicating whether to show minor ticks. /// /// true if show minor ticks; otherwise, false. public bool ShowMinorTicks { get { return axisControl.DrawMinorTicks; } set { axisControl.DrawMinorTicks = value; } } /// /// Gets or sets a value indicating whether to show major labels. /// /// true if show major labels; otherwise, false. 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 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 convertFromDouble; /// /// 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. /// /// The convert from double. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [NotNull] public Func ConvertFromDouble { get { return convertFromDouble; } set { if (value == null) throw new ArgumentNullException("value"); if (convertFromDouble != value) { convertFromDouble = value; if (ParentPlotter != null) { UpdateAxisControl(ParentPlotter); } } } } private Func convertToDouble; /// /// 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. /// /// The convert to double. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] [NotNull] public Func ConvertToDouble { get { return convertToDouble; } set { if (value == null) throw new ArgumentNullException("value"); if (convertToDouble != value) { convertToDouble = value; axisControl.ConvertToDouble = value; } } } /// /// 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. /// /// The minimal viewport value. /// The value of axis type, corresponding to minimal viewport value. /// The maximal viewport value. /// The value of axis type, corresponding to maximal viewport value. public virtual void SetConversion(double min, T minValue, double max, T maxValue) { throw new NotImplementedException(); } private Range CreateRangeFromRect(DataRect visible) { T min, max; Range 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(min, max); return range; } private static void TrySort(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; } } }