[12503] | 1 | using System;
|
---|
| 2 | using System.Collections.Generic;
|
---|
| 3 | using System.Linq;
|
---|
| 4 | using System.Text;
|
---|
| 5 | using System.Windows;
|
---|
| 6 | using System.Windows.Controls;
|
---|
| 7 | using System.Windows.Media;
|
---|
| 8 | using System.Windows.Data;
|
---|
| 9 | using System.Diagnostics;
|
---|
| 10 | using System.ComponentModel;
|
---|
| 11 | using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
---|
| 12 | using Microsoft.Research.DynamicDataDisplay.Common;
|
---|
| 13 | using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
---|
| 14 | using System.Windows.Threading;
|
---|
| 15 |
|
---|
| 16 | namespace Microsoft.Research.DynamicDataDisplay.Charts
|
---|
| 17 | {
|
---|
| 18 | /// <summary>
|
---|
| 19 | /// Represents a base class for all axes in ChartPlotter.
|
---|
| 20 | /// Contains a real UI representation of axis - AxisControl, and means to adjust number of ticks, algorythms of their generating and
|
---|
| 21 | /// look of ticks' labels.
|
---|
| 22 | /// </summary>
|
---|
| 23 | /// <typeparam name="T">Type of each tick's value</typeparam>
|
---|
| 24 | public abstract class AxisBase<T> : GeneralAxis, ITypedAxis<T>, IValueConversion<T>
|
---|
| 25 | {
|
---|
| 26 | /// <summary>
|
---|
| 27 | /// Initializes a new instance of the <see cref="AxisBase<T>"/> class.
|
---|
| 28 | /// </summary>
|
---|
| 29 | /// <param name="axisControl">The axis control.</param>
|
---|
| 30 | /// <param name="convertFromDouble">The convert from double.</param>
|
---|
| 31 | /// <param name="convertToDouble">The convert to double.</param>
|
---|
| 32 | protected AxisBase(AxisControl<T> axisControl, Func<double, T> convertFromDouble, Func<T, double> convertToDouble)
|
---|
| 33 | {
|
---|
| 34 | if (axisControl == null)
|
---|
| 35 | throw new ArgumentNullException("axisControl");
|
---|
| 36 | if (convertFromDouble == null)
|
---|
| 37 | throw new ArgumentNullException("convertFromDouble");
|
---|
| 38 | if (convertToDouble == null)
|
---|
| 39 | throw new ArgumentNullException("convertToDouble");
|
---|
| 40 |
|
---|
| 41 | this.convertToDouble = convertToDouble;
|
---|
| 42 | this.convertFromDouble = convertFromDouble;
|
---|
| 43 |
|
---|
| 44 | this.axisControl = axisControl;
|
---|
| 45 | axisControl.MakeDependent();
|
---|
| 46 | axisControl.ConvertToDouble = convertToDouble;
|
---|
| 47 | axisControl.ScreenTicksChanged += axisControl_ScreenTicksChanged;
|
---|
| 48 |
|
---|
| 49 | Content = axisControl;
|
---|
| 50 | axisControl.SetBinding(Control.BackgroundProperty, new Binding("Background") { Source = this });
|
---|
| 51 |
|
---|
| 52 | Focusable = false;
|
---|
| 53 |
|
---|
| 54 | Loaded += OnLoaded;
|
---|
| 55 | }
|
---|
| 56 |
|
---|
| 57 | public override void ForceUpdate()
|
---|
| 58 | {
|
---|
| 59 | axisControl.UpdateUI();
|
---|
| 60 | }
|
---|
| 61 |
|
---|
| 62 | private void axisControl_ScreenTicksChanged(object sender, EventArgs e)
|
---|
| 63 | {
|
---|
| 64 | RaiseTicksChanged();
|
---|
| 65 | }
|
---|
| 66 |
|
---|
| 67 | /// <summary>
|
---|
| 68 | /// Gets or sets a value indicating whether this axis is default axis.
|
---|
| 69 | /// ChartPlotter's AxisGrid gets axis ticks to display from two default axes - horizontal and vertical.
|
---|
| 70 | /// </summary>
|
---|
| 71 | /// <value>
|
---|
| 72 | /// <c>true</c> if this instance is default axis; otherwise, <c>false</c>.
|
---|
| 73 | /// </value>
|
---|
| 74 | public bool IsDefaultAxis
|
---|
| 75 | {
|
---|
| 76 | get { return Microsoft.Research.DynamicDataDisplay.Plotter.GetIsDefaultAxis(this); }
|
---|
| 77 | set { Microsoft.Research.DynamicDataDisplay.Plotter.SetIsDefaultAxis(this, value); }
|
---|
| 78 | }
|
---|
| 79 |
|
---|
| 80 | private void OnLoaded(object sender, RoutedEventArgs e)
|
---|
| 81 | {
|
---|
| 82 | RaiseTicksChanged();
|
---|
| 83 | }
|
---|
| 84 |
|
---|
| 85 | /// <summary>
|
---|
| 86 | /// Gets or sets a value indicating whether to use smooth panning, when axis ticks are not being repainted after each chart panning.
|
---|
| 87 | /// </summary>
|
---|
| 88 | /// <value><c>true</c> if use smooth panning; otherwise, <c>false</c>.</value>
|
---|
| 89 | public override bool UseSmoothPanning
|
---|
| 90 | {
|
---|
| 91 | get { return axisControl.UseSmoothPanning; }
|
---|
| 92 | set { axisControl.UseSmoothPanning = value; }
|
---|
| 93 | }
|
---|
| 94 |
|
---|
| 95 | /// <summary>
|
---|
| 96 | /// Gets the screen coordinates of axis ticks.
|
---|
| 97 | /// </summary>
|
---|
| 98 | /// <value>The screen ticks.</value>
|
---|
| 99 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
---|
| 100 | [EditorBrowsable(EditorBrowsableState.Never)]
|
---|
| 101 | public override double[] ScreenTicks
|
---|
| 102 | {
|
---|
| 103 | get { return axisControl.ScreenTicks; }
|
---|
| 104 | }
|
---|
| 105 |
|
---|
| 106 | /// <summary>
|
---|
| 107 | /// Gets the screen coordinates of minor ticks.
|
---|
| 108 | /// </summary>
|
---|
| 109 | /// <value>The minor screen ticks.</value>
|
---|
| 110 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
---|
| 111 | [EditorBrowsable(EditorBrowsableState.Never)]
|
---|
| 112 | public override MinorTickInfo<double>[] MinorScreenTicks
|
---|
| 113 | {
|
---|
| 114 | get { return axisControl.MinorScreenTicks; }
|
---|
| 115 | }
|
---|
| 116 |
|
---|
| 117 | private AxisControl<T> axisControl;
|
---|
| 118 | /// <summary>
|
---|
| 119 | /// Gets the axis control - actual UI representation of axis.
|
---|
| 120 | /// </summary>
|
---|
| 121 | /// <value>The axis control.</value>
|
---|
| 122 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
---|
| 123 | public AxisControl<T> AxisControl
|
---|
| 124 | {
|
---|
| 125 | get { return axisControl; }
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | /// <summary>
|
---|
| 129 | /// Gets or sets the ticks provider, which is used to generate ticks in given range.
|
---|
| 130 | /// </summary>
|
---|
| 131 | /// <value>The ticks provider.</value>
|
---|
| 132 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
---|
| 133 | public ITicksProvider<T> TicksProvider
|
---|
| 134 | {
|
---|
| 135 | get { return axisControl.TicksProvider; }
|
---|
| 136 | set { axisControl.TicksProvider = value; }
|
---|
| 137 | }
|
---|
| 138 |
|
---|
| 139 | /// <summary>
|
---|
| 140 | /// Gets or sets the label provider, that is used to create UI look of axis ticks.
|
---|
| 141 | ///
|
---|
| 142 | /// Should not be null.
|
---|
| 143 | /// </summary>
|
---|
| 144 | /// <value>The label provider.</value>
|
---|
| 145 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
---|
| 146 | [NotNull]
|
---|
| 147 | public LabelProviderBase<T> LabelProvider
|
---|
| 148 | {
|
---|
| 149 | get { return axisControl.LabelProvider; }
|
---|
| 150 | set { axisControl.LabelProvider = value; }
|
---|
| 151 | }
|
---|
| 152 |
|
---|
| 153 | /// <summary>
|
---|
| 154 | /// Gets or sets the major label provider, which creates labels for major ticks.
|
---|
| 155 | /// If null, major labels will not be shown.
|
---|
| 156 | /// </summary>
|
---|
| 157 | /// <value>The major label provider.</value>
|
---|
| 158 | public LabelProviderBase<T> MajorLabelProvider
|
---|
| 159 | {
|
---|
| 160 | get { return axisControl.MajorLabelProvider; }
|
---|
| 161 | set { axisControl.MajorLabelProvider = value; }
|
---|
| 162 | }
|
---|
| 163 |
|
---|
| 164 | /// <summary>
|
---|
| 165 | /// Gets or sets the label string format, used to create simple formats of each tick's label, such as
|
---|
| 166 | /// changing tick label from "1.2" to "$1.2".
|
---|
| 167 | /// Should be in format "*{0}*", where '*' is any number of any chars.
|
---|
| 168 | ///
|
---|
| 169 | /// If value is null, format string will not be used.
|
---|
| 170 | /// </summary>
|
---|
| 171 | /// <value>The label string format.</value>
|
---|
| 172 | public string LabelStringFormat
|
---|
| 173 | {
|
---|
| 174 | get { return LabelProvider.LabelStringFormat; }
|
---|
| 175 | set { LabelProvider.LabelStringFormat = value; }
|
---|
| 176 | }
|
---|
| 177 |
|
---|
| 178 | /// <summary>
|
---|
| 179 | /// Gets or sets a value indicating whether to show minor ticks.
|
---|
| 180 | /// </summary>
|
---|
| 181 | /// <value><c>true</c> if show minor ticks; otherwise, <c>false</c>.</value>
|
---|
| 182 | public bool ShowMinorTicks
|
---|
| 183 | {
|
---|
| 184 | get { return axisControl.DrawMinorTicks; }
|
---|
| 185 | set { axisControl.DrawMinorTicks = value; }
|
---|
| 186 | }
|
---|
| 187 |
|
---|
| 188 | /// <summary>
|
---|
| 189 | /// Gets or sets a value indicating whether to show major labels.
|
---|
| 190 | /// </summary>
|
---|
| 191 | /// <value><c>true</c> if show major labels; otherwise, <c>false</c>.</value>
|
---|
| 192 | public bool ShowMajorLabels
|
---|
| 193 | {
|
---|
| 194 | get { return axisControl.DrawMajorLabels; }
|
---|
| 195 | set { axisControl.DrawMajorLabels = value; }
|
---|
| 196 | }
|
---|
| 197 |
|
---|
| 198 | protected override void OnPlotterAttached(Plotter2D plotter)
|
---|
| 199 | {
|
---|
| 200 | plotter.Viewport.PropertyChanged += OnViewportPropertyChanged;
|
---|
| 201 |
|
---|
| 202 | Panel panel = GetPanelByPlacement(Placement);
|
---|
| 203 | if (panel != null)
|
---|
| 204 | {
|
---|
| 205 | int index = GetInsertionIndexByPlacement(Placement, panel);
|
---|
| 206 | panel.Children.Insert(index, this);
|
---|
| 207 | }
|
---|
| 208 |
|
---|
| 209 | using (axisControl.OpenUpdateRegion(true))
|
---|
| 210 | {
|
---|
| 211 | UpdateAxisControl(plotter);
|
---|
| 212 | }
|
---|
| 213 | }
|
---|
| 214 |
|
---|
| 215 | private void UpdateAxisControl(Plotter2D plotter2d)
|
---|
| 216 | {
|
---|
| 217 | axisControl.Transform = plotter2d.Viewport.Transform;
|
---|
| 218 | axisControl.Range = CreateRangeFromRect(plotter2d.Visible.ViewportToData(plotter2d.Viewport.Transform));
|
---|
| 219 | }
|
---|
| 220 |
|
---|
| 221 | private int GetInsertionIndexByPlacement(AxisPlacement placement, Panel panel)
|
---|
| 222 | {
|
---|
| 223 | int index = panel.Children.Count;
|
---|
| 224 |
|
---|
| 225 | switch (placement)
|
---|
| 226 | {
|
---|
| 227 | case AxisPlacement.Left:
|
---|
| 228 | index = 0;
|
---|
| 229 | break;
|
---|
| 230 | case AxisPlacement.Top:
|
---|
| 231 | index = 0;
|
---|
| 232 | break;
|
---|
| 233 | default:
|
---|
| 234 | break;
|
---|
| 235 | }
|
---|
| 236 |
|
---|
| 237 | return index;
|
---|
| 238 | }
|
---|
| 239 |
|
---|
| 240 | ExtendedPropertyChangedEventArgs visibleChangedEventArgs;
|
---|
| 241 | int viewportPropertyChangedEnters = 0;
|
---|
| 242 | DataRect prevDataRect = DataRect.Empty;
|
---|
| 243 | private void OnViewportPropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
|
---|
| 244 | {
|
---|
| 245 | //if (viewportPropertyChangedEnters > 4)
|
---|
| 246 | //{
|
---|
| 247 | // if (e.PropertyName == "Visible")
|
---|
| 248 | // {
|
---|
| 249 | // visibleChangedEventArgs = e;
|
---|
| 250 | // }
|
---|
| 251 | // return;
|
---|
| 252 | //}
|
---|
| 253 |
|
---|
| 254 | viewportPropertyChangedEnters++;
|
---|
| 255 |
|
---|
| 256 | Viewport2D viewport = (Viewport2D)sender;
|
---|
| 257 |
|
---|
| 258 | DataRect visible = viewport.Visible;
|
---|
| 259 |
|
---|
| 260 | DataRect dataRect = visible.ViewportToData(viewport.Transform);
|
---|
| 261 | bool forceUpdate = dataRect != prevDataRect;
|
---|
| 262 | prevDataRect = dataRect;
|
---|
| 263 |
|
---|
| 264 | Range<T> range = CreateRangeFromRect(dataRect);
|
---|
| 265 |
|
---|
| 266 | using (axisControl.OpenUpdateRegion(false)) // todo was forceUpdate
|
---|
| 267 | {
|
---|
| 268 | axisControl.Range = range;
|
---|
| 269 | axisControl.Transform = viewport.Transform;
|
---|
| 270 | }
|
---|
| 271 |
|
---|
| 272 | //OnViewportPropertyChanged(Plotter.Viewport, visibleChangedEventArgs);
|
---|
| 273 |
|
---|
| 274 | //Dispatcher.BeginInvoke(() =>
|
---|
| 275 | //{
|
---|
| 276 | // viewportPropertyChangedEnters--;
|
---|
| 277 | // if (visibleChangedEventArgs != null)
|
---|
| 278 | // {
|
---|
| 279 | // OnViewportPropertyChanged(Plotter.Viewport, visibleChangedEventArgs);
|
---|
| 280 | // }
|
---|
| 281 | // visibleChangedEventArgs = null;
|
---|
| 282 | //}, DispatcherPriority.Render);
|
---|
| 283 | }
|
---|
| 284 |
|
---|
| 285 | private Func<double, T> convertFromDouble;
|
---|
| 286 | /// <summary>
|
---|
| 287 | /// Gets or sets the delegate that is used to create each tick from double.
|
---|
| 288 | /// Is used to create typed range to display for internal AxisControl.
|
---|
| 289 | /// If changed, ConvertToDouble should be changed appropriately, too.
|
---|
| 290 | /// Should not be null.
|
---|
| 291 | /// </summary>
|
---|
| 292 | /// <value>The convert from double.</value>
|
---|
| 293 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
---|
| 294 | [NotNull]
|
---|
| 295 | public Func<double, T> ConvertFromDouble
|
---|
| 296 | {
|
---|
| 297 | get { return convertFromDouble; }
|
---|
| 298 | set
|
---|
| 299 | {
|
---|
| 300 | if (value == null)
|
---|
| 301 | throw new ArgumentNullException("value");
|
---|
| 302 |
|
---|
| 303 | if (convertFromDouble != value)
|
---|
| 304 | {
|
---|
| 305 | convertFromDouble = value;
|
---|
| 306 | if (ParentPlotter != null)
|
---|
| 307 | {
|
---|
| 308 | UpdateAxisControl(ParentPlotter);
|
---|
| 309 | }
|
---|
| 310 | }
|
---|
| 311 | }
|
---|
| 312 | }
|
---|
| 313 |
|
---|
| 314 | private Func<T, double> convertToDouble;
|
---|
| 315 | /// <summary>
|
---|
| 316 | /// Gets or sets the delegate that is used to convert each tick to double.
|
---|
| 317 | /// Is used by internal AxisControl to convert tick to double to get tick's coordinates inside of viewport.
|
---|
| 318 | /// If changed, ConvertFromDouble should be changed appropriately, too.
|
---|
| 319 | /// Should not be null.
|
---|
| 320 | /// </summary>
|
---|
| 321 | /// <value>The convert to double.</value>
|
---|
| 322 | [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
---|
| 323 | [NotNull]
|
---|
| 324 | public Func<T, double> ConvertToDouble
|
---|
| 325 | {
|
---|
| 326 | get { return convertToDouble; }
|
---|
| 327 | set
|
---|
| 328 | {
|
---|
| 329 | if (value == null)
|
---|
| 330 | throw new ArgumentNullException("value");
|
---|
| 331 |
|
---|
| 332 | if (convertToDouble != value)
|
---|
| 333 | {
|
---|
| 334 | convertToDouble = value;
|
---|
| 335 | axisControl.ConvertToDouble = value;
|
---|
| 336 | }
|
---|
| 337 | }
|
---|
| 338 | }
|
---|
| 339 |
|
---|
| 340 | /// <summary>
|
---|
| 341 | /// Sets conversions of axis - functions used to convert values of axis type to and from double values of viewport.
|
---|
| 342 | /// Sets both ConvertToDouble and ConvertFromDouble properties.
|
---|
| 343 | /// </summary>
|
---|
| 344 | /// <param name="min">The minimal viewport value.</param>
|
---|
| 345 | /// <param name="minValue">The value of axis type, corresponding to minimal viewport value.</param>
|
---|
| 346 | /// <param name="max">The maximal viewport value.</param>
|
---|
| 347 | /// <param name="maxValue">The value of axis type, corresponding to maximal viewport value.</param>
|
---|
| 348 | public virtual void SetConversion(double min, T minValue, double max, T maxValue)
|
---|
| 349 | {
|
---|
| 350 | throw new NotImplementedException();
|
---|
| 351 | }
|
---|
| 352 |
|
---|
| 353 | private Range<T> CreateRangeFromRect(DataRect visible)
|
---|
| 354 | {
|
---|
| 355 | T min, max;
|
---|
| 356 |
|
---|
| 357 | Range<T> range;
|
---|
| 358 | switch (Placement)
|
---|
| 359 | {
|
---|
| 360 | case AxisPlacement.Left:
|
---|
| 361 | case AxisPlacement.Right:
|
---|
| 362 | min = ConvertFromDouble(visible.YMin);
|
---|
| 363 | max = ConvertFromDouble(visible.YMax);
|
---|
| 364 | break;
|
---|
| 365 | case AxisPlacement.Top:
|
---|
| 366 | case AxisPlacement.Bottom:
|
---|
| 367 | min = ConvertFromDouble(visible.XMin);
|
---|
| 368 | max = ConvertFromDouble(visible.XMax);
|
---|
| 369 | break;
|
---|
| 370 | default:
|
---|
| 371 | throw new NotSupportedException();
|
---|
| 372 | }
|
---|
| 373 |
|
---|
| 374 | TrySort(ref min, ref max);
|
---|
| 375 | range = new Range<T>(min, max);
|
---|
| 376 | return range;
|
---|
| 377 | }
|
---|
| 378 |
|
---|
| 379 | private static void TrySort<TS>(ref TS min, ref TS max)
|
---|
| 380 | {
|
---|
| 381 | if (min is IComparable)
|
---|
| 382 | {
|
---|
| 383 | IComparable c1 = (IComparable)min;
|
---|
| 384 | // if min > max
|
---|
| 385 | if (c1.CompareTo(max) > 0)
|
---|
| 386 | {
|
---|
| 387 | TS temp = min;
|
---|
| 388 | min = max;
|
---|
| 389 | max = temp;
|
---|
| 390 | }
|
---|
| 391 | }
|
---|
| 392 | }
|
---|
| 393 |
|
---|
| 394 | protected override void OnPlacementChanged(AxisPlacement oldPlacement, AxisPlacement newPlacement)
|
---|
| 395 | {
|
---|
| 396 | axisControl.Placement = Placement;
|
---|
| 397 | if (ParentPlotter != null)
|
---|
| 398 | {
|
---|
| 399 | Panel panel = GetPanelByPlacement(oldPlacement);
|
---|
| 400 | panel.Children.Remove(this);
|
---|
| 401 |
|
---|
| 402 | Panel newPanel = GetPanelByPlacement(newPlacement);
|
---|
| 403 | int index = GetInsertionIndexByPlacement(newPlacement, newPanel);
|
---|
| 404 | newPanel.Children.Insert(index, this);
|
---|
| 405 | }
|
---|
| 406 | }
|
---|
| 407 |
|
---|
| 408 | protected override void OnPlotterDetaching(Plotter2D plotter)
|
---|
| 409 | {
|
---|
| 410 | if (plotter == null)
|
---|
| 411 | return;
|
---|
| 412 |
|
---|
| 413 | Panel panel = GetPanelByPlacement(Placement);
|
---|
| 414 | if (panel != null)
|
---|
| 415 | {
|
---|
| 416 | panel.Children.Remove(this);
|
---|
| 417 | }
|
---|
| 418 |
|
---|
| 419 | plotter.Viewport.PropertyChanged -= OnViewportPropertyChanged;
|
---|
| 420 | axisControl.Transform = null;
|
---|
| 421 | }
|
---|
| 422 | }
|
---|
| 423 | }
|
---|