Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Problems.GrammaticalOptimization/DynamicDataDisplay/Charts/Axes/AxisControl.cs @ 12503

Last change on this file since 12503 was 12503, checked in by aballeit, 9 years ago

#2283 added GUI and charts; fixed MCTS

File size: 35.8 KB
Line 
1using System;
2using System.Diagnostics;
3using System.Linq;
4using System.Windows;
5using System.Windows.Controls;
6using System.Windows.Media;
7using System.Windows.Shapes;
8using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
9using System.Collections.Generic;
10using System.ComponentModel;
11using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
12using System.Windows.Data;
13using Microsoft.Research.DynamicDataDisplay.Common;
14using System.Windows.Threading;
15
16namespace Microsoft.Research.DynamicDataDisplay.Charts
17{
18  /// <summary>
19  /// Defines a base class for axis UI representation.
20  /// Contains a number of properties that can be used to adjust ticks set and their look.
21  /// </summary>
22  /// <typeparam name="T"></typeparam>
23  [TemplatePart(Name = "PART_AdditionalLabelsCanvas", Type = typeof(StackCanvas))]
24  [TemplatePart(Name = "PART_CommonLabelsCanvas", Type = typeof(StackCanvas))]
25  [TemplatePart(Name = "PART_TicksPath", Type = typeof(Path))]
26  [TemplatePart(Name = "PART_ContentsGrid", Type = typeof(Panel))]
27  public abstract class AxisControl<T> : AxisControlBase
28  {
29    private const string templateKey = "axisControlTemplate";
30    private const string smoothTemplateKey = "smoothAxisControlTemplate";
31    private const string additionalLabelTransformKey = "additionalLabelsTransform";
32
33    private const string PART_AdditionalLabelsCanvas = "PART_AdditionalLabelsCanvas";
34    private const string PART_CommonLabelsCanvas = "PART_CommonLabelsCanvas";
35    private const string PART_TicksPath = "PART_TicksPath";
36    private const string PART_ContentsGrid = "PART_ContentsGrid";
37
38    /// <summary>
39    /// Initializes a new instance of the <see cref="AxisControl&lt;T&gt;"/> class.
40    /// </summary>
41    protected AxisControl()
42    {
43      HorizontalContentAlignment = HorizontalAlignment.Stretch;
44      VerticalContentAlignment = VerticalAlignment.Stretch;
45
46      Background = Brushes.Transparent;
47      Focusable = false;
48
49      UpdateUIResources();
50      UpdateSizeGetters();
51    }
52
53    internal void MakeDependent()
54    {
55      independent = false;
56    }
57
58    /// <summary>
59    /// This conversion is performed to make horizontal one-string and two-string labels
60    /// stay at one height.
61    /// </summary>
62    /// <param name="placement"></param>
63    /// <returns></returns>
64    private static AxisPlacement GetBetterPlacement(AxisPlacement placement)
65    {
66      switch (placement)
67      {
68        case AxisPlacement.Left:
69          return AxisPlacement.Left;
70        case AxisPlacement.Right:
71          return AxisPlacement.Right;
72        case AxisPlacement.Top:
73          return AxisPlacement.Top;
74        case AxisPlacement.Bottom:
75          return AxisPlacement.Bottom;
76        default:
77          throw new NotSupportedException();
78      }
79    }
80
81    #region Properties
82
83    private AxisPlacement placement = AxisPlacement.Bottom;
84    /// <summary>
85    /// Gets or sets the placement of axis control.
86    /// Relative positioning of parts of axis depends on this value.
87    /// </summary>
88    /// <value>The placement.</value>
89    public AxisPlacement Placement
90    {
91      get { return placement; }
92      set
93      {
94        if (placement != value)
95        {
96          placement = value;
97          UpdateUIResources();
98          UpdateSizeGetters();
99        }
100      }
101    }
102
103    private void UpdateSizeGetters()
104    {
105      switch (placement)
106      {
107        case AxisPlacement.Left:
108        case AxisPlacement.Right:
109          getSize = size => size.Height;
110          getCoordinate = p => p.Y;
111          createScreenPoint1 = d => new Point(scrCoord1, d);
112          createScreenPoint2 = (d, size) => new Point(scrCoord2 * size, d);
113          break;
114        case AxisPlacement.Top:
115        case AxisPlacement.Bottom:
116          getSize = size => size.Width;
117          getCoordinate = p => p.X;
118          createScreenPoint1 = d => new Point(d, scrCoord1);
119          createScreenPoint2 = (d, size) => new Point(d, scrCoord2 * size);
120          break;
121        default:
122          break;
123      }
124
125      switch (placement)
126      {
127        case AxisPlacement.Left:
128          createDataPoint = d => new Point(0, d);
129          break;
130        case AxisPlacement.Right:
131          createDataPoint = d => new Point(1, d);
132          break;
133        case AxisPlacement.Top:
134          createDataPoint = d => new Point(d, 1);
135          break;
136        case AxisPlacement.Bottom:
137          createDataPoint = d => new Point(d, 0);
138          break;
139        default:
140          break;
141      }
142    }
143
144    private void UpdateUIResources()
145    {
146      ResourceDictionary resources = new ResourceDictionary
147      {
148        Source = new Uri("/DynamicDataDisplay;component/Charts/Axes/AxisControlStyle.xaml", UriKind.Relative)
149      };
150
151      string templateKey;
152      if (useSmoothPanning)
153        templateKey = smoothTemplateKey;
154      else
155        templateKey = AxisControl<T>.templateKey;
156
157      AxisPlacement placement = GetBetterPlacement(this.placement);
158      ControlTemplate template = (ControlTemplate)resources[templateKey + placement.ToString()];
159
160      Verify.AssertNotNull(template);
161      var content = (FrameworkElement)template.LoadContent();
162
163      if (ticksPath != null && ticksPath.Data != null)
164      {
165        GeometryGroup group = (GeometryGroup)ticksPath.Data;
166        foreach (var child in group.Children)
167        {
168          LineGeometry geometry = (LineGeometry)child;
169          lineGeomPool.Put(geometry);
170        }
171        group.Children.Clear();
172      }
173
174      ticksPath = (Path)content.FindName(PART_TicksPath);
175      ticksPath.SnapsToDevicePixels = true;
176      Verify.AssertNotNull(ticksPath);
177
178      // as this method can be called not only on loading of axisControl, but when its placement changes, internal panels
179      // can be not empty and their contents should be released
180      if (commonLabelsCanvas != null && labelProvider != null)
181      {
182        foreach (UIElement child in commonLabelsCanvas.Children)
183        {
184          if (child != null)
185          {
186            labelProvider.ReleaseLabel(child);
187          }
188        }
189
190        labels = null;
191        commonLabelsCanvas.Children.Clear();
192      }
193
194      commonLabelsCanvas = (StackCanvas)content.FindName(PART_CommonLabelsCanvas);
195      Verify.AssertNotNull(commonLabelsCanvas);
196      commonLabelsCanvas.Placement = placement;
197
198      if (additionalLabelsCanvas != null && majorLabelProvider != null)
199      {
200        foreach (UIElement child in additionalLabelsCanvas.Children)
201        {
202          if (child != null)
203          {
204            majorLabelProvider.ReleaseLabel(child);
205          }
206        }
207      }
208
209      additionalLabelsCanvas = (StackCanvas)content.FindName(PART_AdditionalLabelsCanvas);
210      Verify.AssertNotNull(additionalLabelsCanvas);
211      additionalLabelsCanvas.Placement = placement;
212
213      mainGrid = (Panel)content.FindName(PART_ContentsGrid);
214      Verify.AssertNotNull(mainGrid);
215
216      mainGrid.SetBinding(Control.BackgroundProperty, new Binding { Path = new PropertyPath("Background"), Source = this });
217      mainGrid.SizeChanged += new SizeChangedEventHandler(mainGrid_SizeChanged);
218
219      Content = mainGrid;
220
221      string transformKey = additionalLabelTransformKey + placement.ToString();
222      if (resources.Contains(transformKey))
223      {
224        additionalLabelTransform = (Transform)resources[transformKey];
225      }
226
227      UpdateUI();
228    }
229
230    private void mainGrid_SizeChanged(object sender, SizeChangedEventArgs e)
231    {
232      if (placement.IsBottomOrTop() && e.WidthChanged ||
233         e.HeightChanged)
234      {
235        // this is performed because if not, whole axisControl's size was measured wrongly.
236        InvalidateMeasure();
237        UpdateUI();
238      }
239    }
240
241    private bool updateOnCommonChange = true;
242
243    internal IDisposable OpenUpdateRegion(bool forceUpdate)
244    {
245      return new UpdateRegionHolder<T>(this, forceUpdate);
246    }
247
248    private sealed class UpdateRegionHolder<TT> : IDisposable
249    {
250      private Range<TT> prevRange;
251      private CoordinateTransform prevTransform;
252      private AxisControl<TT> owner;
253      private bool forceUpdate = false;
254
255      public UpdateRegionHolder(AxisControl<TT> owner) : this(owner, false) { }
256
257      public UpdateRegionHolder(AxisControl<TT> owner, bool forceUpdate)
258      {
259        this.owner = owner;
260        owner.updateOnCommonChange = false;
261
262        prevTransform = owner.transform;
263        prevRange = owner.range;
264        this.forceUpdate = forceUpdate;
265      }
266
267      #region IDisposable Members
268
269      public void Dispose()
270      {
271        owner.updateOnCommonChange = true;
272
273        bool shouldUpdate = owner.range != prevRange;
274
275        var screenRect = owner.Transform.ScreenRect;
276        var prevScreenRect = prevTransform.ScreenRect;
277        if (owner.placement.IsBottomOrTop())
278        {
279          shouldUpdate |= prevScreenRect.Width != screenRect.Width;
280        }
281        else
282        {
283          shouldUpdate |= prevScreenRect.Height != screenRect.Height;
284        }
285
286        shouldUpdate |= owner.transform.DataTransform != prevTransform.DataTransform;
287        shouldUpdate |= forceUpdate;
288
289        if (shouldUpdate)
290        {
291          owner.UpdateUI();
292        }
293        owner = null;
294      }
295
296      #endregion
297    }
298
299    private Range<T> range;
300    /// <summary>
301    /// Gets or sets the range, which ticks are generated for.
302    /// </summary>
303    /// <value>The range.</value>
304    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
305    public Range<T> Range
306    {
307      get { return range; }
308      set
309      {
310        range = value;
311        if (updateOnCommonChange)
312        {
313          UpdateUI();
314        }
315      }
316    }
317
318    private bool drawMinorTicks = true;
319    /// <summary>
320    /// Gets or sets a value indicating whether to show minor ticks.
321    /// </summary>
322    /// <value><c>true</c> if show minor ticks; otherwise, <c>false</c>.</value>
323    public bool DrawMinorTicks
324    {
325      get { return drawMinorTicks; }
326      set
327      {
328        if (drawMinorTicks != value)
329        {
330          drawMinorTicks = value;
331          UpdateUI();
332        }
333      }
334    }
335
336    private bool drawMajorLabels = true;
337    /// <summary>
338    /// Gets or sets a value indicating whether to show major labels.
339    /// </summary>
340    /// <value><c>true</c> if show major labels; otherwise, <c>false</c>.</value>
341    public bool DrawMajorLabels
342    {
343      get { return drawMajorLabels; }
344      set
345      {
346        if (drawMajorLabels != value)
347        {
348          drawMajorLabels = value;
349          UpdateUI();
350        }
351      }
352    }
353
354    private bool drawTicks = true;
355    public bool DrawTicks
356    {
357      get { return drawTicks; }
358      set
359      {
360        if (drawTicks != value)
361        {
362          drawTicks = value;
363          UpdateUI();
364        }
365      }
366    }
367
368    #region TicksProvider
369
370    private ITicksProvider<T> ticksProvider;
371    /// <summary>
372    /// Gets or sets the ticks provider - generator of ticks for given range.
373    ///
374    /// Should not be null.
375    /// </summary>
376    /// <value>The ticks provider.</value>
377    public ITicksProvider<T> TicksProvider
378    {
379      get { return ticksProvider; }
380      set
381      {
382        if (value == null)
383          throw new ArgumentNullException("value");
384
385        if (ticksProvider != value)
386        {
387          DetachTicksProvider();
388
389          ticksProvider = value;
390
391          AttachTicksProvider();
392
393          UpdateUI();
394        }
395      }
396    }
397
398    private void AttachTicksProvider()
399    {
400      if (ticksProvider != null)
401      {
402        ticksProvider.Changed += ticksProvider_Changed;
403      }
404    }
405
406    private void ticksProvider_Changed(object sender, EventArgs e)
407    {
408      UpdateUI();
409    }
410
411    private void DetachTicksProvider()
412    {
413      if (ticksProvider != null)
414      {
415        ticksProvider.Changed -= ticksProvider_Changed;
416      }
417    }
418
419    #endregion
420
421    [EditorBrowsable(EditorBrowsableState.Never)]
422    public override bool ShouldSerializeContent()
423    {
424      return false;
425    }
426
427    protected override bool ShouldSerializeProperty(DependencyProperty dp)
428    {
429      // do not serialize template - for XAML serialization
430      if (dp == TemplateProperty) return false;
431
432      return base.ShouldSerializeProperty(dp);
433    }
434
435    #region MajorLabelProvider
436
437    private LabelProviderBase<T> majorLabelProvider;
438    /// <summary>
439    /// Gets or sets the major label provider, which creates labels for major ticks.
440    /// If null, major labels will not be shown.
441    /// </summary>
442    /// <value>The major label provider.</value>
443    public LabelProviderBase<T> MajorLabelProvider
444    {
445      get { return majorLabelProvider; }
446      set
447      {
448        if (majorLabelProvider != value)
449        {
450          DetachMajorLabelProvider();
451
452          majorLabelProvider = value;
453
454          AttachMajorLabelProvider();
455
456          UpdateUI();
457        }
458      }
459    }
460
461    private void AttachMajorLabelProvider()
462    {
463      if (majorLabelProvider != null)
464      {
465        majorLabelProvider.Changed += majorLabelProvider_Changed;
466      }
467    }
468
469    private void majorLabelProvider_Changed(object sender, EventArgs e)
470    {
471      UpdateUI();
472    }
473
474    private void DetachMajorLabelProvider()
475    {
476      if (majorLabelProvider != null)
477      {
478        majorLabelProvider.Changed -= majorLabelProvider_Changed;
479      }
480    }
481
482    #endregion
483
484    #region LabelProvider
485
486    private LabelProviderBase<T> labelProvider;
487    /// <summary>
488    /// Gets or sets the label provider, which generates labels for axis ticks.
489    /// Should not be null.
490    /// </summary>
491    /// <value>The label provider.</value>
492    [NotNull]
493    public LabelProviderBase<T> LabelProvider
494    {
495      get { return labelProvider; }
496      set
497      {
498        if (value == null)
499          throw new ArgumentNullException("value");
500
501        if (labelProvider != value)
502        {
503          DetachLabelProvider();
504
505          labelProvider = value;
506
507          AttachLabelProvider();
508
509          UpdateUI();
510        }
511      }
512    }
513
514    private void AttachLabelProvider()
515    {
516      if (labelProvider != null)
517      {
518        labelProvider.Changed += labelProvider_Changed;
519      }
520    }
521
522    private void labelProvider_Changed(object sender, EventArgs e)
523    {
524      UpdateUI();
525    }
526
527    private void DetachLabelProvider()
528    {
529      if (labelProvider != null)
530      {
531        labelProvider.Changed -= labelProvider_Changed;
532      }
533    }
534
535    #endregion
536
537    private CoordinateTransform transform = CoordinateTransform.CreateDefault();
538    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
539    [EditorBrowsable(EditorBrowsableState.Never)]
540    public CoordinateTransform Transform
541    {
542      get { return transform; }
543      set
544      {
545        transform = value;
546        if (updateOnCommonChange)
547        {
548          UpdateUI();
549        }
550      }
551    }
552
553    #endregion
554
555    private const double defaultSmallerSize = 1;
556    private const double defaultBiggerSize = 150;
557    protected override Size MeasureOverride(Size constraint)
558    {
559      var baseSize = base.MeasureOverride(constraint);
560
561      mainGrid.Measure(constraint);
562      Size gridSize = mainGrid.DesiredSize;
563      Size result = gridSize;
564
565      bool isHorizontal = placement == AxisPlacement.Bottom || placement == AxisPlacement.Top;
566      if (Double.IsInfinity(constraint.Width) && isHorizontal)
567      {
568        result = new Size(defaultBiggerSize, gridSize.Height != 0 ? gridSize.Height : defaultSmallerSize);
569      }
570      else if (Double.IsInfinity(constraint.Height) && !isHorizontal)
571      {
572        result = new Size(gridSize.Width != 0 ? gridSize.Width : defaultSmallerSize, defaultBiggerSize);
573      }
574
575      return result;
576    }
577
578    //protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo)
579    //{
580    //    base.OnRenderSizeChanged(sizeInfo);
581
582    //    bool isHorizontal = placement == AxisPlacement.Top || placement == AxisPlacement.Bottom;
583    //    if (isHorizontal && sizeInfo.WidthChanged || !isHorizontal && sizeInfo.HeightChanged)
584    //    {
585    //        UpdateUIRepresentation();
586    //    }
587    //}
588
589    private void InitTransform(Size newRenderSize)
590    {
591      Rect dataRect = CreateDataRect();
592
593      transform = transform.WithRects(dataRect, new Rect(newRenderSize));
594    }
595
596    private Rect CreateDataRect()
597    {
598      double min = convertToDouble(range.Min);
599      double max = convertToDouble(range.Max);
600
601      Rect dataRect;
602      switch (placement)
603      {
604        case AxisPlacement.Left:
605        case AxisPlacement.Right:
606          dataRect = new Rect(new Point(min, min), new Point(max, max));
607          break;
608        case AxisPlacement.Top:
609        case AxisPlacement.Bottom:
610          dataRect = new Rect(new Point(min, min), new Point(max, max));
611          break;
612        default:
613          throw new NotSupportedException();
614      }
615      return dataRect;
616    }
617
618    /// <summary>
619    /// Gets the Path with ticks strokes.
620    /// </summary>
621    /// <value>The ticks path.</value>
622    public override Path TicksPath
623    {
624      get { return ticksPath; }
625    }
626
627    private Panel mainGrid;
628    private StackCanvas additionalLabelsCanvas;
629    private StackCanvas commonLabelsCanvas;
630    private Path ticksPath;
631    private bool rendered = false;
632    protected override void OnRender(DrawingContext dc)
633    {
634      base.OnRender(dc);
635
636      if (!rendered)
637      {
638        UpdateUI();
639      }
640      rendered = true;
641    }
642
643    private bool independent = true;
644
645    private double scrCoord1 = 0; // px
646    private double scrCoord2 = 10; // px
647    /// <summary>
648    /// Gets or sets the size of main axis ticks.
649    /// </summary>
650    /// <value>The size of the tick.</value>
651    public double TickSize
652    {
653      get { return scrCoord2; }
654      set
655      {
656        if (scrCoord2 != value)
657        {
658          scrCoord2 = value;
659          UpdateUI();
660        }
661      }
662    }
663
664    private bool useSmoothPanning = false;
665    /// <summary>
666    /// Gets or sets a value indicating whether axis control uses smooth panning.
667    /// </summary>
668    /// <value><c>true</c> if control uses smooth panning; otherwise, <c>false</c>.</value>
669    public bool UseSmoothPanning
670    {
671      get { return useSmoothPanning; }
672      set
673      {
674        useSmoothPanning = value;
675        UpdateUIResources();
676      }
677    }
678
679    double cachedPartLength;
680    double[] originalScreenTicks;
681    private Range<double> axisLongRange;
682    private GeometryGroup geomGroup = new GeometryGroup();
683    internal void UpdateUI()
684    {
685      if (range.IsEmpty)
686        return;
687
688      if (transform == null) return;
689
690      if (independent)
691      {
692        InitTransform(RenderSize);
693      }
694
695      bool isHorizontal = Placement == AxisPlacement.Bottom || Placement == AxisPlacement.Top;
696      if (transform.ScreenRect.Width == 0 && isHorizontal
697        || transform.ScreenRect.Height == 0 && !isHorizontal)
698        return;
699
700      if (!IsMeasureValid)
701      {
702        InvalidateMeasure();
703      }
704
705      Range<double> currentDoubleRange = new Range<double>(convertToDouble(range.Min), convertToDouble(range.Max));
706      bool sameLength = Math.Abs(cachedPartLength - currentDoubleRange.GetLength()) / cachedPartLength < 0.01 || cachedPartLength == 0;
707
708      if (UseSmoothPanning && sameLength)
709      {
710        Debug.WriteLine(Placement + " " + range + " " + axisLongRange);
711        // current range is included into axisLongRange
712        if (currentDoubleRange < axisLongRange)
713        {
714          var axisContent = (FrameworkElement)mainGrid.Children[0];
715          double leftScreen;
716          if (placement.IsBottomOrTop())
717            leftScreen = ((axisLongRange.Min - currentDoubleRange.Min) / currentDoubleRange.GetLength() + 1) * getSize(transform.ScreenRect.Size);
718          else
719            leftScreen = -((axisLongRange.Min - currentDoubleRange.Min) / currentDoubleRange.GetLength() + 1) * getSize(transform.ScreenRect.Size);
720
721          StackCanvas.SetCoordinate(axisContent, leftScreen);
722
723          // this call should be commented
724          // double rightScreen = ((axisLongRange.Max - currentDoubleRange.Max) / currentDoubleRange.GetLength() + 1) * getSize(transform.ScreenRect.Size);
725          // StackCanvas.SetEndCoordinate(axisContent, rightScreen);
726        }
727        else
728        {
729          double length = currentDoubleRange.GetLength();
730          cachedPartLength = length;
731
732          // cached axis part is three times longer
733          double min = currentDoubleRange.Min - length;
734          double max = currentDoubleRange.Max + length;
735          Range<T> widerRange = new Range<T>(convertFromDouble(min), convertFromDouble(max));
736          axisLongRange = new Range<double>(min, max);
737
738          // rebuild entire ticks
739          FillParts(widerRange);
740
741          Debug.WriteLine("не шире " + Placement + " " + widerRange);
742
743          originalScreenTicks = screenTicks.ToArray();
744        }
745
746        // updating screen ticks (for axis grid)
747        // 3 is a ratio of cached area to visible area
748        double shift = (currentDoubleRange.Min - (axisLongRange.Min + axisLongRange.GetLength() / 3)) * getSize(transform.ScreenRect.Size);
749
750        if (!placement.IsBottomOrTop())
751          shift *= -1;
752
753        screenTicks = originalScreenTicks.ToArray();
754        for (int i = 0; i < originalScreenTicks.Length; i++)
755        {
756          screenTicks[i] -= shift;
757        }
758      }
759      else
760      {
761        FillParts(range);
762      }
763
764      ScreenTicksChanged.Raise(this);
765    }
766
767    private void FillParts(Range<T> range)
768    {
769      CreateTicks(range);
770
771      // removing unfinite screen ticks
772      var tempTicks = new List<T>(ticks);
773      var tempScreenTicks = new List<double>(ticks.Length);
774      var tempLabels = new List<UIElement>(labels);
775
776      int i = 0;
777      while (i < tempTicks.Count)
778      {
779        T tick = tempTicks[i];
780        double screenTick = getCoordinate(createDataPoint(convertToDouble(tick)).DataToScreen(transform));
781        if (screenTick.IsFinite())
782        {
783          tempScreenTicks.Add(screenTick);
784          i++;
785        }
786        else
787        {
788          tempTicks.RemoveAt(i);
789          tempLabels.RemoveAt(i);
790        }
791      }
792
793      ticks = tempTicks.ToArray();
794      screenTicks = tempScreenTicks.ToArray();
795      labels = tempLabels.ToArray();
796
797      // saving generated lines into pool
798      for (i = 0; i < geomGroup.Children.Count; i++)
799      {
800        var geometry = (LineGeometry)geomGroup.Children[i];
801        lineGeomPool.Put(geometry);
802      }
803
804      geomGroup = new GeometryGroup();
805      geomGroup.Children = new GeometryCollection(lineGeomPool.Count);
806
807      if (drawTicks)
808        DoDrawTicks(screenTicks, geomGroup.Children);
809
810      if (drawMinorTicks)
811        DoDrawMinorTicks(geomGroup.Children);
812
813      ticksPath.Data = geomGroup;
814
815      DoDrawCommonLabels(screenTicks);
816
817      if (drawMajorLabels)
818        DoDrawMajorLabels();
819    }
820
821    bool drawTicksOnEmptyLabel = false;
822    /// <summary>
823    /// Gets or sets a value indicating whether to draw ticks on empty label.
824    /// </summary>
825    /// <value>
826    ///   <c>true</c> if draw ticks on empty label; otherwise, <c>false</c>.
827    /// </value>
828    public bool DrawTicksOnEmptyLabel
829    {
830      get { return drawTicksOnEmptyLabel; }
831      set
832      {
833        if (drawTicksOnEmptyLabel != value)
834        {
835          drawTicksOnEmptyLabel = value;
836          UpdateUI();
837        }
838      }
839    }
840
841    private readonly ResourcePool<LineGeometry> lineGeomPool = new ResourcePool<LineGeometry>();
842    private void DoDrawTicks(double[] screenTicksX, ICollection<Geometry> lines)
843    {
844      for (int i = 0; i < screenTicksX.Length; i++)
845      {
846        if (labels[i] == null && !drawTicksOnEmptyLabel)
847          continue;
848
849        Point p1 = createScreenPoint1(screenTicksX[i]);
850        Point p2 = createScreenPoint2(screenTicksX[i], 1);
851
852        LineGeometry line = lineGeomPool.GetOrCreate();
853
854        line.StartPoint = p1;
855        line.EndPoint = p2;
856        lines.Add(line);
857      }
858    }
859
860    private double GetRangesRatio(Range<T> nominator, Range<T> denominator)
861    {
862      double nomMin = ConvertToDouble(nominator.Min);
863      double nomMax = ConvertToDouble(nominator.Max);
864      double denMin = ConvertToDouble(denominator.Min);
865      double denMax = ConvertToDouble(denominator.Max);
866
867      return (nomMax - nomMin) / (denMax - denMin);
868    }
869
870    Transform additionalLabelTransform = null;
871    private void DoDrawMajorLabels()
872    {
873      ITicksProvider<T> majorTicksProvider = ticksProvider.MajorProvider;
874      additionalLabelsCanvas.Children.Clear();
875
876      if (majorTicksProvider != null && majorLabelProvider != null)
877      {
878        additionalLabelsCanvas.Visibility = Visibility.Visible;
879
880        Size renderSize = RenderSize;
881        var majorTicks = majorTicksProvider.GetTicks(range, DefaultTicksProvider.DefaultTicksCount);
882
883        double[] screenCoords = majorTicks.Ticks.Select(tick => createDataPoint(convertToDouble(tick))).
884          Select(p => p.DataToScreen(transform)).Select(p => getCoordinate(p)).ToArray();
885
886        // todo this is not the best decision - when displaying, for example,
887        // milliseconds, it causes to create hundreds and thousands of textBlocks.
888        double rangesRatio = GetRangesRatio(majorTicks.Ticks.GetPairs().ToArray()[0], range);
889
890        object info = majorTicks.Info;
891        MajorLabelsInfo newInfo = new MajorLabelsInfo
892        {
893          Info = info,
894          MajorLabelsCount = (int)Math.Ceiling(rangesRatio)
895        };
896
897        var newMajorTicks = new TicksInfo<T>
898        {
899          Info = newInfo,
900          Ticks = majorTicks.Ticks,
901          TickSizes = majorTicks.TickSizes
902        };
903
904        UIElement[] additionalLabels = MajorLabelProvider.CreateLabels(newMajorTicks);
905
906        for (int i = 0; i < additionalLabels.Length; i++)
907        {
908          if (screenCoords[i].IsNaN())
909            continue;
910
911          UIElement tickLabel = additionalLabels[i];
912
913          tickLabel.Measure(renderSize);
914
915          StackCanvas.SetCoordinate(tickLabel, screenCoords[i]);
916          StackCanvas.SetEndCoordinate(tickLabel, screenCoords[i + 1]);
917
918          if (tickLabel is FrameworkElement)
919            ((FrameworkElement)tickLabel).LayoutTransform = additionalLabelTransform;
920
921          additionalLabelsCanvas.Children.Add(tickLabel);
922        }
923      }
924      else
925      {
926        additionalLabelsCanvas.Visibility = Visibility.Collapsed;
927      }
928    }
929
930    private int prevMinorTicksCount = DefaultTicksProvider.DefaultTicksCount;
931    private const int maxTickArrangeIterations = 12;
932    private void DoDrawMinorTicks(ICollection<Geometry> lines)
933    {
934      ITicksProvider<T> minorTicksProvider = ticksProvider.MinorProvider;
935      if (minorTicksProvider != null)
936      {
937        int minorTicksCount = prevMinorTicksCount;
938        int prevActualTicksCount = -1;
939        ITicksInfo<T> minorTicks;
940        TickCountChange result = TickCountChange.OK;
941        TickCountChange prevResult;
942        int iteration = 0;
943        do
944        {
945          Verify.IsTrue(++iteration < maxTickArrangeIterations);
946
947          minorTicks = minorTicksProvider.GetTicks(range, minorTicksCount);
948
949          prevActualTicksCount = minorTicks.Ticks.Length;
950          prevResult = result;
951          result = CheckMinorTicksArrangement(minorTicks);
952          if (prevResult == TickCountChange.Decrease && result == TickCountChange.Increase)
953          {
954            // stop tick number oscillating
955            result = TickCountChange.OK;
956          }
957          if (result == TickCountChange.Decrease)
958          {
959            int newMinorTicksCount = minorTicksProvider.DecreaseTickCount(minorTicksCount);
960            if (newMinorTicksCount == minorTicksCount)
961            {
962              result = TickCountChange.OK;
963            }
964            minorTicksCount = newMinorTicksCount;
965          }
966          else if (result == TickCountChange.Increase)
967          {
968            int newCount = minorTicksProvider.IncreaseTickCount(minorTicksCount);
969            if (newCount == minorTicksCount)
970            {
971              result = TickCountChange.OK;
972            }
973            minorTicksCount = newCount;
974          }
975
976        } while (result != TickCountChange.OK);
977        prevMinorTicksCount = minorTicksCount;
978
979        double[] sizes = minorTicks.TickSizes;
980
981        double[] screenCoords = minorTicks.Ticks.Select(
982          coord => getCoordinate(createDataPoint(convertToDouble(coord)).
983            DataToScreen(transform))).ToArray();
984
985        minorScreenTicks = new MinorTickInfo<double>[screenCoords.Length];
986        for (int i = 0; i < screenCoords.Length; i++)
987        {
988          minorScreenTicks[i] = new MinorTickInfo<double>(sizes[i], screenCoords[i]);
989        }
990
991        for (int i = 0; i < screenCoords.Length; i++)
992        {
993          double screenCoord = screenCoords[i];
994
995          Point p1 = createScreenPoint1(screenCoord);
996          Point p2 = createScreenPoint2(screenCoord, sizes[i]);
997
998          LineGeometry line = lineGeomPool.GetOrCreate();
999          line.StartPoint = p1;
1000          line.EndPoint = p2;
1001
1002          lines.Add(line);
1003        }
1004      }
1005    }
1006
1007    private TickCountChange CheckMinorTicksArrangement(ITicksInfo<T> minorTicks)
1008    {
1009      Size renderSize = RenderSize;
1010      TickCountChange result = TickCountChange.OK;
1011      if (minorTicks.Ticks.Length * 3 > getSize(renderSize))
1012        result = TickCountChange.Decrease;
1013      else if (minorTicks.Ticks.Length * 6 < getSize(renderSize))
1014        result = TickCountChange.Increase;
1015      return result;
1016    }
1017
1018    private bool isStaticAxis = false;
1019    /// <summary>
1020    /// Gets or sets a value indicating whether this instance is a static axis.
1021    /// If axis is static, its labels from sides are shifted so that they are not clipped by axis bounds.
1022    /// </summary>
1023    /// <value>
1024    ///   <c>true</c> if this instance is static axis; otherwise, <c>false</c>.
1025    /// </value>
1026    public bool IsStaticAxis
1027    {
1028      get { return isStaticAxis; }
1029      set
1030      {
1031        if (isStaticAxis != value)
1032        {
1033          isStaticAxis = value;
1034          UpdateUI();
1035        }
1036      }
1037    }
1038
1039    private double ToScreen(T value)
1040    {
1041      return getCoordinate(createDataPoint(convertToDouble(value)).DataToScreen(transform));
1042    }
1043
1044    private double staticAxisMargin = 1; // px
1045
1046    private void DoDrawCommonLabels(double[] screenTicksX)
1047    {
1048      Size renderSize = RenderSize;
1049
1050      commonLabelsCanvas.Children.Clear();
1051
1052#if DEBUG
1053      if (labels != null)
1054      {
1055        foreach (FrameworkElement item in labels)
1056        {
1057          if (item != null)
1058            Debug.Assert(item.Parent == null);
1059        }
1060      }
1061#endif
1062
1063      double minCoordUnsorted = ToScreen(range.Min);
1064      double maxCoordUnsorted = ToScreen(range.Max);
1065
1066      double minCoord = Math.Min(minCoordUnsorted, maxCoordUnsorted);
1067      double maxCoord = Math.Max(minCoordUnsorted, maxCoordUnsorted);
1068
1069      double maxCoordDiff = (maxCoord - minCoord) / labels.Length / 2.0;
1070
1071      double minCoordToAdd = minCoord - maxCoordDiff;
1072      double maxCoordToAdd = maxCoord + maxCoordDiff;
1073
1074      for (int i = 0; i < ticks.Length; i++)
1075      {
1076        FrameworkElement tickLabel = (FrameworkElement)labels[i];
1077        if (tickLabel == null) continue;
1078
1079        Debug.Assert(((FrameworkElement)tickLabel).Parent == null);
1080
1081        tickLabel.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
1082
1083        double screenX = screenTicksX[i];
1084        double coord = screenX;
1085
1086        tickLabel.HorizontalAlignment = HorizontalAlignment.Center;
1087        tickLabel.VerticalAlignment = VerticalAlignment.Center;
1088
1089        if (isStaticAxis)
1090        {
1091          // getting real size of label
1092          tickLabel.Measure(renderSize);
1093          Size tickLabelSize = tickLabel.DesiredSize;
1094
1095          if (Math.Abs(screenX - minCoord) < maxCoordDiff)
1096          {
1097            coord = minCoord + staticAxisMargin;
1098            if (placement.IsBottomOrTop())
1099              tickLabel.HorizontalAlignment = HorizontalAlignment.Left;
1100            else
1101              tickLabel.VerticalAlignment = VerticalAlignment.Top;
1102          }
1103          else if (Math.Abs(screenX - maxCoord) < maxCoordDiff)
1104          {
1105            coord = maxCoord - getSize(tickLabelSize) / 2 - staticAxisMargin;
1106            if (!placement.IsBottomOrTop())
1107            {
1108              tickLabel.VerticalAlignment = VerticalAlignment.Bottom;
1109              coord = maxCoord - staticAxisMargin;
1110            }
1111          }
1112        }
1113
1114        // label is out of visible area
1115        if (coord < minCoord || coord > maxCoord)
1116        {
1117          // todo investigate
1118          //continue;
1119        }
1120
1121        if (coord.IsNaN())
1122          continue;
1123
1124        StackCanvas.SetCoordinate(tickLabel, coord);
1125
1126        commonLabelsCanvas.Children.Add(tickLabel);
1127      }
1128    }
1129
1130    private double GetCoordinateFromTick(T tick)
1131    {
1132      return getCoordinate(createDataPoint(convertToDouble(tick)).DataToScreen(transform));
1133    }
1134
1135    private Func<T, double> convertToDouble;
1136    /// <summary>
1137    /// Gets or sets the convertion of tick to double.
1138    /// Should not be null.
1139    /// </summary>
1140    /// <value>The convert to double.</value>
1141    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1142    public Func<T, double> ConvertToDouble
1143    {
1144      get { return convertToDouble; }
1145      set
1146      {
1147        if (value == null)
1148          throw new ArgumentNullException("value");
1149
1150        convertToDouble = value;
1151        UpdateUI();
1152      }
1153    }
1154
1155    private Func<double, T> convertFromDouble;
1156    /// <summary>
1157    /// Convertation function for use in smooth axes panning.
1158    /// Converts from axis value to double.
1159    /// </summary>
1160    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1161    public Func<double, T> ConvertFromDouble
1162    {
1163      get { return convertFromDouble; }
1164      set
1165      {
1166        if (value == null)
1167          throw new ArgumentNullException("value");
1168
1169        convertFromDouble = value;
1170        UpdateUI();
1171      }
1172    }
1173
1174    internal event EventHandler ScreenTicksChanged;
1175    private double[] screenTicks;
1176    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1177    [EditorBrowsable(EditorBrowsableState.Never)]
1178    public double[] ScreenTicks
1179    {
1180      get { return screenTicks; }
1181    }
1182
1183    private MinorTickInfo<double>[] minorScreenTicks;
1184    [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
1185    [EditorBrowsable(EditorBrowsableState.Never)]
1186    public MinorTickInfo<double>[] MinorScreenTicks
1187    {
1188      get { return minorScreenTicks; }
1189    }
1190
1191    ITicksInfo<T> ticksInfo;
1192    private T[] ticks;
1193    private UIElement[] labels;
1194    private const double increaseRatio = 3.0;
1195    private const double decreaseRatio = 1.6;
1196
1197    private Func<Size, double> getSize = size => size.Width;
1198    private Func<Point, double> getCoordinate = p => p.X;
1199    private Func<double, Point> createDataPoint = d => new Point(d, 0);
1200
1201    private Func<double, Point> createScreenPoint1 = d => new Point(d, 0);
1202    private Func<double, double, Point> createScreenPoint2 = (d, size) => new Point(d, size);
1203
1204    private int previousTickCount = DefaultTicksProvider.DefaultTicksCount;
1205    private void CreateTicks(Range<T> range)
1206    {
1207      TickCountChange result = TickCountChange.OK;
1208      TickCountChange prevResult;
1209
1210      int prevActualTickCount = -1;
1211
1212      int tickCount = previousTickCount;
1213      int iteration = 0;
1214
1215      do
1216      {
1217        Verify.IsTrue(++iteration < maxTickArrangeIterations);
1218
1219        ticksInfo = ticksProvider.GetTicks(range, tickCount);
1220        ticks = ticksInfo.Ticks;
1221
1222        if (ticks.Length == prevActualTickCount)
1223        {
1224          result = TickCountChange.OK;
1225          break;
1226        }
1227
1228        prevActualTickCount = ticks.Length;
1229
1230        if (labels != null)
1231        {
1232          for (int i = 0; i < labels.Length; i++)
1233          {
1234            labelProvider.ReleaseLabel(labels[i]);
1235          }
1236        }
1237
1238        labels = labelProvider.CreateLabels(ticksInfo);
1239
1240        prevResult = result;
1241        result = CheckLabelsArrangement(labels, ticks);
1242
1243        if (prevResult == TickCountChange.Decrease && result == TickCountChange.Increase)
1244        {
1245          // stop tick number oscillating
1246          result = TickCountChange.OK;
1247        }
1248
1249        if (result != TickCountChange.OK)
1250        {
1251          int prevTickCount = tickCount;
1252          if (result == TickCountChange.Decrease)
1253            tickCount = ticksProvider.DecreaseTickCount(tickCount);
1254          else
1255          {
1256            tickCount = ticksProvider.IncreaseTickCount(tickCount);
1257            //DebugVerify.Is(tickCount >= prevTickCount);
1258          }
1259
1260          // ticks provider could not create less ticks or tick number didn't change
1261          if (tickCount == 0 || prevTickCount == tickCount)
1262          {
1263            tickCount = prevTickCount;
1264            result = TickCountChange.OK;
1265          }
1266        }
1267      } while (result != TickCountChange.OK);
1268
1269      previousTickCount = tickCount;
1270    }
1271
1272    private TickCountChange CheckLabelsArrangement(UIElement[] labels, T[] ticks)
1273    {
1274      var actualLabels = labels.Select((label, i) => new { Label = label, Index = i })
1275        .Where(el => el.Label != null)
1276        .Select(el => new { Label = el.Label, Tick = ticks[el.Index] })
1277        .ToList();
1278
1279      actualLabels.ForEach(item => item.Label.Measure(RenderSize));
1280
1281      var sizeInfos = actualLabels.Select(item =>
1282        new { X = GetCoordinateFromTick(item.Tick), Size = getSize(item.Label.DesiredSize) })
1283        .OrderBy(item => item.X).ToArray();
1284
1285      TickCountChange res = TickCountChange.OK;
1286
1287      int increaseCount = 0;
1288      for (int i = 0; i < sizeInfos.Length - 1; i++)
1289      {
1290        if ((sizeInfos[i].X + sizeInfos[i].Size * decreaseRatio) > sizeInfos[i + 1].X)
1291        {
1292          res = TickCountChange.Decrease;
1293          break;
1294        }
1295        if ((sizeInfos[i].X + sizeInfos[i].Size * increaseRatio) < sizeInfos[i + 1].X)
1296        {
1297          increaseCount++;
1298        }
1299      }
1300      if (increaseCount > sizeInfos.Length / 2)
1301        res = TickCountChange.Increase;
1302
1303      return res;
1304    }
1305  }
1306
1307  [DebuggerDisplay("{X} + {Size}")]
1308  internal sealed class SizeInfo : IComparable<SizeInfo>
1309  {
1310    public double Size { get; set; }
1311    public double X { get; set; }
1312
1313
1314    public int CompareTo(SizeInfo other)
1315    {
1316      return X.CompareTo(other.X);
1317    }
1318  }
1319
1320  internal enum TickCountChange
1321  {
1322    Increase = -1,
1323    OK = 0,
1324    Decrease = 1
1325  }
1326
1327  /// <summary>
1328  /// Represents an auxiliary structure for storing additional info during major DateTime labels generation.
1329  /// </summary>
1330  public struct MajorLabelsInfo
1331  {
1332    public object Info { get; set; }
1333    public int MajorLabelsCount { get; set; }
1334
1335    public override string ToString()
1336    {
1337      return String.Format("{0}, Count={1}", Info, MajorLabelsCount);
1338    }
1339  }
1340}
Note: See TracBrowser for help on using the repository browser.