Initial Commit
This commit is contained in:
411
Charts/Axes/AxisBase.cs
Normal file
411
Charts/Axes/AxisBase.cs
Normal file
@@ -0,0 +1,411 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1231
Charts/Axes/AxisControl.cs
Normal file
1231
Charts/Axes/AxisControl.cs
Normal file
File diff suppressed because it is too large
Load Diff
42
Charts/Axes/AxisControlBase.cs
Normal file
42
Charts/Axes/AxisControlBase.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class AxisControlBase : ContentControl
|
||||
{
|
||||
#region Properties
|
||||
|
||||
public HorizontalAlignment LabelsHorizontalAlignment
|
||||
{
|
||||
get { return (HorizontalAlignment)GetValue(LabelsHorizontalAlignmentProperty); }
|
||||
set { SetValue(LabelsHorizontalAlignmentProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LabelsHorizontalAlignmentProperty = DependencyProperty.Register(
|
||||
"LabelsHorizontalAlignment",
|
||||
typeof(HorizontalAlignment),
|
||||
typeof(AxisControlBase),
|
||||
new FrameworkPropertyMetadata(HorizontalAlignment.Center));
|
||||
|
||||
|
||||
public VerticalAlignment LabelsVerticalAlignment
|
||||
{
|
||||
get { return (VerticalAlignment)GetValue(LabelsVerticalAlignmentProperty); }
|
||||
set { SetValue(LabelsVerticalAlignmentProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty LabelsVerticalAlignmentProperty = DependencyProperty.Register(
|
||||
"LabelsVerticalAlignment",
|
||||
typeof(VerticalAlignment),
|
||||
typeof(AxisControlBase),
|
||||
new FrameworkPropertyMetadata(VerticalAlignment.Center));
|
||||
|
||||
#endregion // end of Properties
|
||||
|
||||
}
|
||||
}
|
||||
76
Charts/Axes/AxisControlStyle.xaml
Normal file
76
Charts/Axes/AxisControlStyle.xaml
Normal file
@@ -0,0 +1,76 @@
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts"
|
||||
>
|
||||
<Style TargetType="{x:Type Grid}" x:Key="gridStyle">
|
||||
<Setter Property="Grid.ClipToBounds" Value="False"/>
|
||||
</Style>
|
||||
|
||||
<RotateTransform Angle="-90" x:Key="additionalLabelsTransformLeft"/>
|
||||
|
||||
<ControlTemplate x:Key="axisControlTemplateBottom">
|
||||
<Grid Style="{StaticResource gridStyle}" Name="PART_ContentsGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Path Name="PART_TicksPath" Grid.Row="0" Stroke="Black"/>
|
||||
<local:StackCanvas Name="PART_CommonLabelsCanvas" Grid.Row="1" Placement="Bottom"/>
|
||||
<local:StackCanvas Name="PART_AdditionalLabelsCanvas" Grid.Row="2" Placement="Bottom"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="axisControlTemplateTop">
|
||||
<Grid Background="{TemplateBinding Background}" Style="{StaticResource gridStyle}" Name="PART_ContentsGrid">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Path Name="PART_TicksPath" Grid.Row="2" Stroke="Black">
|
||||
<Path.LayoutTransform>
|
||||
<MatrixTransform Matrix="1,0,0,-1.1,0,0"/>
|
||||
</Path.LayoutTransform>
|
||||
</Path>
|
||||
<local:StackCanvas Name="PART_CommonLabelsCanvas" Grid.Row="1" Placement="Top"/>
|
||||
<local:StackCanvas Name="PART_AdditionalLabelsCanvas" Grid.Row="0" Placement="Top"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="axisControlTemplateLeft">
|
||||
<Grid Background="{TemplateBinding Background}" Style="{StaticResource gridStyle}" Name="PART_ContentsGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Rectangle Grid.Column="2"/>
|
||||
<Path Name="PART_TicksPath" Grid.Column="2" Stroke="Black">
|
||||
<Path.LayoutTransform>
|
||||
<MatrixTransform Matrix="-1,0,0,1,0,0"/>
|
||||
</Path.LayoutTransform>
|
||||
</Path>
|
||||
<local:StackCanvas Name="PART_CommonLabelsCanvas" Grid.Column="1" Placement="Left" Margin="1,0,1,0"/>
|
||||
<local:StackCanvas Name="PART_AdditionalLabelsCanvas" Grid.Column="0" Placement="Left" Margin="1,0,1,0"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
<ControlTemplate x:Key="axisControlTemplateRight">
|
||||
<Grid Background="{TemplateBinding Background}" Style="{StaticResource gridStyle}" Name="PART_ContentsGrid">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Path Name="PART_TicksPath" Grid.Column="0" Stroke="Black"/>
|
||||
<local:StackCanvas Name="PART_CommonLabelsCanvas" Grid.Column="1" Placement="Right" Margin="1,0,0,0"/>
|
||||
<local:StackCanvas Name="PART_AdditionalLabelsCanvas" Grid.Column="2" Placement="Right"/>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
|
||||
</ResourceDictionary>
|
||||
287
Charts/Axes/AxisGrid.cs
Normal file
287
Charts/Axes/AxisGrid.cs
Normal file
@@ -0,0 +1,287 @@
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts;
|
||||
using System.Windows.Controls;
|
||||
using System;
|
||||
using System.Windows.Shapes;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
/// <summary>
|
||||
/// Draws grid over viewport. Number of
|
||||
/// grid lines depends on Plotter's MainHorizontalAxis and MainVerticalAxis ticks.
|
||||
/// </summary>
|
||||
public class AxisGrid : ContentControl, IPlotterElement
|
||||
{
|
||||
static AxisGrid()
|
||||
{
|
||||
Type thisType = typeof(AxisGrid);
|
||||
Panel.ZIndexProperty.OverrideMetadata(thisType, new FrameworkPropertyMetadata(-1));
|
||||
}
|
||||
|
||||
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
|
||||
internal void BeginTicksUpdate()
|
||||
{
|
||||
}
|
||||
internal void EndTicksUpdate()
|
||||
{
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
|
||||
protected internal MinorTickInfo<double>[] MinorHorizontalTicks { get; set; }
|
||||
|
||||
protected internal MinorTickInfo<double>[] MinorVerticalTicks { get; set; }
|
||||
|
||||
protected internal double[] HorizontalTicks { get; set; }
|
||||
|
||||
protected internal double[] VerticalTicks { get; set; }
|
||||
|
||||
|
||||
private bool drawVerticalTicks = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to draw vertical tick lines.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if draw vertical ticks; otherwise, <c>false</c>.</value>
|
||||
public bool DrawVerticalTicks
|
||||
{
|
||||
get { return drawVerticalTicks; }
|
||||
set
|
||||
{
|
||||
if (drawVerticalTicks != value)
|
||||
{
|
||||
drawVerticalTicks = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool drawHorizontalTicks = true;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to draw horizontal tick lines.
|
||||
/// </summary>
|
||||
/// <value><c>true</c> if draw horizontal ticks; otherwise, <c>false</c>.</value>
|
||||
public bool DrawHorizontalTicks
|
||||
{
|
||||
get { return drawHorizontalTicks; }
|
||||
set
|
||||
{
|
||||
if (drawHorizontalTicks != value)
|
||||
{
|
||||
drawHorizontalTicks = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool drawHorizontalMinorTicks = false;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to draw horizontal minor ticks.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if draw horizontal minor ticks; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool DrawHorizontalMinorTicks
|
||||
{
|
||||
get { return drawHorizontalMinorTicks; }
|
||||
set
|
||||
{
|
||||
if (drawHorizontalMinorTicks != value)
|
||||
{
|
||||
drawHorizontalMinorTicks = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool drawVerticalMinorTicks = false;
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether to draw vertical minor ticks.
|
||||
/// </summary>
|
||||
/// <value>
|
||||
/// <c>true</c> if draw vertical minor ticks; otherwise, <c>false</c>.
|
||||
/// </value>
|
||||
public bool DrawVerticalMinorTicks
|
||||
{
|
||||
get { return drawVerticalMinorTicks; }
|
||||
set
|
||||
{
|
||||
if (drawVerticalMinorTicks != value)
|
||||
{
|
||||
drawVerticalMinorTicks = value;
|
||||
UpdateUIRepresentation();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double gridBrushThickness = 1;
|
||||
|
||||
private Path path = new Path();
|
||||
private Canvas canvas = new Canvas();
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AxisGrid"/> class.
|
||||
/// </summary>
|
||||
public AxisGrid()
|
||||
{
|
||||
IsHitTestVisible = false;
|
||||
|
||||
canvas.ClipToBounds = true;
|
||||
|
||||
path.Stroke = Brushes.LightGray;
|
||||
path.StrokeThickness = gridBrushThickness;
|
||||
|
||||
Content = canvas;
|
||||
}
|
||||
|
||||
private readonly ResourcePool<LineGeometry> lineGeometryPool = new ResourcePool<LineGeometry>();
|
||||
private readonly ResourcePool<Line> linePool = new ResourcePool<Line>();
|
||||
|
||||
private void UpdateUIRepresentation()
|
||||
{
|
||||
foreach (UIElement item in canvas.Children)
|
||||
{
|
||||
Line line = item as Line;
|
||||
if (line != null)
|
||||
{
|
||||
linePool.Put(line);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Children.Clear();
|
||||
Size size = RenderSize;
|
||||
|
||||
DrawMinorHorizontalTicks();
|
||||
DrawMinorVerticalTicks();
|
||||
|
||||
GeometryGroup prevGroup = path.Data as GeometryGroup;
|
||||
if (prevGroup != null)
|
||||
{
|
||||
foreach (LineGeometry geometry in prevGroup.Children)
|
||||
{
|
||||
lineGeometryPool.Put(geometry);
|
||||
}
|
||||
}
|
||||
|
||||
GeometryGroup group = new GeometryGroup();
|
||||
if (HorizontalTicks != null && drawHorizontalTicks)
|
||||
{
|
||||
double minY = 0;
|
||||
double maxY = size.Height;
|
||||
|
||||
for (int i = 0; i < HorizontalTicks.Length; i++)
|
||||
{
|
||||
double screenX = HorizontalTicks[i];
|
||||
LineGeometry line = lineGeometryPool.GetOrCreate();
|
||||
line.StartPoint = new Point(screenX, minY);
|
||||
line.EndPoint = new Point(screenX, maxY);
|
||||
group.Children.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (VerticalTicks != null && drawVerticalTicks)
|
||||
{
|
||||
double minX = 0;
|
||||
double maxX = size.Width;
|
||||
|
||||
for (int i = 0; i < VerticalTicks.Length; i++)
|
||||
{
|
||||
double screenY = VerticalTicks[i];
|
||||
LineGeometry line = lineGeometryPool.GetOrCreate();
|
||||
line.StartPoint = new Point(minX, screenY);
|
||||
line.EndPoint = new Point(maxX, screenY);
|
||||
group.Children.Add(line);
|
||||
}
|
||||
}
|
||||
|
||||
canvas.Children.Add(path);
|
||||
path.Data = group;
|
||||
}
|
||||
|
||||
private void DrawMinorVerticalTicks()
|
||||
{
|
||||
Size size = RenderSize;
|
||||
if (MinorVerticalTicks != null && drawVerticalMinorTicks)
|
||||
{
|
||||
double minX = 0;
|
||||
double maxX = size.Width;
|
||||
|
||||
for (int i = 0; i < MinorVerticalTicks.Length; i++)
|
||||
{
|
||||
double screenY = MinorVerticalTicks[i].Tick;
|
||||
if (screenY < 0)
|
||||
continue;
|
||||
if (screenY > size.Height)
|
||||
continue;
|
||||
|
||||
Line line = linePool.GetOrCreate();
|
||||
|
||||
line.Y1 = screenY;
|
||||
line.Y2 = screenY;
|
||||
line.X1 = minX;
|
||||
line.X2 = maxX;
|
||||
line.Stroke = Brushes.LightGray;
|
||||
line.StrokeThickness = MinorVerticalTicks[i].Value * gridBrushThickness;
|
||||
|
||||
canvas.Children.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMinorHorizontalTicks()
|
||||
{
|
||||
Size size = RenderSize;
|
||||
if (MinorHorizontalTicks != null && drawHorizontalMinorTicks)
|
||||
{
|
||||
double minY = 0;
|
||||
double maxY = size.Height;
|
||||
|
||||
for (int i = 0; i < MinorHorizontalTicks.Length; i++)
|
||||
{
|
||||
double screenX = MinorHorizontalTicks[i].Tick;
|
||||
if (screenX < 0)
|
||||
continue;
|
||||
if (screenX > size.Width)
|
||||
continue;
|
||||
|
||||
Line line = linePool.GetOrCreate();
|
||||
line.X1 = screenX;
|
||||
line.X2 = screenX;
|
||||
line.Y1 = minY;
|
||||
line.Y2 = maxY;
|
||||
line.Stroke = Brushes.LightGray;
|
||||
line.StrokeThickness = MinorHorizontalTicks[i].Value * gridBrushThickness;
|
||||
|
||||
canvas.Children.Add(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = plotter;
|
||||
plotter.CentralGrid.Children.Add(this);
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
plotter.CentralGrid.Children.Remove(this);
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
private Plotter plotter;
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public Plotter Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
30
Charts/Axes/AxisPlacement.cs
Normal file
30
Charts/Axes/AxisPlacement.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the position of axis inside ChartPlotter.
|
||||
/// </summary>
|
||||
public enum AxisPlacement
|
||||
{
|
||||
/// <summary>
|
||||
/// Axis is placed to the left.
|
||||
/// </summary>
|
||||
Left,
|
||||
/// <summary>
|
||||
/// Axis is placed to the right.
|
||||
/// </summary>
|
||||
Right,
|
||||
/// <summary>
|
||||
/// Axis is placed to the top.
|
||||
/// </summary>
|
||||
Top,
|
||||
/// <summary>
|
||||
/// Axis is placed to the bottom.
|
||||
/// </summary>
|
||||
Bottom
|
||||
}
|
||||
}
|
||||
113
Charts/Axes/DateTime/DateTimeAxis.cs
Normal file
113
Charts/Axes/DateTime/DateTimeAxis.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.ViewportRestrictions;
|
||||
using System.Windows.Media;
|
||||
using System.Windows;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an axis with ticks of <see cref="System.DateTime"/> type.
|
||||
/// </summary>
|
||||
public class DateTimeAxis : AxisBase<DateTime>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeAxis"/> class.
|
||||
/// </summary>
|
||||
public DateTimeAxis()
|
||||
: base(new DateTimeAxisControl(), DoubleToDate,
|
||||
dt => dt.Ticks / 10000000000.0)
|
||||
{
|
||||
AxisControl.SetBinding(MajorLabelBackgroundBrushProperty, new Binding("MajorLabelBackgroundBrush") { Source = this });
|
||||
AxisControl.SetBinding(MajorLabelRectangleBorderPropertyProperty, new Binding("MajorLabelRectangleBorderProperty") { Source = this });
|
||||
}
|
||||
|
||||
#region VisualProperties
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the major tick labels' background brush. This is a DependencyProperty.
|
||||
/// </summary>
|
||||
/// <value>The major label background brush.</value>
|
||||
public Brush MajorLabelBackgroundBrush
|
||||
{
|
||||
get { return (Brush)GetValue(MajorLabelBackgroundBrushProperty); }
|
||||
set { SetValue(MajorLabelBackgroundBrushProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MajorLabelBackgroundBrushProperty = DependencyProperty.Register(
|
||||
"MajorLabelBackgroundBrush",
|
||||
typeof(Brush),
|
||||
typeof(DateTimeAxis),
|
||||
new FrameworkPropertyMetadata(Brushes.Beige));
|
||||
|
||||
|
||||
public Brush MajorLabelRectangleBorderProperty
|
||||
{
|
||||
get { return (Brush)GetValue(MajorLabelRectangleBorderPropertyProperty); }
|
||||
set { SetValue(MajorLabelRectangleBorderPropertyProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty MajorLabelRectangleBorderPropertyProperty = DependencyProperty.Register(
|
||||
"MajorLabelRectangleBorderProperty",
|
||||
typeof(Brush),
|
||||
typeof(DateTimeAxis),
|
||||
new FrameworkPropertyMetadata(Brushes.Peru));
|
||||
|
||||
#endregion // end of VisualProperties
|
||||
|
||||
private ViewportRestriction restriction = new DateTimeHorizontalAxisRestriction();
|
||||
protected ViewportRestriction Restriction
|
||||
{
|
||||
get { return restriction; }
|
||||
set { restriction = value; }
|
||||
}
|
||||
|
||||
protected override void OnPlotterAttached(Plotter2D plotter)
|
||||
{
|
||||
base.OnPlotterAttached(plotter);
|
||||
|
||||
plotter.Viewport.Restrictions.Add(restriction);
|
||||
}
|
||||
|
||||
protected override void OnPlotterDetaching(Plotter2D plotter)
|
||||
{
|
||||
plotter.Viewport.Restrictions.Remove(restriction);
|
||||
|
||||
base.OnPlotterDetaching(plotter);
|
||||
}
|
||||
|
||||
private static readonly long minTicks = DateTime.MinValue.Ticks;
|
||||
private static readonly long maxTicks = DateTime.MaxValue.Ticks;
|
||||
private static DateTime DoubleToDate(double d)
|
||||
{
|
||||
long ticks = (long)(d * 10000000000L);
|
||||
|
||||
// todo should we throw an exception if number of ticks is too big or small?
|
||||
if (ticks < minTicks)
|
||||
ticks = minTicks;
|
||||
else if (ticks > maxTicks)
|
||||
ticks = maxTicks;
|
||||
|
||||
return new DateTime(ticks);
|
||||
}
|
||||
|
||||
/// <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 override void SetConversion(double min, DateTime minValue, double max, DateTime maxValue)
|
||||
{
|
||||
var conversion = new DateTimeToDoubleConversion(min, minValue, max, maxValue);
|
||||
|
||||
ConvertToDouble = conversion.ToDouble;
|
||||
ConvertFromDouble = conversion.FromDouble;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Charts/Axes/DateTime/DateTimeAxisControl.cs
Normal file
27
Charts/Axes/DateTime/DateTimeAxisControl.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// AxisControl for DateTime axes.
|
||||
/// </summary>
|
||||
public class DateTimeAxisControl : AxisControl<DateTime>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeAxisControl"/> class.
|
||||
/// </summary>
|
||||
public DateTimeAxisControl()
|
||||
{
|
||||
LabelProvider = new DateTimeLabelProvider();
|
||||
TicksProvider = new DateTimeTicksProvider();
|
||||
MajorLabelProvider = new MajorDateTimeLabelProvider();
|
||||
|
||||
ConvertToDouble = dt => dt.Ticks;
|
||||
|
||||
Range = new Range<DateTime>(DateTime.Now, DateTime.Now.AddYears(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Charts/Axes/DateTime/DateTimeLabelProvider.cs
Normal file
48
Charts/Axes/DateTime/DateTimeLabelProvider.cs
Normal file
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a label provider for <see cref="System.DateTime"/> ticks.
|
||||
/// </summary>
|
||||
public class DateTimeLabelProvider : DateTimeLabelProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public DateTimeLabelProvider() { }
|
||||
|
||||
public override UIElement[] CreateLabels(ITicksInfo<DateTime> ticksInfo)
|
||||
{
|
||||
object info = ticksInfo.Info;
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
if (info is DifferenceIn)
|
||||
{
|
||||
DifferenceIn diff = (DifferenceIn)info;
|
||||
DateFormat = GetDateFormat(diff);
|
||||
}
|
||||
|
||||
LabelTickInfo<DateTime> tickInfo = new LabelTickInfo<DateTime> { Info = info };
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
for (int i = 0; i < ticks.Length; i++)
|
||||
{
|
||||
tickInfo.Tick = ticks[i];
|
||||
|
||||
string tickText = GetString(tickInfo);
|
||||
UIElement label = new TextBlock { Text = tickText, ToolTip = ticks[i] };
|
||||
ApplyCustomView(tickInfo, label);
|
||||
res[i] = label;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Charts/Axes/DateTime/DateTimeLabelProviderBase.cs
Normal file
59
Charts/Axes/DateTime/DateTimeLabelProviderBase.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class DateTimeLabelProviderBase : LabelProviderBase<DateTime>
|
||||
{
|
||||
private string dateFormat;
|
||||
protected string DateFormat
|
||||
{
|
||||
get { return dateFormat; }
|
||||
set { dateFormat = value; }
|
||||
}
|
||||
|
||||
protected override string GetStringCore(LabelTickInfo<DateTime> tickInfo)
|
||||
{
|
||||
return tickInfo.Tick.ToString(dateFormat);
|
||||
}
|
||||
|
||||
protected virtual string GetDateFormat(DifferenceIn diff)
|
||||
{
|
||||
string format = null;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
format = "yyyy";
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
format = "MMM";
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
format = "%d";
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
format = "HH:mm";
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
format = "%m";
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
format = "ss";
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
format = "fff";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
307
Charts/Axes/DateTime/DateTimeTicksProvider.cs
Normal file
307
Charts/Axes/DateTime/DateTimeTicksProvider.cs
Normal file
@@ -0,0 +1,307 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ticks provider for ticks of <see cref="T:System.DateTime"/> type.
|
||||
/// </summary>
|
||||
public class DateTimeTicksProvider : TimeTicksProviderBase<DateTime>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DateTimeTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public DateTimeTicksProvider() { }
|
||||
|
||||
static DateTimeTicksProvider()
|
||||
{
|
||||
Providers.Add(DifferenceIn.Year, new YearDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Month, new MonthDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Day, new DayDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Hour, new HourDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Minute, new MinuteDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Second, new SecondDateTimeProvider());
|
||||
Providers.Add(DifferenceIn.Millisecond, new MillisecondDateTimeProvider());
|
||||
|
||||
MinorProviders.Add(DifferenceIn.Year, new MinorDateTimeProvider(new YearDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Month, new MinorDateTimeProvider(new MonthDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Day, new MinorDateTimeProvider(new DayDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Hour, new MinorDateTimeProvider(new HourDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Minute, new MinorDateTimeProvider(new MinuteDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Second, new MinorDateTimeProvider(new SecondDateTimeProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Millisecond, new MinorDateTimeProvider(new MillisecondDateTimeProvider()));
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan GetDifference(DateTime start, DateTime end)
|
||||
{
|
||||
return end - start;
|
||||
}
|
||||
}
|
||||
|
||||
internal static class DateTimeArrayExtensions
|
||||
{
|
||||
internal static int GetIndex(this DateTime[] array, DateTime value)
|
||||
{
|
||||
for (int i = 0; i < array.Length - 1; i++)
|
||||
{
|
||||
if (array[i] <= value && value < array[i + 1])
|
||||
return i;
|
||||
}
|
||||
|
||||
return array.Length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MinorDateTimeProvider : MinorTimeProviderBase<DateTime>
|
||||
{
|
||||
public MinorDateTimeProvider(ITicksProvider<DateTime> owner) : base(owner) { }
|
||||
|
||||
protected override bool IsInside(DateTime value, Range<DateTime> range)
|
||||
{
|
||||
return range.Min < value && value < range.Max;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class YearDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Year;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 20, 10, 5, 4, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return dt.Year;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
int year = start.Year;
|
||||
int newYear = (year / step) * step;
|
||||
if (newYear == 0) newYear = 1;
|
||||
|
||||
return new DateTime(newYear, 1, 1);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Year == DateTime.MinValue.Year;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
if (dt.Year + step > DateTime.MaxValue.Year)
|
||||
return DateTime.MaxValue;
|
||||
|
||||
return dt.AddYears(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MonthDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Month;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return dt.Month + (dt.Year - start.Year) * 12;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return new DateTime(start.Year, 1, 1);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Month == DateTime.MinValue.Month;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMonths(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DayDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Day;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 30, 15, 10, 5, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Days;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date;
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Day == 1;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddDays(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HourDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Hour;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 24, 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (int)(dt - start).TotalHours;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date;
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddHours(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MinuteDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Minute;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (int)(dt - start).TotalMinutes;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMinutes(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SecondDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Second;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (int)(dt - start).TotalSeconds;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour).AddMinutes(start.Minute);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddSeconds(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MillisecondDateTimeProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Millisecond;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 100, 50, 40, 25, 20, 10, 5, 4, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (int)(dt - start).TotalMilliseconds;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour).AddMinutes(start.Minute).AddSeconds(start.Second);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMilliseconds(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
134
Charts/Axes/DateTime/DateTimeTicksProviderBase.cs
Normal file
134
Charts/Axes/DateTime/DateTimeTicksProviderBase.cs
Normal file
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class DateTimeTicksProviderBase : ITicksProvider<DateTime>
|
||||
{
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
{
|
||||
Changed(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
protected static DateTime Shift(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = dateTime;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static DateTime RoundDown(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = dateTime;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = new DateTime(dateTime.Year, 1, 1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = new DateTime(dateTime.Year, dateTime.Month, 1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = dateTime.Date;
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute).AddSeconds(dateTime.Second);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute).AddSeconds(dateTime.Second).AddMilliseconds(dateTime.Millisecond);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
DebugVerify.Is(res <= dateTime);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static DateTime RoundUp(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = RoundDown(dateTime, diff);
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#region ITicksProvider<DateTime> Members
|
||||
|
||||
public abstract ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount);
|
||||
public abstract int DecreaseTickCount(int ticksCount);
|
||||
public abstract int IncreaseTickCount(int ticksCount);
|
||||
public abstract ITicksProvider<DateTime> MinorProvider { get; }
|
||||
public abstract ITicksProvider<DateTime> MajorProvider { get; }
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
39
Charts/Axes/DateTime/DateTimeToDoubleConversion.cs
Normal file
39
Charts/Axes/DateTime/DateTimeToDoubleConversion.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal sealed class DateTimeToDoubleConversion
|
||||
{
|
||||
public DateTimeToDoubleConversion(double min, DateTime minDate, double max, DateTime maxDate)
|
||||
{
|
||||
this.min = min;
|
||||
this.length = max - min;
|
||||
this.ticksMin = minDate.Ticks;
|
||||
this.ticksLength = maxDate.Ticks - ticksMin;
|
||||
}
|
||||
|
||||
private double min;
|
||||
private double length;
|
||||
private long ticksMin;
|
||||
private long ticksLength;
|
||||
|
||||
internal DateTime FromDouble(double d)
|
||||
{
|
||||
double ratio = (d - min) / length;
|
||||
long tick = (long)(ticksMin + ticksLength * ratio);
|
||||
|
||||
tick = MathHelper.Clamp(tick, DateTime.MinValue.Ticks, DateTime.MaxValue.Ticks);
|
||||
|
||||
return new DateTime(tick);
|
||||
}
|
||||
|
||||
internal double ToDouble(DateTime dt)
|
||||
{
|
||||
double ratio = (dt.Ticks - ticksMin) / (double)ticksLength;
|
||||
return min + ratio * length;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Charts/Axes/DateTime/DifferenceIn.cs
Normal file
22
Charts/Axes/DateTime/DifferenceIn.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public enum DifferenceIn
|
||||
{
|
||||
Biggest = Year,
|
||||
|
||||
Year = 6,
|
||||
Month = 5,
|
||||
Day = 4,
|
||||
Hour = 3,
|
||||
Minute = 2,
|
||||
Second = 1,
|
||||
Millisecond = 0,
|
||||
|
||||
Smallest = Millisecond
|
||||
}
|
||||
}
|
||||
28
Charts/Axes/DateTime/HorizontalDateTimeAxis.cs
Normal file
28
Charts/Axes/DateTime/HorizontalDateTimeAxis.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an axis with ticks of <see cref="System.DateTime"/> type, which can be placed only from bottom or top of <see cref="Plotter"/>.
|
||||
/// By default is placed from bottom.
|
||||
/// </summary>
|
||||
public class HorizontalDateTimeAxis : DateTimeAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HorizontalDateTimeAxis"/> class.
|
||||
/// </summary>
|
||||
public HorizontalDateTimeAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Bottom;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Left || newPlacement == AxisPlacement.Right)
|
||||
throw new ArgumentException(Strings.Exceptions.HorizontalAxisCannotBeVertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Charts/Axes/DateTime/MajorDateTimeLabelProvider.cs
Normal file
133
Charts/Axes/DateTime/MajorDateTimeLabelProvider.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
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.Shapes;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a label provider for major ticks of <see cref="System.DateTime"/> type.
|
||||
/// </summary>
|
||||
public class MajorDateTimeLabelProvider : DateTimeLabelProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MajorDateTimeLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public MajorDateTimeLabelProvider() { }
|
||||
|
||||
public override UIElement[] CreateLabels(ITicksInfo<DateTime> ticksInfo)
|
||||
{
|
||||
object info = ticksInfo.Info;
|
||||
var ticks = ticksInfo.Ticks;
|
||||
UIElement[] res = new UIElement[ticks.Length - 1];
|
||||
int labelsNum = 3;
|
||||
|
||||
if (info is DifferenceIn)
|
||||
{
|
||||
DifferenceIn diff = (DifferenceIn)info;
|
||||
DateFormat = GetDateFormat(diff);
|
||||
}
|
||||
else if (info is MajorLabelsInfo)
|
||||
{
|
||||
MajorLabelsInfo mInfo = (MajorLabelsInfo)info;
|
||||
DifferenceIn diff = (DifferenceIn)mInfo.Info;
|
||||
DateFormat = GetDateFormat(diff);
|
||||
labelsNum = mInfo.MajorLabelsCount + 1;
|
||||
|
||||
//DebugVerify.Is(labelsNum < 100);
|
||||
}
|
||||
|
||||
DebugVerify.Is(ticks.Length < 10);
|
||||
|
||||
LabelTickInfo<DateTime> tickInfo = new LabelTickInfo<DateTime>();
|
||||
for (int i = 0; i < ticks.Length - 1; i++)
|
||||
{
|
||||
tickInfo.Info = info;
|
||||
tickInfo.Tick = ticks[i];
|
||||
|
||||
string tickText = GetString(tickInfo);
|
||||
|
||||
Grid grid = new Grid { };
|
||||
|
||||
// doing binding as described at http://sdolha.spaces.live.com/blog/cns!4121802308C5AB4E!3724.entry?wa=wsignin1.0&sa=835372863
|
||||
|
||||
grid.SetBinding(Grid.BackgroundProperty, new Binding { Path = new PropertyPath("(0)", DateTimeAxis.MajorLabelBackgroundBrushProperty), RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(AxisControlBase) } });
|
||||
Rectangle rect = new Rectangle
|
||||
{
|
||||
StrokeThickness = 2
|
||||
};
|
||||
rect.SetBinding(Rectangle.StrokeProperty, new Binding { Path = new PropertyPath("(0)", DateTimeAxis.MajorLabelRectangleBorderPropertyProperty), RelativeSource = new RelativeSource(RelativeSourceMode.FindAncestor) { AncestorType = typeof(AxisControlBase) } });
|
||||
|
||||
Grid.SetColumn(rect, 0);
|
||||
Grid.SetColumnSpan(rect, labelsNum);
|
||||
|
||||
for (int j = 0; j < labelsNum; j++)
|
||||
{
|
||||
grid.ColumnDefinitions.Add(new ColumnDefinition());
|
||||
}
|
||||
|
||||
grid.Children.Add(rect);
|
||||
|
||||
for (int j = 0; j < labelsNum; j++)
|
||||
{
|
||||
var tb = new TextBlock
|
||||
{
|
||||
Text = tickText,
|
||||
HorizontalAlignment = HorizontalAlignment.Center,
|
||||
Margin = new Thickness(0, 3, 0, 3)
|
||||
};
|
||||
Grid.SetColumn(tb, j);
|
||||
grid.Children.Add(tb);
|
||||
}
|
||||
|
||||
ApplyCustomView(tickInfo, grid);
|
||||
|
||||
res[i] = grid;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override string GetDateFormat(DifferenceIn diff)
|
||||
{
|
||||
string format = null;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
format = "yyyy";
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
format = "MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
format = "%d MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
format = "HH:mm %d MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
format = "HH:mm %d MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
format = "HH:mm:ss %d MMMM yyyy";
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
format = "fff";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
Charts/Axes/DateTime/MinorTimeProviderBase.cs
Normal file
114
Charts/Axes/DateTime/MinorTimeProviderBase.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal abstract class MinorTimeProviderBase<T> : ITicksProvider<T>
|
||||
{
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
{
|
||||
Changed(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly ITicksProvider<T> provider;
|
||||
public MinorTimeProviderBase(ITicksProvider<T> provider)
|
||||
{
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
private T[] majorTicks = new T[] { };
|
||||
internal void SetTicks(T[] ticks)
|
||||
{
|
||||
this.majorTicks = ticks;
|
||||
}
|
||||
|
||||
private double ticksSize = 0.5;
|
||||
public ITicksInfo<T> GetTicks(Range<T> range, int ticksCount)
|
||||
{
|
||||
if (majorTicks.Length == 0)
|
||||
return new TicksInfo<T>();
|
||||
|
||||
ticksCount /= majorTicks.Length;
|
||||
if (ticksCount == 0)
|
||||
ticksCount = 2;
|
||||
|
||||
var ticks = majorTicks.GetPairs().Select(r => Clip(provider.GetTicks(r, ticksCount), r)).
|
||||
SelectMany(t => t.Ticks).ToArray();
|
||||
|
||||
var res = new TicksInfo<T>
|
||||
{
|
||||
Ticks = ticks,
|
||||
TickSizes = ArrayExtensions.CreateArray(ticks.Length, ticksSize)
|
||||
};
|
||||
return res;
|
||||
}
|
||||
|
||||
private ITicksInfo<T> Clip(ITicksInfo<T> ticks, Range<T> range)
|
||||
{
|
||||
var newTicks = new List<T>(ticks.Ticks.Length);
|
||||
var newSizes = new List<double>(ticks.TickSizes.Length);
|
||||
|
||||
for (int i = 0; i < ticks.Ticks.Length; i++)
|
||||
{
|
||||
T tick = ticks.Ticks[i];
|
||||
if (IsInside(tick, range))
|
||||
{
|
||||
newTicks.Add(tick);
|
||||
newSizes.Add(ticks.TickSizes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new TicksInfo<T>
|
||||
{
|
||||
Ticks = newTicks.ToArray(),
|
||||
TickSizes = newSizes.ToArray(),
|
||||
Info = ticks.Info
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract bool IsInside(T value, Range<T> range);
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (majorTicks.Length > 0)
|
||||
ticksCount /= majorTicks.Length;
|
||||
|
||||
int minorTicksCount = provider.DecreaseTickCount(ticksCount);
|
||||
|
||||
if (majorTicks.Length > 0)
|
||||
minorTicksCount *= majorTicks.Length;
|
||||
|
||||
return minorTicksCount;
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (majorTicks.Length > 0)
|
||||
ticksCount /= majorTicks.Length;
|
||||
|
||||
int minorTicksCount = provider.IncreaseTickCount(ticksCount);
|
||||
|
||||
if (majorTicks.Length > 0)
|
||||
minorTicksCount *= majorTicks.Length;
|
||||
|
||||
return minorTicksCount;
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MinorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class DefaultDateTimeTicksStrategy : IDateTimeTicksStrategy
|
||||
{
|
||||
public virtual DifferenceIn GetDifference(TimeSpan span)
|
||||
{
|
||||
span = span.Duration();
|
||||
|
||||
DifferenceIn diff;
|
||||
if (span.Days > 365)
|
||||
diff = DifferenceIn.Year;
|
||||
else if (span.Days > 30)
|
||||
diff = DifferenceIn.Month;
|
||||
else if (span.Days > 0)
|
||||
diff = DifferenceIn.Day;
|
||||
else if (span.Hours > 0)
|
||||
diff = DifferenceIn.Hour;
|
||||
else if (span.Minutes > 0)
|
||||
diff = DifferenceIn.Minute;
|
||||
else if (span.Seconds > 0)
|
||||
diff = DifferenceIn.Second;
|
||||
else
|
||||
diff = DifferenceIn.Millisecond;
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
public virtual bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff)
|
||||
{
|
||||
lowerDiff = diff;
|
||||
|
||||
int code = (int)diff;
|
||||
bool res = code > (int)DifferenceIn.Smallest;
|
||||
if (res)
|
||||
{
|
||||
lowerDiff = (DifferenceIn)(code - 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public virtual bool TryGetBiggerDiff(DifferenceIn diff, out DifferenceIn biggerDiff)
|
||||
{
|
||||
biggerDiff = diff;
|
||||
|
||||
int code = (int)diff;
|
||||
bool res = code < (int)DifferenceIn.Biggest;
|
||||
if (res)
|
||||
{
|
||||
biggerDiff = (DifferenceIn)(code + 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Charts/Axes/DateTime/Strategies/DelegateStrategy.cs
Normal file
30
Charts/Axes/DateTime/Strategies/DelegateStrategy.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.DateTime.Strategies
|
||||
{
|
||||
public class DelegateDateTimeStrategy : DefaultDateTimeTicksStrategy
|
||||
{
|
||||
private readonly Func<TimeSpan, DifferenceIn?> function;
|
||||
public DelegateDateTimeStrategy(Func<TimeSpan, DifferenceIn?> function)
|
||||
{
|
||||
if (function == null)
|
||||
throw new ArgumentNullException("function");
|
||||
|
||||
this.function = function;
|
||||
}
|
||||
|
||||
public override DifferenceIn GetDifference(TimeSpan span)
|
||||
{
|
||||
DifferenceIn? customResult = function(span);
|
||||
|
||||
DifferenceIn result = customResult.HasValue ?
|
||||
customResult.Value :
|
||||
base.GetDifference(span);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Charts/Axes/DateTime/Strategies/ExtendedDaysStrategy.cs
Normal file
67
Charts/Axes/DateTime/Strategies/ExtendedDaysStrategy.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class ExtendedDaysStrategy : IDateTimeTicksStrategy
|
||||
{
|
||||
private static readonly DifferenceIn[] diffs = new DifferenceIn[] {
|
||||
DifferenceIn.Year,
|
||||
DifferenceIn.Day,
|
||||
DifferenceIn.Hour,
|
||||
DifferenceIn.Minute,
|
||||
DifferenceIn.Second,
|
||||
DifferenceIn.Millisecond
|
||||
};
|
||||
|
||||
public DifferenceIn GetDifference(TimeSpan span)
|
||||
{
|
||||
span = span.Duration();
|
||||
|
||||
DifferenceIn diff;
|
||||
if (span.Days > 365)
|
||||
diff = DifferenceIn.Year;
|
||||
else if (span.Days > 0)
|
||||
diff = DifferenceIn.Day;
|
||||
else if (span.Hours > 0)
|
||||
diff = DifferenceIn.Hour;
|
||||
else if (span.Minutes > 0)
|
||||
diff = DifferenceIn.Minute;
|
||||
else if (span.Seconds > 0)
|
||||
diff = DifferenceIn.Second;
|
||||
else
|
||||
diff = DifferenceIn.Millisecond;
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
public bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff)
|
||||
{
|
||||
lowerDiff = diff;
|
||||
|
||||
int index = Array.IndexOf(diffs, diff);
|
||||
if (index == -1)
|
||||
return false;
|
||||
|
||||
if (index == diffs.Length - 1)
|
||||
return false;
|
||||
|
||||
lowerDiff = diffs[index + 1];
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetBiggerDiff(DifferenceIn diff, out DifferenceIn biggerDiff)
|
||||
{
|
||||
biggerDiff = diff;
|
||||
|
||||
int index = Array.IndexOf(diffs, diff);
|
||||
if (index == -1 || index == 0)
|
||||
return false;
|
||||
|
||||
biggerDiff = diffs[index - 1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Charts/Axes/DateTime/Strategies/IDateTimeTicksStrategy.cs
Normal file
14
Charts/Axes/DateTime/Strategies/IDateTimeTicksStrategy.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public interface IDateTimeTicksStrategy
|
||||
{
|
||||
DifferenceIn GetDifference(TimeSpan span);
|
||||
bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff);
|
||||
bool TryGetBiggerDiff(DifferenceIn diff, out DifferenceIn biggerDiff);
|
||||
}
|
||||
}
|
||||
269
Charts/Axes/DateTime/TimePeriodTicksProvider.cs
Normal file
269
Charts/Axes/DateTime/TimePeriodTicksProvider.cs
Normal file
@@ -0,0 +1,269 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal abstract class TimePeriodTicksProvider<T> : ITicksProvider<T>
|
||||
{
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
{
|
||||
Changed(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract T RoundUp(T time, DifferenceIn diff);
|
||||
protected abstract T RoundDown(T time, DifferenceIn diff);
|
||||
|
||||
private bool differenceInited = false;
|
||||
private DifferenceIn difference;
|
||||
protected DifferenceIn Difference
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!differenceInited)
|
||||
{
|
||||
difference = GetDifferenceCore();
|
||||
differenceInited = true;
|
||||
}
|
||||
return difference;
|
||||
}
|
||||
}
|
||||
protected abstract DifferenceIn GetDifferenceCore();
|
||||
|
||||
private int[] tickCounts = null;
|
||||
protected int[] TickCounts
|
||||
{
|
||||
get
|
||||
{
|
||||
if (tickCounts == null)
|
||||
tickCounts = GetTickCountsCore();
|
||||
return tickCounts;
|
||||
}
|
||||
}
|
||||
protected abstract int[] GetTickCountsCore();
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (ticksCount > TickCounts[0]) return TickCounts[0];
|
||||
|
||||
for (int i = 0; i < TickCounts.Length; i++)
|
||||
if (ticksCount > TickCounts[i])
|
||||
return TickCounts[i];
|
||||
|
||||
return TickCounts.Last();
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (ticksCount >= TickCounts[0]) return TickCounts[0];
|
||||
|
||||
for (int i = TickCounts.Length - 1; i >= 0; i--)
|
||||
if (ticksCount < TickCounts[i])
|
||||
return TickCounts[i];
|
||||
|
||||
return TickCounts.Last();
|
||||
}
|
||||
|
||||
protected abstract int GetSpecificValue(T start, T dt);
|
||||
protected abstract T GetStart(T start, int value, int step);
|
||||
protected abstract bool IsMinDate(T dt);
|
||||
protected abstract T AddStep(T dt, int step);
|
||||
|
||||
public ITicksInfo<T> GetTicks(Range<T> range, int ticksCount)
|
||||
{
|
||||
T start = range.Min;
|
||||
T end = range.Max;
|
||||
DifferenceIn diff = Difference;
|
||||
start = RoundDown(start, end);
|
||||
end = RoundUp(start, end);
|
||||
|
||||
RoundingInfo bounds = RoundingHelper.CreateRoundedRange(
|
||||
GetSpecificValue(start, start),
|
||||
GetSpecificValue(start, end));
|
||||
|
||||
int delta = (int)(bounds.Max - bounds.Min);
|
||||
if (delta == 0)
|
||||
return new TicksInfo<T> { Ticks = new T[] { start } };
|
||||
|
||||
int step = delta / ticksCount;
|
||||
|
||||
if (step == 0) step = 1;
|
||||
|
||||
T tick = GetStart(start, (int)bounds.Min, step);
|
||||
bool isMinDateTime = IsMinDate(tick) && step != 1;
|
||||
if (isMinDateTime)
|
||||
step--;
|
||||
|
||||
List<T> ticks = new List<T>();
|
||||
T finishTick = AddStep(range.Max, step);
|
||||
while (Continue(tick, finishTick))
|
||||
{
|
||||
ticks.Add(tick);
|
||||
tick = AddStep(tick, step);
|
||||
if (isMinDateTime)
|
||||
{
|
||||
isMinDateTime = false;
|
||||
step++;
|
||||
}
|
||||
}
|
||||
|
||||
ticks = Trim(ticks, range);
|
||||
|
||||
TicksInfo<T> res = new TicksInfo<T> { Ticks = ticks.ToArray(), Info = diff };
|
||||
return res;
|
||||
}
|
||||
|
||||
protected abstract bool Continue(T current, T end);
|
||||
|
||||
protected abstract T RoundUp(T start, T end);
|
||||
|
||||
protected abstract T RoundDown(T start, T end);
|
||||
|
||||
protected abstract List<T> Trim(List<T> ticks, Range<T> range);
|
||||
|
||||
public ITicksProvider<T> MinorProvider
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MajorProvider
|
||||
{
|
||||
get { throw new NotSupportedException(); }
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class DatePeriodTicksProvider : TimePeriodTicksProvider<DateTime>
|
||||
{
|
||||
protected sealed override bool Continue(DateTime current, DateTime end)
|
||||
{
|
||||
return current < end;
|
||||
}
|
||||
|
||||
protected sealed override List<DateTime> Trim(List<DateTime> ticks, Range<DateTime> range)
|
||||
{
|
||||
int startIndex = 0;
|
||||
for (int i = 0; i < ticks.Count - 1; i++)
|
||||
{
|
||||
if (ticks[i] <= range.Min && range.Min <= ticks[i + 1])
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int endIndex = ticks.Count - 1;
|
||||
for (int i = ticks.Count - 1; i >= 1; i--)
|
||||
{
|
||||
if (ticks[i] >= range.Max && range.Max > ticks[i - 1])
|
||||
{
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<DateTime> res = new List<DateTime>(endIndex - startIndex + 1);
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
res.Add(ticks[i]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected sealed override DateTime RoundUp(DateTime start, DateTime end)
|
||||
{
|
||||
bool isPositive = (end - start).Ticks > 0;
|
||||
return isPositive ? SafelyRoundUp(end) : RoundDown(end, Difference);
|
||||
}
|
||||
|
||||
private DateTime SafelyRoundUp(DateTime dt)
|
||||
{
|
||||
if (AddStep(dt, 1) == DateTime.MaxValue)
|
||||
return DateTime.MaxValue;
|
||||
|
||||
return RoundUp(dt, Difference);
|
||||
}
|
||||
|
||||
protected sealed override DateTime RoundDown(DateTime start, DateTime end)
|
||||
{
|
||||
bool isPositive = (end - start).Ticks > 0;
|
||||
return isPositive ? RoundDown(start, Difference) : SafelyRoundUp(start);
|
||||
}
|
||||
|
||||
protected sealed override DateTime RoundDown(DateTime time, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = time;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = new DateTime(time.Year, 1, 1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = new DateTime(time.Year, time.Month, 1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = time.Date;
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = time.Date.AddHours(time.Hour);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = time.Date.AddHours(time.Hour).AddMinutes(time.Minute).AddSeconds(time.Second).AddMilliseconds(time.Millisecond);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
DebugVerify.Is(res <= time);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override DateTime RoundUp(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = RoundDown(dateTime, diff);
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Charts/Axes/DateTime/VerticalDateTimeAxis.cs
Normal file
23
Charts/Axes/DateTime/VerticalDateTimeAxis.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.ViewportRestrictions;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class VerticalDateTimeAxis : DateTimeAxis
|
||||
{
|
||||
public VerticalDateTimeAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
Restriction = new DateTimeVerticalAxisRestriction();
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
495
Charts/Axes/DateTimeTicksProvider.cs
Normal file
495
Charts/Axes/DateTimeTicksProvider.cs
Normal file
@@ -0,0 +1,495 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Globalization;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.NewAxis
|
||||
{
|
||||
public class DateTimeTicksProvider : DateTimeTicksProviderBase
|
||||
{
|
||||
private static readonly Dictionary<DifferenceIn, ITicksProvider<DateTime>> providers =
|
||||
new Dictionary<DifferenceIn, ITicksProvider<DateTime>>();
|
||||
|
||||
static DateTimeTicksProvider()
|
||||
{
|
||||
providers.Add(DifferenceIn.Year, new YearProvider());
|
||||
providers.Add(DifferenceIn.Month, new MonthProvider());
|
||||
providers.Add(DifferenceIn.Day, new DayProvider());
|
||||
providers.Add(DifferenceIn.Hour, new HourProvider());
|
||||
providers.Add(DifferenceIn.Minute, new MinuteProvider());
|
||||
providers.Add(DifferenceIn.Second, new SecondProvider());
|
||||
}
|
||||
|
||||
private DifferenceIn diff;
|
||||
/// <summary>
|
||||
/// Gets the ticks.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns></returns>
|
||||
public override ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount)
|
||||
{
|
||||
Verify.Is(ticksCount > 0);
|
||||
|
||||
DateTime start = range.Min;
|
||||
DateTime end = range.Max;
|
||||
TimeSpan length = end - start;
|
||||
|
||||
diff = GetDifference(length);
|
||||
|
||||
TicksInfo<DateTime> res = new TicksInfo<DateTime> { Info = diff };
|
||||
if (providers.ContainsKey(diff))
|
||||
{
|
||||
ITicksInfo<DateTime> result = providers[diff].GetTicks(range, ticksCount);
|
||||
DateTime[] mayorTicks = result.Ticks;
|
||||
|
||||
res.Ticks = mayorTicks;
|
||||
|
||||
DifferenceIn lowerDiff = DifferenceIn.Year;
|
||||
// todo разобраться с minor ticks
|
||||
bool lowerDiffExists = TryGetLowerDiff(diff, out lowerDiff);
|
||||
if (lowerDiffExists && providers.ContainsKey(lowerDiff))
|
||||
{
|
||||
var minorTicks = result.Ticks.GetPairs().Select(r => ((IMinorTicksProvider<DateTime>)providers[lowerDiff]).CreateTicks(r)).
|
||||
SelectMany(m => m).ToArray();
|
||||
|
||||
res.MinorTicks = minorTicks;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
DateTime newStart = RoundDown(start, diff);
|
||||
DateTime newEnd = RoundUp(end, diff);
|
||||
|
||||
DebugVerify.Is(newStart <= start);
|
||||
|
||||
List<DateTime> resultTicks = new List<DateTime>();
|
||||
DateTime dt = newStart;
|
||||
do
|
||||
{
|
||||
resultTicks.Add(dt);
|
||||
dt = Shift(dt, diff);
|
||||
} while (dt <= newEnd);
|
||||
|
||||
while (resultTicks.Count > ticksCount)
|
||||
{
|
||||
var res2 = resultTicks;
|
||||
resultTicks = res2.Where((date, i) => i % 2 == 0).ToList();
|
||||
}
|
||||
|
||||
res.Ticks = resultTicks.ToArray();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries the get lower diff.
|
||||
/// </summary>
|
||||
/// <param name="diff">The diff.</param>
|
||||
/// <param name="lowerDiff">The lower diff.</param>
|
||||
/// <returns></returns>
|
||||
private static bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff)
|
||||
{
|
||||
lowerDiff = diff;
|
||||
|
||||
int code = (int)diff;
|
||||
bool res = code > 0;
|
||||
if (res)
|
||||
{
|
||||
lowerDiff = (DifferenceIn)(code - 1);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the tick count.
|
||||
/// </summary>
|
||||
/// <param name="tickCount">The tick count.</param>
|
||||
/// <returns></returns>
|
||||
public override int DecreaseTickCount(int tickCount)
|
||||
{
|
||||
if (providers.ContainsKey(diff))
|
||||
return providers[diff].DecreaseTickCount(tickCount);
|
||||
|
||||
int res = tickCount / 2;
|
||||
if (res < 2) res = 2;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the tick count.
|
||||
/// </summary>
|
||||
/// <param name="tickCount">The tick count.</param>
|
||||
/// <returns></returns>
|
||||
public override int IncreaseTickCount(int tickCount)
|
||||
{
|
||||
DebugVerify.Is(tickCount < 2000);
|
||||
|
||||
if (providers.ContainsKey(diff))
|
||||
return providers[diff].IncreaseTickCount(tickCount);
|
||||
|
||||
return tickCount * 2;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DifferenceIn
|
||||
{
|
||||
Year = 7,
|
||||
Month = 6,
|
||||
Day = 5,
|
||||
Hour = 4,
|
||||
Minute = 3,
|
||||
Second = 2,
|
||||
Millisecond = 1
|
||||
}
|
||||
|
||||
internal static class DateTimeArrayExt
|
||||
{
|
||||
[Obsolete("Works wrongly", true)]
|
||||
internal static DateTime[] Clip(this DateTime[] array, DateTime start, DateTime end)
|
||||
{
|
||||
if (start > end)
|
||||
{
|
||||
DateTime temp = start;
|
||||
start = end;
|
||||
end = temp;
|
||||
}
|
||||
|
||||
int startIndex = array.GetIndex(start);
|
||||
int endIndex = array.GetIndex(end) + 1;
|
||||
DateTime[] res = new DateTime[endIndex - startIndex];
|
||||
Array.Copy(array, startIndex, res, 0, res.Length);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
internal static int GetIndex(this DateTime[] array, DateTime value)
|
||||
{
|
||||
for (int i = 0; i < array.Length - 1; i++)
|
||||
{
|
||||
if (array[i] <= value && value < array[i + 1])
|
||||
return i;
|
||||
}
|
||||
|
||||
return array.Length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
internal abstract class DatePeriodTicksProvider : DateTimeTicksProviderBase, IMinorTicksProvider<DateTime>
|
||||
{
|
||||
protected DatePeriodTicksProvider()
|
||||
{
|
||||
tickCounts = GetTickCountsCore();
|
||||
difference = GetDifferenceCore();
|
||||
}
|
||||
|
||||
protected DifferenceIn difference;
|
||||
protected abstract DifferenceIn GetDifferenceCore();
|
||||
|
||||
protected abstract int[] GetTickCountsCore();
|
||||
protected int[] tickCounts = { };
|
||||
|
||||
public sealed override int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (ticksCount > tickCounts[0]) return tickCounts[0];
|
||||
|
||||
for (int i = 0; i < tickCounts.Length; i++)
|
||||
if (ticksCount > tickCounts[i])
|
||||
return tickCounts[i];
|
||||
|
||||
return tickCounts.Last();
|
||||
}
|
||||
|
||||
public sealed override int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (ticksCount >= tickCounts[0]) return tickCounts[0];
|
||||
|
||||
for (int i = tickCounts.Length - 1; i >= 0; i--)
|
||||
if (ticksCount < tickCounts[i])
|
||||
return tickCounts[i];
|
||||
|
||||
return tickCounts.Last();
|
||||
}
|
||||
|
||||
protected abstract int GetSpecificValue(DateTime start, DateTime dt);
|
||||
protected abstract DateTime GetStart(DateTime start, int value, int step);
|
||||
protected abstract bool IsMinDate(DateTime dt);
|
||||
protected abstract DateTime AddStep(DateTime dt, int step);
|
||||
|
||||
public sealed override ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount)
|
||||
{
|
||||
DateTime start = range.Min;
|
||||
DateTime end = range.Max;
|
||||
TimeSpan length = end - start;
|
||||
|
||||
bool isPositive = length.Ticks > 0;
|
||||
DifferenceIn diff = difference;
|
||||
|
||||
DateTime newStart = isPositive ? RoundDown(start, diff) : SafelyRoundUp(start);
|
||||
DateTime newEnd = isPositive ? SafelyRoundUp(end) : RoundDown(end, diff);
|
||||
|
||||
RoundingInfo bounds = RoundHelper.CreateRoundedRange(GetSpecificValue(newStart, newStart), GetSpecificValue(newStart, newEnd));
|
||||
|
||||
int delta = (int)(bounds.Max - bounds.Min);
|
||||
if (delta == 0)
|
||||
return new TicksInfo<DateTime> { Ticks = new DateTime[] { newStart } };
|
||||
|
||||
int step = delta / ticksCount;
|
||||
|
||||
if (step == 0) step = 1;
|
||||
|
||||
DateTime tick = GetStart(newStart, (int)bounds.Min, step);
|
||||
bool isMinDateTime = IsMinDate(tick) && step != 1;
|
||||
if (isMinDateTime)
|
||||
step--;
|
||||
|
||||
List<DateTime> ticks = new List<DateTime>();
|
||||
DateTime finishTick = AddStep(range.Max, step);
|
||||
while (tick < finishTick)
|
||||
{
|
||||
ticks.Add(tick);
|
||||
tick = AddStep(tick, step);
|
||||
if (isMinDateTime)
|
||||
{
|
||||
isMinDateTime = false;
|
||||
step++;
|
||||
}
|
||||
}
|
||||
|
||||
TicksInfo<DateTime> res = new TicksInfo<DateTime> { Ticks = ticks.ToArray(), Info = diff };
|
||||
return res;
|
||||
}
|
||||
|
||||
private DateTime SafelyRoundUp(DateTime dt)
|
||||
{
|
||||
if (AddStep(dt, 1) == DateTime.MaxValue)
|
||||
return DateTime.MaxValue;
|
||||
|
||||
return RoundUp(dt, difference);
|
||||
}
|
||||
|
||||
#region IMinorTicksProvider<DateTime> Members
|
||||
|
||||
public MinorTickInfo<DateTime>[] CreateTicks(Range<DateTime> range)
|
||||
{
|
||||
int tickCount = tickCounts[1];
|
||||
ITicksInfo<DateTime> ticks = GetTicks(range, tickCount);
|
||||
|
||||
MinorTickInfo<DateTime>[] res = ticks.Ticks.
|
||||
Select(dt => new MinorTickInfo<DateTime>(0.5, dt)).ToArray();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal class YearProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Year;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 20, 10, 5, 4, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return dt.Year;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
int year = start.Year;
|
||||
int newYear = (year / step) * step;
|
||||
if (newYear == 0) newYear = 1;
|
||||
|
||||
return new DateTime(newYear, 1, 1);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Year == DateTime.MinValue.Year;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
if (dt.Year + step > DateTime.MaxValue.Year)
|
||||
return DateTime.MaxValue;
|
||||
|
||||
return dt.AddYears(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MonthProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Month;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return dt.Month + (dt.Year - start.Year) * 12;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return new DateTime(start.Year, 1, 1);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Month == DateTime.MinValue.Month;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMonths(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class DayProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Day;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 30, 15, 10, 5, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Days;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date;
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return dt.Day == 1;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddDays(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class HourProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Hour;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 24, 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Hours;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date;//.AddHours(start.Hour);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddHours(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class MinuteProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Minute;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Minutes;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddMinutes(step);
|
||||
}
|
||||
}
|
||||
|
||||
internal class SecondProvider : DatePeriodTicksProvider
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Second;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(DateTime start, DateTime dt)
|
||||
{
|
||||
return (dt - start).Seconds;
|
||||
}
|
||||
|
||||
protected override DateTime GetStart(DateTime start, int value, int step)
|
||||
{
|
||||
return start.Date.AddHours(start.Hour).AddMinutes(start.Minute);
|
||||
}
|
||||
|
||||
protected override bool IsMinDate(DateTime dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override DateTime AddStep(DateTime dt, int step)
|
||||
{
|
||||
return dt.AddSeconds(step);
|
||||
}
|
||||
}
|
||||
}
|
||||
145
Charts/Axes/DateTimeTicksProviderBase.cs
Normal file
145
Charts/Axes/DateTimeTicksProviderBase.cs
Normal file
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.NewAxis
|
||||
{
|
||||
public abstract class DateTimeTicksProviderBase : ITicksProvider<DateTime>
|
||||
{
|
||||
public abstract ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount);
|
||||
|
||||
public abstract int DecreaseTickCount(int ticksCount);
|
||||
|
||||
public abstract int IncreaseTickCount(int ticksCount);
|
||||
|
||||
protected static DifferenceIn GetDifference(TimeSpan span)
|
||||
{
|
||||
// for negative time spans
|
||||
span = span.Duration();
|
||||
|
||||
DifferenceIn diff;
|
||||
if (span.Days > 365)
|
||||
diff = DifferenceIn.Year;
|
||||
else if (span.Days > 30)
|
||||
diff = DifferenceIn.Month;
|
||||
else if (span.Days > 0)
|
||||
diff = DifferenceIn.Day;
|
||||
else if (span.Hours > 0)
|
||||
diff = DifferenceIn.Hour;
|
||||
else if (span.Minutes > 0)
|
||||
diff = DifferenceIn.Minute;
|
||||
else if (span.Seconds > 0)
|
||||
diff = DifferenceIn.Second;
|
||||
else
|
||||
diff = DifferenceIn.Millisecond;
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
protected static DateTime Shift(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = dateTime;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static DateTime RoundDown(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = dateTime;
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = new DateTime(dateTime.Year, 1, 1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = new DateTime(dateTime.Year, dateTime.Month, 1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = dateTime.Date;
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute).AddSeconds(dateTime.Second);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = dateTime.Date.AddHours(dateTime.Hour).AddMinutes(dateTime.Minute).AddSeconds(dateTime.Second).AddMilliseconds(dateTime.Millisecond);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
DebugVerify.Is(res <= dateTime);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected static DateTime RoundUp(DateTime dateTime, DifferenceIn diff)
|
||||
{
|
||||
DateTime res = RoundDown(dateTime, diff);
|
||||
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
res = res.AddYears(1);
|
||||
break;
|
||||
case DifferenceIn.Month:
|
||||
res = res.AddMonths(1);
|
||||
break;
|
||||
case DifferenceIn.Day:
|
||||
res = res.AddDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = res.AddHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = res.AddMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = res.AddSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = res.AddMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
109
Charts/Axes/DefaultAxisConversions.cs
Normal file
109
Charts/Axes/DefaultAxisConversions.cs
Normal file
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains default axis value conversions.
|
||||
/// </summary>
|
||||
public static class DefaultAxisConversions
|
||||
{
|
||||
#region double
|
||||
|
||||
private static readonly Func<double, double> doubleToDouble = d => d;
|
||||
public static Func<double, double> DoubleToDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.doubleToDouble; }
|
||||
}
|
||||
|
||||
private static readonly Func<double, double> doubleFromDouble = d => d;
|
||||
public static Func<double, double> DoubleFromDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.doubleFromDouble; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DateTime
|
||||
|
||||
private static readonly long minDateTimeTicks = DateTime.MinValue.Ticks;
|
||||
private static readonly long maxDateTimeTicks = DateTime.MaxValue.Ticks;
|
||||
private static readonly Func<double, System.DateTime> dateTimeFromDouble = d =>
|
||||
{
|
||||
long ticks = (long)(d * 10000000000L);
|
||||
|
||||
// todo should we throw an exception if number of ticks is too big or small?
|
||||
if (ticks < minDateTimeTicks)
|
||||
ticks = minDateTimeTicks;
|
||||
else if (ticks > maxDateTimeTicks)
|
||||
ticks = maxDateTimeTicks;
|
||||
|
||||
return new DateTime(ticks);
|
||||
};
|
||||
public static Func<double, DateTime> DateTimeFromDouble
|
||||
{
|
||||
get { return dateTimeFromDouble; }
|
||||
}
|
||||
|
||||
private static readonly Func<DateTime, double> dateTimeToDouble = dt => dt.Ticks / 10000000000.0;
|
||||
public static Func<DateTime, double> DateTimeToDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.dateTimeToDouble; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TimeSpan
|
||||
|
||||
private static readonly long minTimeSpanTicks = TimeSpan.MinValue.Ticks;
|
||||
private static readonly long maxTimeSpanTicks = TimeSpan.MaxValue.Ticks;
|
||||
|
||||
private static readonly Func<double, TimeSpan> timeSpanFromDouble = d =>
|
||||
{
|
||||
long ticks = (long)(d * 10000000000L);
|
||||
|
||||
// todo should we throw an exception if number of ticks is too big or small?
|
||||
if (ticks < minTimeSpanTicks)
|
||||
ticks = minTimeSpanTicks;
|
||||
else if (ticks > maxTimeSpanTicks)
|
||||
ticks = maxTimeSpanTicks;
|
||||
|
||||
return new TimeSpan(ticks);
|
||||
};
|
||||
|
||||
public static Func<double, TimeSpan> TimeSpanFromDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.timeSpanFromDouble; }
|
||||
}
|
||||
|
||||
private static readonly Func<TimeSpan, double> timeSpanToDouble = timeSpan =>
|
||||
{
|
||||
return timeSpan.Ticks / 10000000000.0;
|
||||
};
|
||||
|
||||
public static Func<TimeSpan, double> TimeSpanToDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.timeSpanToDouble; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region integer
|
||||
|
||||
private readonly static Func<double, int> intFromDouble = d => (int)d;
|
||||
public static Func<double, int> IntFromDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.intFromDouble; }
|
||||
}
|
||||
|
||||
private readonly static Func<int, double> intToDouble = i => (double)i;
|
||||
public static Func<int, double> IntToDouble
|
||||
{
|
||||
get { return DefaultAxisConversions.intToDouble; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
99
Charts/Axes/DefaultNumericTicksProvider.cs
Normal file
99
Charts/Axes/DefaultNumericTicksProvider.cs
Normal file
@@ -0,0 +1,99 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.NewAxis
|
||||
{
|
||||
public sealed class DefaultDoubleTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
public double[] GetTicks(Range<double> range, int preferredTicksCount)
|
||||
{
|
||||
double start = range.Min;
|
||||
double finish = range.Max;
|
||||
|
||||
double delta = finish - start;
|
||||
|
||||
int log = (int)Math.Round(Math.Log10(delta));
|
||||
|
||||
double newStart = Round(start, log);
|
||||
double newFinish = Round(finish, log);
|
||||
if (newStart == newFinish)
|
||||
{
|
||||
log--;
|
||||
newStart = Round(start, log);
|
||||
newFinish = Round(finish, log);
|
||||
}
|
||||
|
||||
double step = (newFinish - newStart) / preferredTicksCount;
|
||||
|
||||
//double[] ticks = CreateTicks(newStart, newFinish, preferredTicksCount);
|
||||
double[] ticks = CreateTicks(start, finish, step);
|
||||
return ticks;
|
||||
}
|
||||
|
||||
protected static double[] CreateTicks(double start, double finish, double step)
|
||||
{
|
||||
double x = step * (Math.Floor(start / step) + 1);
|
||||
List<double> res = new List<double>();
|
||||
while (x <= finish)
|
||||
{
|
||||
res.Add(x);
|
||||
x += step;
|
||||
}
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
//private static double[] CreateTicks(double start, double finish, int tickCount)
|
||||
//{
|
||||
// double[] ticks = new double[tickCount];
|
||||
// if (tickCount == 0)
|
||||
// return ticks;
|
||||
|
||||
// DebugVerify.Is(tickCount > 0);
|
||||
|
||||
// double delta = (finish - start) / (tickCount - 1);
|
||||
|
||||
// for (int i = 0; i < tickCount; i++)
|
||||
// {
|
||||
// ticks[i] = start + i * delta;
|
||||
// }
|
||||
|
||||
// return ticks;
|
||||
//}
|
||||
|
||||
private static double Round(double number, int rem)
|
||||
{
|
||||
if (rem <= 0)
|
||||
{
|
||||
return Math.Round(number, -rem);
|
||||
}
|
||||
else
|
||||
{
|
||||
double pow = Math.Pow(10, rem - 1);
|
||||
double val = pow * Math.Round(number / Math.Pow(10, rem - 1));
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
private static ReadOnlyCollection<int> TickCount =
|
||||
new ReadOnlyCollection<int>(new int[] { 20, 10, 5, 4, 2, 1 });
|
||||
|
||||
public const int DefaultPreferredTicksCount = 10;
|
||||
|
||||
public int DecreaseTickCount(int tickCount)
|
||||
{
|
||||
return TickCount.FirstOrDefault(tick => tick < tickCount);
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int tickCount) {
|
||||
int newTickCount = TickCount.Reverse().FirstOrDefault(tick => tick > tickCount);
|
||||
if (newTickCount == 0)
|
||||
newTickCount = TickCount[0];
|
||||
|
||||
return newTickCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Charts/Axes/DefaultTicksProvider.cs
Normal file
17
Charts/Axes/DefaultTicksProvider.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal static class DefaultTicksProvider
|
||||
{
|
||||
internal static readonly int DefaultTicksCount = 10;
|
||||
|
||||
internal static ITicksInfo<T> GetTicks<T>(this ITicksProvider<T> provider, Range<T> range)
|
||||
{
|
||||
return provider.GetTicks(range, DefaultTicksCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
142
Charts/Axes/GeneralAxis.cs
Normal file
142
Charts/Axes/GeneralAxis.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a base class for all DynamicDataDisplay's axes.
|
||||
/// Has several axis-specific and all WPF-specific properties.
|
||||
/// </summary>
|
||||
public abstract class GeneralAxis : ContentControl, IPlotterElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GeneralAxis"/> class.
|
||||
/// </summary>
|
||||
protected GeneralAxis() { }
|
||||
|
||||
#region Placement property
|
||||
|
||||
private AxisPlacement placement = AxisPlacement.Bottom;
|
||||
/// <summary>
|
||||
/// Gets or sets the placement of axis - place in ChartPlotter where it should be placed.
|
||||
/// </summary>
|
||||
/// <value>The placement.</value>
|
||||
public AxisPlacement Placement
|
||||
{
|
||||
get { return placement; }
|
||||
set
|
||||
{
|
||||
if (placement != value)
|
||||
{
|
||||
ValidatePlacement(value);
|
||||
AxisPlacement oldPlacement = placement;
|
||||
placement = value;
|
||||
OnPlacementChanged(oldPlacement, placement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnPlacementChanged(AxisPlacement oldPlacement, AxisPlacement newPlacement) { }
|
||||
|
||||
protected Panel GetPanelByPlacement(AxisPlacement placement)
|
||||
{
|
||||
Panel panel = null;
|
||||
switch (placement)
|
||||
{
|
||||
case AxisPlacement.Left:
|
||||
panel = ParentPlotter.LeftPanel;
|
||||
break;
|
||||
case AxisPlacement.Right:
|
||||
panel = ParentPlotter.RightPanel;
|
||||
break;
|
||||
case AxisPlacement.Top:
|
||||
panel = ParentPlotter.TopPanel;
|
||||
break;
|
||||
case AxisPlacement.Bottom:
|
||||
panel = ParentPlotter.BottomPanel;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return panel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the placement - e.g., vertical axis should not be placed from top or bottom, etc.
|
||||
/// If proposed placement is wrong, throws an ArgumentException.
|
||||
/// </summary>
|
||||
/// <param name="newPlacement">The new placement.</param>
|
||||
protected virtual void ValidatePlacement(AxisPlacement newPlacement) { }
|
||||
|
||||
#endregion
|
||||
|
||||
protected void RaiseTicksChanged()
|
||||
{
|
||||
TicksChanged.Raise(this);
|
||||
}
|
||||
|
||||
public abstract void ForceUpdate();
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when ticks changes.
|
||||
/// </summary>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public event EventHandler TicksChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen coordinates of axis ticks.
|
||||
/// </summary>
|
||||
/// <value>The screen ticks.</value>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract double[] ScreenTicks { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the screen coordinates of minor ticks.
|
||||
/// </summary>
|
||||
/// <value>The minor screen ticks.</value>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract MinorTickInfo<double>[] MinorScreenTicks { get; }
|
||||
|
||||
#region IPlotterElement Members
|
||||
|
||||
private Plotter2D plotter;
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
||||
public Plotter2D ParentPlotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
void IPlotterElement.OnPlotterAttached(Plotter plotter)
|
||||
{
|
||||
this.plotter = (Plotter2D)plotter;
|
||||
OnPlotterAttached(this.plotter);
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterAttached(Plotter2D plotter) { }
|
||||
|
||||
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
|
||||
{
|
||||
OnPlotterDetaching(this.plotter);
|
||||
this.plotter = null;
|
||||
}
|
||||
|
||||
protected virtual void OnPlotterDetaching(Plotter2D plotter) { }
|
||||
|
||||
public Plotter2D Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
Plotter IPlotterElement.Plotter
|
||||
{
|
||||
get { return plotter; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
55
Charts/Axes/GenericLabelProvider.cs
Normal file
55
Charts/Axes/GenericLabelProvider.cs
Normal file
@@ -0,0 +1,55 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents default implementation of label provider for specified type.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Axis values type.</typeparam>
|
||||
public class GenericLabelProvider<T> : LabelProviderBase<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericLabelProvider<T>"/> class.
|
||||
/// </summary>
|
||||
public GenericLabelProvider() { }
|
||||
|
||||
#region ILabelProvider<T> Members
|
||||
|
||||
/// <summary>
|
||||
/// Creates the labels by given ticks info.
|
||||
/// </summary>
|
||||
/// <param name="ticksInfo">The ticks info.</param>
|
||||
/// <returns>
|
||||
/// Array of <see cref="UIElement"/>s, which are axis labels for specified axis ticks.
|
||||
/// </returns>
|
||||
public override UIElement[] CreateLabels(ITicksInfo<T> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
var info = ticksInfo.Info;
|
||||
|
||||
LabelTickInfo<T> tickInfo = new LabelTickInfo<T>();
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
tickInfo.Tick = ticks[i];
|
||||
tickInfo.Info = info;
|
||||
|
||||
string text = GetString(tickInfo);
|
||||
|
||||
res[i] = new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
ToolTip = ticks[i].ToString()
|
||||
};
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.GenericLocational
|
||||
{
|
||||
public class GenericLocationalLabelProvider<TItem, TAxis> : LabelProviderBase<TAxis>
|
||||
{
|
||||
private readonly IList<TItem> collection;
|
||||
private readonly Func<TItem, string> displayMemberMapping;
|
||||
|
||||
public GenericLocationalLabelProvider(IList<TItem> collection, Func<TItem, string> displayMemberMapping)
|
||||
{
|
||||
if (collection == null)
|
||||
throw new ArgumentNullException("collection");
|
||||
if (displayMemberMapping == null)
|
||||
throw new ArgumentNullException("displayMemberMapping");
|
||||
|
||||
this.collection = collection;
|
||||
this.displayMemberMapping = displayMemberMapping;
|
||||
}
|
||||
|
||||
int startIndex;
|
||||
public override UIElement[] CreateLabels(ITicksInfo<TAxis> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
if (ticks.Length == 0)
|
||||
return EmptyLabelsArray;
|
||||
|
||||
startIndex = (int)ticksInfo.Info;
|
||||
|
||||
UIElement[] result = new UIElement[ticks.Length];
|
||||
|
||||
LabelTickInfo<TAxis> labelInfo = new LabelTickInfo<TAxis> { Info = ticksInfo.Info };
|
||||
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
var tick = ticks[i];
|
||||
labelInfo.Tick = tick;
|
||||
labelInfo.Index = i;
|
||||
|
||||
string labelText = GetString(labelInfo);
|
||||
|
||||
TextBlock label = new TextBlock { Text = labelText };
|
||||
|
||||
ApplyCustomView(labelInfo, label);
|
||||
|
||||
result[i] = label;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected override string GetStringCore(LabelTickInfo<TAxis> tickInfo)
|
||||
{
|
||||
return displayMemberMapping(collection[tickInfo.Index + startIndex]);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Charts/Axes/GenericLocational/GenericLocationalTicksProvider.cs
Normal file
146
Charts/Axes/GenericLocational/GenericLocationalTicksProvider.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.DataSearch;
|
||||
using System.Windows;
|
||||
using System.Collections;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.GenericLocational
|
||||
{
|
||||
public class GenericLocationalTicksProvider<TCollection, TAxis> : ITicksProvider<TAxis> where TAxis : IComparable<TAxis>
|
||||
{
|
||||
private IList<TCollection> collection;
|
||||
public IList<TCollection> Collection
|
||||
{
|
||||
get { return collection; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
Changed.Raise(this);
|
||||
collection = value;
|
||||
}
|
||||
}
|
||||
|
||||
private Func<TCollection, TAxis> axisMapping;
|
||||
public Func<TCollection, TAxis> AxisMapping
|
||||
{
|
||||
get { return axisMapping; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
Changed.Raise(this);
|
||||
axisMapping = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericLocationalTicksProvider<T>"/> class.
|
||||
/// </summary>
|
||||
public GenericLocationalTicksProvider() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="GenericLocationalTicksProvider<T>"/> class.
|
||||
/// </summary>
|
||||
/// <param name="collection">The collection of axis ticks and labels.</param>
|
||||
public GenericLocationalTicksProvider(IList<TCollection> collection)
|
||||
{
|
||||
Collection = collection;
|
||||
}
|
||||
|
||||
public GenericLocationalTicksProvider(IList<TCollection> collection, Func<TCollection, TAxis> coordinateMapping)
|
||||
{
|
||||
Collection = collection;
|
||||
AxisMapping = coordinateMapping;
|
||||
}
|
||||
|
||||
#region ITicksProvider<T> Members
|
||||
|
||||
SearchResult1d minResult = SearchResult1d.Empty;
|
||||
SearchResult1d maxResult = SearchResult1d.Empty;
|
||||
GenericSearcher1d<TCollection, TAxis> searcher;
|
||||
/// <summary>
|
||||
/// Generates ticks for given range and preferred ticks count.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns></returns>
|
||||
public ITicksInfo<TAxis> GetTicks(Range<TAxis> range, int ticksCount)
|
||||
{
|
||||
EnsureSearcher();
|
||||
|
||||
//minResult = searcher.SearchBetween(range.Min, minResult);
|
||||
//maxResult = searcher.SearchBetween(range.Max, maxResult);
|
||||
|
||||
minResult = searcher.SearchFirstLess(range.Min);
|
||||
maxResult = searcher.SearchGreater(range.Max);
|
||||
|
||||
if (!(minResult.IsEmpty && maxResult.IsEmpty))
|
||||
{
|
||||
int startIndex = !minResult.IsEmpty ? minResult.Index : 0;
|
||||
int endIndex = !maxResult.IsEmpty ? maxResult.Index : collection.Count - 1;
|
||||
|
||||
int count = endIndex - startIndex + 1;
|
||||
|
||||
TAxis[] ticks = new TAxis[count];
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
ticks[i - startIndex] = axisMapping(collection[i]);
|
||||
}
|
||||
|
||||
TicksInfo<TAxis> result = new TicksInfo<TAxis>
|
||||
{
|
||||
Info = startIndex,
|
||||
TickSizes = ArrayExtensions.CreateArray(count, 1.0),
|
||||
Ticks = ticks
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return TicksInfo<TAxis>.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
private void EnsureSearcher()
|
||||
{
|
||||
if (searcher == null)
|
||||
{
|
||||
if (collection == null || axisMapping == null)
|
||||
throw new InvalidOperationException(Strings.Exceptions.GenericLocationalProviderInvalidState);
|
||||
|
||||
searcher = new GenericSearcher1d<TCollection, TAxis>(collection, axisMapping);
|
||||
}
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return collection.Count;
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return collection.Count;
|
||||
}
|
||||
|
||||
public ITicksProvider<TAxis> MinorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public ITicksProvider<TAxis> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
153
Charts/Axes/ITicksProvider.cs
Normal file
153
Charts/Axes/ITicksProvider.cs
Normal file
@@ -0,0 +1,153 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains information about one minor tick - its value (relative size) and its tick.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
[DebuggerDisplay("{Value} @ {Tick}")]
|
||||
public struct MinorTickInfo<T>
|
||||
{
|
||||
internal MinorTickInfo(double value, T tick)
|
||||
{
|
||||
this.value = value;
|
||||
this.tick = tick;
|
||||
}
|
||||
|
||||
private readonly double value;
|
||||
private readonly T tick;
|
||||
|
||||
public double Value { get { return value; } }
|
||||
public T Tick { get { return tick; } }
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return String.Format("{0} @ {1}", value, tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains data for all generated ticks.
|
||||
/// Used by TicksLabelProvider.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of axis tick.</typeparam>
|
||||
public interface ITicksInfo<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the array of axis ticks.
|
||||
/// </summary>
|
||||
/// <value>The ticks.</value>
|
||||
T[] Ticks { get; }
|
||||
/// <summary>
|
||||
/// Gets the tick sizes.
|
||||
/// </summary>
|
||||
/// <value>The tick sizes.</value>
|
||||
double[] TickSizes { get; }
|
||||
/// <summary>
|
||||
/// Gets the additional information, added to ticks info and specifying range's features.
|
||||
/// </summary>
|
||||
/// <value>The info.</value>
|
||||
object Info { get; }
|
||||
}
|
||||
|
||||
internal class TicksInfo<T> : ITicksInfo<T>
|
||||
{
|
||||
private T[] ticks = { };
|
||||
/// <summary>
|
||||
/// Gets the array of axis ticks.
|
||||
/// </summary>
|
||||
/// <value>The ticks.</value>
|
||||
public T[] Ticks
|
||||
{
|
||||
get { return ticks; }
|
||||
internal set { ticks = value; }
|
||||
}
|
||||
|
||||
private double[] tickSizes = { };
|
||||
/// <summary>
|
||||
/// Gets the tick sizes.
|
||||
/// </summary>
|
||||
/// <value>The tick sizes.</value>
|
||||
public double[] TickSizes
|
||||
{
|
||||
get
|
||||
{
|
||||
if (tickSizes.Length != ticks.Length)
|
||||
tickSizes = ArrayExtensions.CreateArray(ticks.Length, 1.0);
|
||||
|
||||
return tickSizes;
|
||||
}
|
||||
internal set { tickSizes = value; }
|
||||
}
|
||||
|
||||
private object info = null;
|
||||
/// <summary>
|
||||
/// Gets the additional information, added to ticks info and specifying range's features.
|
||||
/// </summary>
|
||||
/// <value>The info.</value>
|
||||
public object Info
|
||||
{
|
||||
get { return info; }
|
||||
internal set { info = value; }
|
||||
}
|
||||
|
||||
private static readonly TicksInfo<T> empty = new TicksInfo<T> { info = null, ticks = new T[0], tickSizes = new double[0] };
|
||||
internal static TicksInfo<T> Empty
|
||||
{
|
||||
get { return empty; }
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base interface for ticks generator.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public interface ITicksProvider<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Generates ticks for given range and preferred ticks count.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns></returns>
|
||||
ITicksInfo<T> GetTicks(Range<T> range, int ticksCount);
|
||||
/// <summary>
|
||||
/// Decreases the tick count.
|
||||
/// Returned value should be later passed as ticksCount parameter to GetTicks method.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns>Decreased ticks count.</returns>
|
||||
int DecreaseTickCount(int ticksCount);
|
||||
/// <summary>
|
||||
/// Increases the tick count.
|
||||
/// Returned value should be later passed as ticksCount parameter to GetTicks method.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns>Increased ticks count.</returns>
|
||||
int IncreaseTickCount(int ticksCount);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minor ticks provider, used to generate ticks between each two adjacent ticks.
|
||||
/// </summary>
|
||||
/// <value>The minor provider. If there is no minor provider available, returns null.</value>
|
||||
ITicksProvider<T> MinorProvider { get; }
|
||||
/// <summary>
|
||||
/// Gets the major provider, used to generate major ticks - for example, years for common ticks as months.
|
||||
/// </summary>
|
||||
/// <value>The major provider. If there is no major provider available, returns null.</value>
|
||||
ITicksProvider<T> MajorProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when properties of ticks provider changeds.
|
||||
/// Notifies axis to rebuild its view.
|
||||
/// </summary>
|
||||
event EventHandler Changed;
|
||||
}
|
||||
}
|
||||
40
Charts/Axes/ITypedAxis.cs
Normal file
40
Charts/Axes/ITypedAxis.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes axis as having ticks type.
|
||||
/// Provides access to some typed properties.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Axis tick's type.</typeparam>
|
||||
public interface ITypedAxis<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the ticks provider.
|
||||
/// </summary>
|
||||
/// <value>The ticks provider.</value>
|
||||
ITicksProvider<T> TicksProvider { get; }
|
||||
/// <summary>
|
||||
/// Gets the label provider.
|
||||
/// </summary>
|
||||
/// <value>The label provider.</value>
|
||||
LabelProviderBase<T> LabelProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the convertion of tick from double.
|
||||
/// Should not be null.
|
||||
/// </summary>
|
||||
/// <value>The convert from double.</value>
|
||||
Func<double, T> ConvertFromDouble { get; set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the convertion of tick to double.
|
||||
/// Should not be null.
|
||||
/// </summary>
|
||||
/// <value>The convert to double.</value>
|
||||
Func<T, double> ConvertToDouble { get; set; }
|
||||
}
|
||||
}
|
||||
13
Charts/Axes/IValueConversion.cs
Normal file
13
Charts/Axes/IValueConversion.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public interface IValueConversion<T>
|
||||
{
|
||||
Func<T, double> ConvertToDouble { get; set; }
|
||||
Func<double, T> ConvertFromDouble { get; set; }
|
||||
}
|
||||
}
|
||||
107
Charts/Axes/Integer/CollectionLabelProvider.cs
Normal file
107
Charts/Axes/Integer/CollectionLabelProvider.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class CollectionLabelProvider<T> : LabelProviderBase<int>
|
||||
{
|
||||
private IList<T> collection;
|
||||
|
||||
[SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
|
||||
public IList<T> Collection
|
||||
{
|
||||
get { return collection; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
if (collection != value)
|
||||
{
|
||||
DetachCollection();
|
||||
|
||||
collection = value;
|
||||
|
||||
AttachCollection();
|
||||
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Collection changed
|
||||
|
||||
private void AttachCollection()
|
||||
{
|
||||
INotifyCollectionChanged observableCollection = collection as INotifyCollectionChanged;
|
||||
if (observableCollection != null)
|
||||
{
|
||||
observableCollection.CollectionChanged += OnCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
RaiseChanged();
|
||||
}
|
||||
|
||||
private void DetachCollection()
|
||||
{
|
||||
INotifyCollectionChanged observableCollection = collection as INotifyCollectionChanged;
|
||||
if (observableCollection != null)
|
||||
{
|
||||
observableCollection.CollectionChanged -= OnCollectionChanged;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CollectionLabelProvider<T>"/> class with empty labels collection.
|
||||
/// </summary>
|
||||
public CollectionLabelProvider() { }
|
||||
|
||||
public CollectionLabelProvider(IList<T> collection)
|
||||
: this()
|
||||
{
|
||||
Collection = collection;
|
||||
}
|
||||
|
||||
public override UIElement[] CreateLabels(ITicksInfo<int> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
|
||||
var tickInfo = new LabelTickInfo<int> { Info = ticksInfo.Info };
|
||||
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
int tick = ticks[i];
|
||||
tickInfo.Tick = tick;
|
||||
|
||||
if (0 <= tick && tick < collection.Count)
|
||||
{
|
||||
string text = collection[tick].ToString();
|
||||
res[i] = new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
ToolTip = text
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
res[i] = null;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Charts/Axes/Integer/HorizontalIntegerAxis.cs
Normal file
21
Charts/Axes/Integer/HorizontalIntegerAxis.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class HorizontalIntegerAxis : IntegerAxis
|
||||
{
|
||||
public HorizontalIntegerAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Bottom;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Left || newPlacement == AxisPlacement.Right)
|
||||
throw new ArgumentException(Strings.Exceptions.HorizontalAxisCannotBeVertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Charts/Axes/Integer/IntegerAxis.cs
Normal file
18
Charts/Axes/Integer/IntegerAxis.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class IntegerAxis : AxisBase<int>
|
||||
{
|
||||
public IntegerAxis()
|
||||
: base(new IntegerAxisControl(),
|
||||
d => (int)d,
|
||||
i => (double)i)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Charts/Axes/Integer/IntegerAxisControl.cs
Normal file
18
Charts/Axes/Integer/IntegerAxisControl.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class IntegerAxisControl : AxisControl<int>
|
||||
{
|
||||
public IntegerAxisControl()
|
||||
{
|
||||
LabelProvider = new GenericLabelProvider<int>();
|
||||
TicksProvider = new IntegerTicksProvider();
|
||||
ConvertToDouble = i => (double)i;
|
||||
Range = new Range<int>(0, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
189
Charts/Axes/Integer/IntegerTicksProvider.cs
Normal file
189
Charts/Axes/Integer/IntegerTicksProvider.cs
Normal file
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ticks provider for intefer values.
|
||||
/// </summary>
|
||||
public class IntegerTicksProvider : ITicksProvider<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IntegerTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public IntegerTicksProvider() { }
|
||||
|
||||
private int minStep = 0;
|
||||
/// <summary>
|
||||
/// Gets or sets the minimal step between ticks.
|
||||
/// </summary>
|
||||
/// <value>The min step.</value>
|
||||
public int MinStep
|
||||
{
|
||||
get { return minStep; }
|
||||
set
|
||||
{
|
||||
Verify.IsTrue(value >= 0, "value");
|
||||
if (minStep != value)
|
||||
{
|
||||
minStep = value;
|
||||
RaiseChangedEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int maxStep = Int32.MaxValue;
|
||||
/// <summary>
|
||||
/// Gets or sets the maximal step between ticks.
|
||||
/// </summary>
|
||||
/// <value>The max step.</value>
|
||||
public int MaxStep
|
||||
{
|
||||
get { return maxStep; }
|
||||
set
|
||||
{
|
||||
if (maxStep != value)
|
||||
{
|
||||
if (value < 0)
|
||||
throw new ArgumentOutOfRangeException("value", Strings.Exceptions.ParameterShouldBePositive);
|
||||
|
||||
maxStep = value;
|
||||
RaiseChangedEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region ITicksProvider<int> Members
|
||||
|
||||
/// <summary>
|
||||
/// Generates ticks for given range and preferred ticks count.
|
||||
/// </summary>
|
||||
/// <param name="range">The range.</param>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns></returns>
|
||||
public ITicksInfo<int> GetTicks(Range<int> range, int ticksCount)
|
||||
{
|
||||
double start = range.Min;
|
||||
double finish = range.Max;
|
||||
|
||||
double delta = finish - start;
|
||||
|
||||
int log = (int)Math.Round(Math.Log10(delta));
|
||||
|
||||
double newStart = RoundingHelper.Round(start, log);
|
||||
double newFinish = RoundingHelper.Round(finish, log);
|
||||
if (newStart == newFinish)
|
||||
{
|
||||
log--;
|
||||
newStart = RoundingHelper.Round(start, log);
|
||||
newFinish = RoundingHelper.Round(finish, log);
|
||||
}
|
||||
|
||||
// calculating step between ticks
|
||||
double unroundedStep = (newFinish - newStart) / ticksCount;
|
||||
int stepLog = log;
|
||||
// trying to round step
|
||||
int step = (int)RoundingHelper.Round(unroundedStep, stepLog);
|
||||
if (step == 0)
|
||||
{
|
||||
stepLog--;
|
||||
step = (int)RoundingHelper.Round(unroundedStep, stepLog);
|
||||
if (step == 0)
|
||||
{
|
||||
// step will not be rounded if attempts to be rounded to zero.
|
||||
step = (int)unroundedStep;
|
||||
}
|
||||
}
|
||||
|
||||
if (step < minStep)
|
||||
step = minStep;
|
||||
if (step > maxStep)
|
||||
step = maxStep;
|
||||
|
||||
if (step <= 0)
|
||||
step = 1;
|
||||
|
||||
int[] ticks = CreateTicks(start, finish, step);
|
||||
|
||||
TicksInfo<int> res = new TicksInfo<int> { Info = log, Ticks = ticks };
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static int[] CreateTicks(double start, double finish, int step)
|
||||
{
|
||||
DebugVerify.Is(step != 0);
|
||||
|
||||
int x = (int)(step * Math.Floor(start / (double)step));
|
||||
List<int> res = new List<int>();
|
||||
|
||||
checked
|
||||
{
|
||||
double increasedFinish = finish + step * 1.05;
|
||||
while (x <= increasedFinish)
|
||||
{
|
||||
res.Add(x);
|
||||
x += step;
|
||||
}
|
||||
}
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
private static int[] tickCounts = new int[] { 20, 10, 5, 4, 2, 1 };
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the tick count.
|
||||
/// Returned value should be later passed as ticksCount parameter to GetTicks method.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns>Decreased ticks count.</returns>
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return tickCounts.FirstOrDefault(tick => tick < ticksCount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the tick count.
|
||||
/// Returned value should be later passed as ticksCount parameter to GetTicks method.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The ticks count.</param>
|
||||
/// <returns>Increased ticks count.</returns>
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
int newTickCount = tickCounts.Reverse().FirstOrDefault(tick => tick > ticksCount);
|
||||
if (newTickCount == 0)
|
||||
newTickCount = tickCounts[0];
|
||||
|
||||
return newTickCount;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the minor ticks provider, used to generate ticks between each two adjacent ticks.
|
||||
/// </summary>
|
||||
/// <value>The minor provider.</value>
|
||||
public ITicksProvider<int> MinorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the major provider, used to generate major ticks - for example, years for common ticks as months.
|
||||
/// </summary>
|
||||
/// <value>The major provider.</value>
|
||||
public ITicksProvider<int> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
protected void RaiseChangedEvent()
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
21
Charts/Axes/Integer/VerticalIntegerAxis.cs
Normal file
21
Charts/Axes/Integer/VerticalIntegerAxis.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class VerticalIntegerAxis : IntegerAxis
|
||||
{
|
||||
public VerticalIntegerAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Charts/Axes/LabelProvider.cs
Normal file
43
Charts/Axes/LabelProvider.cs
Normal file
@@ -0,0 +1,43 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public abstract class LabelProvider<T> : LabelProviderBase<T>
|
||||
{
|
||||
public override UIElement[] CreateLabels(ITicksInfo<T> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
LabelTickInfo<T> labelInfo = new LabelTickInfo<T> { Info = ticksInfo.Info };
|
||||
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
labelInfo.Tick = ticks[i];
|
||||
labelInfo.Index = i;
|
||||
|
||||
string labelText = GetString(labelInfo);
|
||||
|
||||
TextBlock label = (TextBlock)GetResourceFromPool();
|
||||
if (label == null)
|
||||
{
|
||||
label = new TextBlock();
|
||||
}
|
||||
|
||||
label.Text = labelText;
|
||||
label.ToolTip = ticks[i].ToString();
|
||||
|
||||
res[i] = label;
|
||||
|
||||
ApplyCustomView(labelInfo, label);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
212
Charts/Axes/LabelProviderBase.cs
Normal file
212
Charts/Axes/LabelProviderBase.cs
Normal file
@@ -0,0 +1,212 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains data for custom generation of tick's label.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of ticks</typeparam>
|
||||
public sealed class LabelTickInfo<T>
|
||||
{
|
||||
internal LabelTickInfo() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the tick.
|
||||
/// </summary>
|
||||
/// <value>The tick.</value>
|
||||
public T Tick { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets or sets additional info about ticks range.
|
||||
/// </summary>
|
||||
/// <value>The info.</value>
|
||||
public object Info { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets or sets the index of tick in ticks array.
|
||||
/// </summary>
|
||||
/// <value>The index.</value>
|
||||
public int Index { get; internal set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all label providers.
|
||||
/// Contains a number of properties that can be used to adjust generated labels.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of ticks, which labels are generated for</typeparam>
|
||||
/// <remarks>
|
||||
/// Order of apllication of custom label string properties:
|
||||
/// If CustomFormatter is not null, it is called first.
|
||||
/// Then, if it was null or if it returned null string,
|
||||
/// virtual GetStringCore method is called. It can be overloaded in subclasses. GetStringCore should not return null.
|
||||
/// Then if LabelStringFormat is not null, it is applied.
|
||||
/// After label's UI was created, you can change it by setting CustomView delegate - it allows you to adjust
|
||||
/// UI properties of label. Note: not all labelProviders takes CustomView into account.
|
||||
/// </remarks>
|
||||
public abstract class LabelProviderBase<T>
|
||||
{
|
||||
|
||||
#region Private
|
||||
|
||||
private string labelStringFormat = null;
|
||||
private Func<LabelTickInfo<T>, string> customFormatter = null;
|
||||
private Action<LabelTickInfo<T>, UIElement> customView = null;
|
||||
|
||||
#endregion
|
||||
|
||||
private static readonly UIElement[] emptyLabelsArray = new UIElement[0];
|
||||
protected static UIElement[] EmptyLabelsArray
|
||||
{
|
||||
get { return emptyLabelsArray; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates labels by given ticks info.
|
||||
/// Is not intended to be called from your code.
|
||||
/// </summary>
|
||||
/// <param name="ticksInfo">The ticks info.</param>
|
||||
/// <returns>Array of <see cref="UIElement"/>s, which are axis labels for specified axis ticks.</returns>
|
||||
[EditorBrowsable(EditorBrowsableState.Never)]
|
||||
public abstract UIElement[] CreateLabels(ITicksInfo<T> ticksInfo);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the label string format.
|
||||
/// </summary>
|
||||
/// <value>The label string format.</value>
|
||||
public string LabelStringFormat
|
||||
{
|
||||
get { return labelStringFormat; }
|
||||
set
|
||||
{
|
||||
if (labelStringFormat != value)
|
||||
{
|
||||
labelStringFormat = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom formatter - delegate that can be called to create custom string representation of tick.
|
||||
/// </summary>
|
||||
/// <value>The custom formatter.</value>
|
||||
public Func<LabelTickInfo<T>, string> CustomFormatter
|
||||
{
|
||||
get { return customFormatter; }
|
||||
set
|
||||
{
|
||||
if (customFormatter != value)
|
||||
{
|
||||
customFormatter = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom view - delegate that is used to create a custom, non-default look of axis label.
|
||||
/// Can be used to adjust some UI properties of generated label.
|
||||
/// </summary>
|
||||
/// <value>The custom view.</value>
|
||||
public Action<LabelTickInfo<T>, UIElement> CustomView
|
||||
{
|
||||
get { return customView; }
|
||||
set
|
||||
{
|
||||
if (customView != value)
|
||||
{
|
||||
customView = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the custom formatter.
|
||||
/// This is alternative to CustomFormatter property setter, the only difference is that Visual Studio shows
|
||||
/// more convenient tooltip for methods rather than for properties' setters.
|
||||
/// </summary>
|
||||
/// <param name="formatter">The formatter.</param>
|
||||
public void SetCustomFormatter(Func<LabelTickInfo<T>, string> formatter)
|
||||
{
|
||||
CustomFormatter = formatter;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the custom view.
|
||||
/// This is alternative to CustomView property setter, the only difference is that Visual Studio shows
|
||||
/// more convenient tooltip for methods rather than for properties' setters.
|
||||
/// </summary>
|
||||
/// <param name="view">The view.</param>
|
||||
public void SetCustomView(Action<LabelTickInfo<T>, UIElement> view)
|
||||
{
|
||||
CustomView = view;
|
||||
}
|
||||
|
||||
protected virtual string GetString(LabelTickInfo<T> tickInfo)
|
||||
{
|
||||
string text = null;
|
||||
if (CustomFormatter != null)
|
||||
{
|
||||
text = CustomFormatter(tickInfo);
|
||||
}
|
||||
if (text == null)
|
||||
{
|
||||
text = GetStringCore(tickInfo);
|
||||
|
||||
if (text == null)
|
||||
throw new ArgumentNullException(Strings.Exceptions.TextOfTickShouldNotBeNull);
|
||||
}
|
||||
if (LabelStringFormat != null)
|
||||
{
|
||||
text = String.Format(LabelStringFormat, text);
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
protected virtual string GetStringCore(LabelTickInfo<T> tickInfo)
|
||||
{
|
||||
return tickInfo.Tick.ToString();
|
||||
}
|
||||
|
||||
protected void ApplyCustomView(LabelTickInfo<T> info, UIElement label)
|
||||
{
|
||||
if (CustomView != null)
|
||||
{
|
||||
CustomView(info, label);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Occurs when label provider is changed.
|
||||
/// Notifies axis to update its view.
|
||||
/// </summary>
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private readonly ResourcePool<UIElement> pool = new ResourcePool<UIElement>();
|
||||
internal void ReleaseLabel(UIElement label)
|
||||
{
|
||||
if (ReleaseCore(label))
|
||||
{
|
||||
pool.Put(label);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual bool ReleaseCore(UIElement label) { return false; }
|
||||
|
||||
protected UIElement GetResourceFromPool()
|
||||
{
|
||||
return pool.Get();
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Charts/Axes/LabelProviderProperties.cs
Normal file
27
Charts/Axes/LabelProviderProperties.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
internal class LabelProviderProperties : DependencyObject
|
||||
{
|
||||
public static bool GetExponentialIsCommonLabel(DependencyObject obj)
|
||||
{
|
||||
return (bool)obj.GetValue(ExponentialIsCommonLabelProperty);
|
||||
}
|
||||
|
||||
public static void SetExponentialIsCommonLabel(DependencyObject obj, bool value)
|
||||
{
|
||||
obj.SetValue(ExponentialIsCommonLabelProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ExponentialIsCommonLabelProperty = DependencyProperty.RegisterAttached(
|
||||
"ExponentialIsCommonLabel",
|
||||
typeof(bool),
|
||||
typeof(LabelProviderProperties),
|
||||
new FrameworkPropertyMetadata(true));
|
||||
}
|
||||
}
|
||||
92
Charts/Axes/Numeric/CustomBaseNumericLabelProvider.cs
Normal file
92
Charts/Axes/Numeric/CustomBaseNumericLabelProvider.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
public class CustomBaseNumericLabelProvider : LabelProvider<double>
|
||||
{
|
||||
private double customBase = 2;
|
||||
/// <summary>
|
||||
/// Gets or sets the custom base.
|
||||
/// </summary>
|
||||
/// <value>The custom base.</value>
|
||||
public double CustomBase
|
||||
{
|
||||
get { return customBase; }
|
||||
set
|
||||
{
|
||||
if (Double.IsNaN(value))
|
||||
throw new ArgumentException(Strings.Exceptions.CustomBaseTicksProviderBaseIsNaN);
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException(Strings.Exceptions.CustomBaseTicksProviderBaseIsTooSmall);
|
||||
|
||||
customBase = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public CustomBaseNumericLabelProvider() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public CustomBaseNumericLabelProvider(double customBase)
|
||||
: this()
|
||||
{
|
||||
CustomBase = customBase;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericLabelProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="customBase">The custom base.</param>
|
||||
/// <param name="customBaseString">The custom base string.</param>
|
||||
public CustomBaseNumericLabelProvider(double customBase, string customBaseString)
|
||||
: this(customBase)
|
||||
{
|
||||
CustomBaseString = customBaseString;
|
||||
}
|
||||
|
||||
private string customBaseString = null;
|
||||
/// <summary>
|
||||
/// Gets or sets the custom base string.
|
||||
/// </summary>
|
||||
/// <value>The custom base string.</value>
|
||||
public string CustomBaseString
|
||||
{
|
||||
get { return customBaseString; }
|
||||
set
|
||||
{
|
||||
if (customBaseString != value)
|
||||
{
|
||||
customBaseString = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override string GetStringCore(LabelTickInfo<double> tickInfo)
|
||||
{
|
||||
double value = tickInfo.Tick / customBase;
|
||||
|
||||
string customBaseStr = customBaseString ?? customBase.ToString();
|
||||
string result;
|
||||
if (value == 1)
|
||||
result = customBaseStr;
|
||||
else if (value == -1)
|
||||
{
|
||||
result = "-" + customBaseStr;
|
||||
}
|
||||
else
|
||||
result = value.ToString() + customBaseStr;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
202
Charts/Axes/Numeric/CustomBaseNumericTicksProvider.cs
Normal file
202
Charts/Axes/Numeric/CustomBaseNumericTicksProvider.cs
Normal file
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Windows.Markup;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
[ContentProperty("TicksProvider")]
|
||||
public class CustomBaseNumericTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
private double customBase = 2;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the custom base.
|
||||
/// </summary>
|
||||
/// <value>The custom base.</value>
|
||||
public double CustomBase
|
||||
{
|
||||
get { return customBase; }
|
||||
set
|
||||
{
|
||||
if (Double.IsNaN(value))
|
||||
throw new ArgumentException(Strings.Exceptions.CustomBaseTicksProviderBaseIsNaN);
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException(Strings.Exceptions.CustomBaseTicksProviderBaseIsTooSmall);
|
||||
|
||||
customBase = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public CustomBaseNumericTicksProvider() : this(2.0) { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CustomBaseNumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="customBase">The custom base, e.g. Math.PI</param>
|
||||
public CustomBaseNumericTicksProvider(double customBase) : this(customBase, new NumericTicksProvider()) { }
|
||||
|
||||
private CustomBaseNumericTicksProvider(double customBase, ITicksProvider<double> ticksProvider)
|
||||
{
|
||||
if (ticksProvider == null)
|
||||
throw new ArgumentNullException("ticksProvider");
|
||||
|
||||
CustomBase = customBase;
|
||||
|
||||
TicksProvider = ticksProvider;
|
||||
}
|
||||
|
||||
private void ticksProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private ITicksProvider<double> ticksProvider = null;
|
||||
public ITicksProvider<double> TicksProvider
|
||||
{
|
||||
get { return ticksProvider; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
if (ticksProvider != null)
|
||||
ticksProvider.Changed -= ticksProvider_Changed;
|
||||
ticksProvider = value;
|
||||
ticksProvider.Changed += ticksProvider_Changed;
|
||||
|
||||
if (minorTicksProvider != null)
|
||||
minorTicksProvider.Changed -= minorTicksProvider_Changed;
|
||||
minorTicksProvider = new MinorProviderWrapper(this);
|
||||
minorTicksProvider.Changed += minorTicksProvider_Changed;
|
||||
|
||||
Changed.Raise(this);
|
||||
}
|
||||
}
|
||||
|
||||
void minorTicksProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private Range<double> TransformRange(Range<double> range)
|
||||
{
|
||||
double min = range.Min / customBase;
|
||||
double max = range.Max / customBase;
|
||||
|
||||
return new Range<double>(min, max);
|
||||
}
|
||||
|
||||
#region ITicksProvider<double> Members
|
||||
|
||||
private double[] tickMarks;
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
var ticks = ticksProvider.GetTicks(TransformRange(range), ticksCount);
|
||||
|
||||
TransformTicks(ticks);
|
||||
|
||||
tickMarks = ticks.Ticks;
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
private void TransformTicks(ITicksInfo<double> ticks)
|
||||
{
|
||||
for (int i = 0; i < ticks.Ticks.Length; i++)
|
||||
{
|
||||
ticks.Ticks[i] *= customBase;
|
||||
}
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksProvider.DecreaseTickCount(ticksCount);
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksProvider.IncreaseTickCount(ticksCount);
|
||||
}
|
||||
|
||||
private ITicksProvider<double> minorTicksProvider;
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get { return minorTicksProvider; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the major provider, used to generate major ticks - for example, years for common ticks as months.
|
||||
/// </summary>
|
||||
/// <value>The major provider.</value>
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
|
||||
private sealed class MinorProviderWrapper : ITicksProvider<double>
|
||||
{
|
||||
private readonly CustomBaseNumericTicksProvider owner;
|
||||
|
||||
public MinorProviderWrapper(CustomBaseNumericTicksProvider owner)
|
||||
{
|
||||
this.owner = owner;
|
||||
|
||||
MinorTicksProvider.Changed += MinorTicksProvider_Changed;
|
||||
}
|
||||
|
||||
private void MinorTicksProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private ITicksProvider<double> MinorTicksProvider
|
||||
{
|
||||
get { return owner.ticksProvider.MinorProvider; }
|
||||
}
|
||||
|
||||
#region ITicksProvider<double> Members
|
||||
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
var minorProvider = MinorTicksProvider;
|
||||
var ticks = minorProvider.GetTicks(range, ticksCount);
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return MinorTicksProvider.DecreaseTickCount(ticksCount);
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return MinorTicksProvider.IncreaseTickCount(ticksCount);
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get { return MinorTicksProvider.MinorProvider; }
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return owner; }
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
92
Charts/Axes/Numeric/ExponentialLabelProvider.cs
Normal file
92
Charts/Axes/Numeric/ExponentialLabelProvider.cs
Normal file
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Documents;
|
||||
using System.Globalization;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents an axis label provider for double ticks, generating labels with numbers in exponential form when it is appropriate.
|
||||
/// </summary>
|
||||
public sealed class ExponentialLabelProvider : NumericLabelProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ExponentialLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public ExponentialLabelProvider() { }
|
||||
|
||||
/// <summary>
|
||||
/// Creates labels by given ticks info.
|
||||
/// Is not intended to be called from your code.
|
||||
/// </summary>
|
||||
/// <param name="ticksInfo">The ticks info.</param>
|
||||
/// <returns>
|
||||
/// Array of <see cref="UIElement"/>s, which are axis labels for specified axis ticks.
|
||||
/// </returns>
|
||||
public override UIElement[] CreateLabels(ITicksInfo<double> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
Init(ticks);
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
|
||||
LabelTickInfo<double> tickInfo = new LabelTickInfo<double> { Info = ticksInfo.Info };
|
||||
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
var tick = ticks[i];
|
||||
tickInfo.Tick = tick;
|
||||
tickInfo.Index = i;
|
||||
|
||||
string labelText = GetString(tickInfo);
|
||||
|
||||
TextBlock label;
|
||||
if (labelText.Contains('E'))
|
||||
{
|
||||
string[] substrs = labelText.Split('E');
|
||||
string mantissa = substrs[0];
|
||||
string exponenta = substrs[1];
|
||||
exponenta = exponenta.TrimStart('+');
|
||||
Span span = new Span();
|
||||
span.Inlines.Add(String.Format(CultureInfo.CurrentCulture, "{0}·10", mantissa));
|
||||
Span exponentaSpan = new Span(new Run(exponenta));
|
||||
exponentaSpan.BaselineAlignment = BaselineAlignment.Superscript;
|
||||
exponentaSpan.FontSize = 8;
|
||||
span.Inlines.Add(exponentaSpan);
|
||||
|
||||
label = new TextBlock(span);
|
||||
LabelProviderProperties.SetExponentialIsCommonLabel(label, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
label = (TextBlock)GetResourceFromPool();
|
||||
if (label == null)
|
||||
{
|
||||
label = new TextBlock();
|
||||
}
|
||||
|
||||
label.Text = labelText;
|
||||
}
|
||||
res[i] = label;
|
||||
label.ToolTip = tick.ToString(CultureInfo.CurrentCulture);
|
||||
|
||||
ApplyCustomView(tickInfo, label);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override bool ReleaseCore(UIElement label)
|
||||
{
|
||||
bool isNotExponential = LabelProviderProperties.GetExponentialIsCommonLabel(label);
|
||||
return isNotExponential && CustomView == null;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Charts/Axes/Numeric/HorizontalAxis.cs
Normal file
34
Charts/Axes/Numeric/HorizontalAxis.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a horizontal axis with values of <see cref="Double"/> type.
|
||||
/// Can be placed only from bottom or top side of plotter.
|
||||
/// By default is placed from the bottom side.
|
||||
/// </summary>
|
||||
public class HorizontalAxis : NumericAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HorizontalAxis"/> class, placed on bottom of <see cref="ChartPlotter"/>.
|
||||
/// </summary>
|
||||
public HorizontalAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Bottom;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the placement - e.g., vertical axis should not be placed from top or bottom, etc.
|
||||
/// If proposed placement is wrong, throws an ArgumentException.
|
||||
/// </summary>
|
||||
/// <param name="newPlacement">The new placement.</param>
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Left || newPlacement == AxisPlacement.Right)
|
||||
throw new ArgumentException(Strings.Exceptions.HorizontalAxisCannotBeVertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
131
Charts/Axes/Numeric/LogarithmNumericTicksProvider.cs
Normal file
131
Charts/Axes/Numeric/LogarithmNumericTicksProvider.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Markup;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ticks provider for logarithmically transfomed axis - returns ticks which are a power of specified logarithm base.
|
||||
/// </summary>
|
||||
public class LogarithmNumericTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LogarithmNumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public LogarithmNumericTicksProvider()
|
||||
{
|
||||
minorProvider = new MinorNumericTicksProvider(this);
|
||||
minorProvider.Changed += ticksProvider_Changed;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LogarithmNumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
/// <param name="logarithmBase">The logarithm base.</param>
|
||||
public LogarithmNumericTicksProvider(double logarithmBase)
|
||||
: this()
|
||||
{
|
||||
LogarithmBase = logarithmBase;
|
||||
}
|
||||
|
||||
private void ticksProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private double logarithmBase = 10;
|
||||
public double LogarithmBase
|
||||
{
|
||||
get { return logarithmBase; }
|
||||
set
|
||||
{
|
||||
if (value <= 0)
|
||||
throw new ArgumentOutOfRangeException(Strings.Exceptions.LogarithmBaseShouldBePositive);
|
||||
|
||||
logarithmBase = value;
|
||||
}
|
||||
}
|
||||
|
||||
private double LogByBase(double d)
|
||||
{
|
||||
return Math.Log10(d) / Math.Log10(logarithmBase);
|
||||
}
|
||||
|
||||
#region ITicksProvider<double> Members
|
||||
|
||||
private double[] ticks;
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
double min = LogByBase(range.Min);
|
||||
double max = LogByBase(range.Max);
|
||||
|
||||
double minDown = Math.Floor(min);
|
||||
double maxUp = Math.Ceiling(max);
|
||||
|
||||
double logLength = LogByBase(range.GetLength());
|
||||
|
||||
ticks = CreateTicks(range);
|
||||
|
||||
int log = RoundingHelper.GetDifferenceLog(range.Min, range.Max);
|
||||
TicksInfo<double> result = new TicksInfo<double> { Ticks = ticks, TickSizes = ArrayExtensions.CreateArray(ticks.Length, 1.0), Info = log };
|
||||
return result;
|
||||
}
|
||||
|
||||
private double[] CreateTicks(Range<double> range)
|
||||
{
|
||||
double min = LogByBase(range.Min);
|
||||
double max = LogByBase(range.Max);
|
||||
|
||||
double minDown = Math.Floor(min);
|
||||
double maxUp = Math.Ceiling(max);
|
||||
|
||||
int intStart = (int)Math.Floor(minDown);
|
||||
int count = (int)(maxUp - minDown + 1);
|
||||
|
||||
var ticks = new double[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ticks[i] = intStart + i;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ticks.Length; i++)
|
||||
{
|
||||
ticks[i] = Math.Pow(logarithmBase, ticks[i]);
|
||||
}
|
||||
|
||||
return ticks;
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksCount;
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksCount;
|
||||
}
|
||||
|
||||
private MinorNumericTicksProvider minorProvider;
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
minorProvider.SetRanges(ArrayExtensions.GetPairs(ticks));
|
||||
return minorProvider;
|
||||
}
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
89
Charts/Axes/Numeric/MinorNumericTicksProvider.cs
Normal file
89
Charts/Axes/Numeric/MinorNumericTicksProvider.cs
Normal file
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public sealed class MinorNumericTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
private readonly ITicksProvider<double> parent;
|
||||
private Range<double>[] ranges;
|
||||
internal void SetRanges(IEnumerable<Range<double>> ranges)
|
||||
{
|
||||
this.ranges = ranges.ToArray();
|
||||
}
|
||||
|
||||
private double[] coeffs;
|
||||
public double[] Coeffs
|
||||
{
|
||||
get { return coeffs; }
|
||||
set
|
||||
{
|
||||
if (value == null)
|
||||
throw new ArgumentNullException("value");
|
||||
|
||||
coeffs = value;
|
||||
Changed.Raise(this);
|
||||
}
|
||||
}
|
||||
|
||||
internal MinorNumericTicksProvider(ITicksProvider<double> parent)
|
||||
{
|
||||
this.parent = parent;
|
||||
Coeffs = new double[] { 0.3, 0.3, 0.3, 0.3, 0.6, 0.3, 0.3, 0.3, 0.3 };
|
||||
}
|
||||
|
||||
#region ITicksProvider<double> Members
|
||||
|
||||
public event EventHandler Changed;
|
||||
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
if (Coeffs.Length == 0)
|
||||
return new TicksInfo<double>();
|
||||
|
||||
var minorTicks = ranges.Select(r => CreateTicks(r)).SelectMany(m => m);
|
||||
var res = new TicksInfo<double>();
|
||||
res.TickSizes = minorTicks.Select(m => m.Value).ToArray();
|
||||
res.Ticks = minorTicks.Select(m => m.Tick).ToArray();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public MinorTickInfo<double>[] CreateTicks(Range<double> range)
|
||||
{
|
||||
double step = (range.Max - range.Min) / (Coeffs.Length + 1);
|
||||
|
||||
MinorTickInfo<double>[] res = new MinorTickInfo<double>[Coeffs.Length];
|
||||
for (int i = 0; i < Coeffs.Length; i++)
|
||||
{
|
||||
res[i] = new MinorTickInfo<double>(Coeffs[i], range.Min + step * (i + 1));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksCount;
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
return ticksCount;
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return parent; }
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
41
Charts/Axes/Numeric/NumericAxis.cs
Normal file
41
Charts/Axes/Numeric/NumericAxis.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a numeric axis with values of <see cref="System.Double"/> type.
|
||||
/// </summary>
|
||||
public class NumericAxis : AxisBase<double>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NumericAxis"/> class.
|
||||
/// </summary>
|
||||
public NumericAxis()
|
||||
: base(new NumericAxisControl(),
|
||||
d => d,
|
||||
d => d)
|
||||
{
|
||||
}
|
||||
|
||||
/// <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 override void SetConversion(double min, double minValue, double max, double maxValue)
|
||||
{
|
||||
var conversion = new NumericConversion(min, minValue, max, maxValue);
|
||||
|
||||
this.ConvertFromDouble = conversion.FromDouble;
|
||||
this.ConvertToDouble = conversion.ToDouble;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
Charts/Axes/Numeric/NumericAxisControl.cs
Normal file
18
Charts/Axes/Numeric/NumericAxisControl.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class NumericAxisControl : AxisControl<double>
|
||||
{
|
||||
public NumericAxisControl()
|
||||
{
|
||||
LabelProvider = new ExponentialLabelProvider();
|
||||
TicksProvider = new NumericTicksProvider();
|
||||
ConvertToDouble = d => d;
|
||||
Range = new Range<double>(0, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Charts/Axes/Numeric/NumericConversion.cs
Normal file
38
Charts/Axes/Numeric/NumericConversion.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
internal sealed class NumericConversion
|
||||
{
|
||||
private readonly double min;
|
||||
private readonly double length;
|
||||
private readonly double minValue;
|
||||
private readonly double valueLength;
|
||||
|
||||
public NumericConversion(double min, double minValue, double max, double maxValue)
|
||||
{
|
||||
this.min = min;
|
||||
this.length = max - min;
|
||||
|
||||
this.minValue = minValue;
|
||||
this.valueLength = maxValue - minValue;
|
||||
}
|
||||
|
||||
public double FromDouble(double value)
|
||||
{
|
||||
double ratio = (value - min) / length;
|
||||
|
||||
return minValue + ratio * valueLength;
|
||||
}
|
||||
|
||||
public double ToDouble(double value)
|
||||
{
|
||||
double ratio = (value - minValue) / valueLength;
|
||||
|
||||
return min + length * ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
Charts/Axes/Numeric/NumericLabelProviderBase.cs
Normal file
54
Charts/Axes/Numeric/NumericLabelProviderBase.cs
Normal file
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class NumericLabelProviderBase : LabelProviderBase<double>
|
||||
{
|
||||
bool shouldRound = true;
|
||||
private int rounding;
|
||||
protected void Init(double[] ticks)
|
||||
{
|
||||
if (ticks.Length == 0)
|
||||
return;
|
||||
|
||||
double start = ticks[0];
|
||||
double finish = ticks[ticks.Length - 1];
|
||||
|
||||
if (start == finish)
|
||||
{
|
||||
shouldRound = false;
|
||||
return;
|
||||
}
|
||||
|
||||
double delta = finish - start;
|
||||
|
||||
rounding = (int)Math.Round(Math.Log10(delta));
|
||||
|
||||
double newStart = RoundingHelper.Round(start, rounding);
|
||||
double newFinish = RoundingHelper.Round(finish, rounding);
|
||||
if (newStart == newFinish)
|
||||
rounding--;
|
||||
}
|
||||
|
||||
protected override string GetStringCore(LabelTickInfo<double> tickInfo)
|
||||
{
|
||||
string res;
|
||||
if (!shouldRound)
|
||||
{
|
||||
res = tickInfo.Tick.ToString();
|
||||
}
|
||||
else
|
||||
{
|
||||
int round = Math.Min(15, Math.Max(-15, rounding - 3)); // was rounding - 2
|
||||
res = RoundingHelper.Round(tickInfo.Tick, round).ToString();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
167
Charts/Axes/Numeric/NumericTicksProvider.cs
Normal file
167
Charts/Axes/Numeric/NumericTicksProvider.cs
Normal file
@@ -0,0 +1,167 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a ticks provider for <see cref="System.Double"/> values.
|
||||
/// </summary>
|
||||
public sealed class NumericTicksProvider : ITicksProvider<double>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="NumericTicksProvider"/> class.
|
||||
/// </summary>
|
||||
public NumericTicksProvider()
|
||||
{
|
||||
minorProvider = new MinorNumericTicksProvider(this);
|
||||
minorProvider.Changed += minorProvider_Changed;
|
||||
minorProvider.Coeffs = new double[] { 0.3, 0.3, 0.3, 0.3, 0.6, 0.3, 0.3, 0.3, 0.3 };
|
||||
}
|
||||
|
||||
private void minorProvider_Changed(object sender, EventArgs e)
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
public event EventHandler Changed;
|
||||
private void RaiseChangedEvent()
|
||||
{
|
||||
Changed.Raise(this);
|
||||
}
|
||||
|
||||
private double minStep = 0.0;
|
||||
/// <summary>
|
||||
/// Gets or sets the minimal step between ticks.
|
||||
/// </summary>
|
||||
/// <value>The min step.</value>
|
||||
public double MinStep
|
||||
{
|
||||
get { return minStep; }
|
||||
set
|
||||
{
|
||||
Verify.IsTrue(value >= 0.0, "value");
|
||||
if (minStep != value)
|
||||
{
|
||||
minStep = value;
|
||||
RaiseChangedEvent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private double[] ticks;
|
||||
public ITicksInfo<double> GetTicks(Range<double> range, int ticksCount)
|
||||
{
|
||||
double start = range.Min;
|
||||
double finish = range.Max;
|
||||
|
||||
double delta = finish - start;
|
||||
|
||||
int log = (int)Math.Round(Math.Log10(delta));
|
||||
|
||||
double newStart = RoundingHelper.Round(start, log);
|
||||
double newFinish = RoundingHelper.Round(finish, log);
|
||||
if (newStart == newFinish)
|
||||
{
|
||||
log--;
|
||||
newStart = RoundingHelper.Round(start, log);
|
||||
newFinish = RoundingHelper.Round(finish, log);
|
||||
}
|
||||
|
||||
// calculating step between ticks
|
||||
double unroundedStep = (newFinish - newStart) / ticksCount;
|
||||
int stepLog = log;
|
||||
// trying to round step
|
||||
double step = RoundingHelper.Round(unroundedStep, stepLog);
|
||||
if (step == 0)
|
||||
{
|
||||
stepLog--;
|
||||
step = RoundingHelper.Round(unroundedStep, stepLog);
|
||||
if (step == 0)
|
||||
{
|
||||
// step will not be rounded if attempts to be rounded to zero.
|
||||
step = unroundedStep;
|
||||
}
|
||||
}
|
||||
|
||||
if (step < minStep)
|
||||
step = minStep;
|
||||
|
||||
if (step != 0.0)
|
||||
{
|
||||
ticks = CreateTicks(start, finish, step);
|
||||
}
|
||||
else
|
||||
{
|
||||
ticks = new double[] { };
|
||||
}
|
||||
|
||||
TicksInfo<double> res = new TicksInfo<double> { Info = log, Ticks = ticks };
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static double[] CreateTicks(double start, double finish, double step)
|
||||
{
|
||||
DebugVerify.Is(step != 0.0);
|
||||
|
||||
double x = step * Math.Floor(start / step);
|
||||
|
||||
if (x == x + step)
|
||||
{
|
||||
return new double[0];
|
||||
}
|
||||
|
||||
List<double> res = new List<double>();
|
||||
|
||||
double increasedFinish = finish + step * 1.05;
|
||||
while (x <= increasedFinish)
|
||||
{
|
||||
res.Add(x);
|
||||
DebugVerify.Is(res.Count < 2000);
|
||||
x += step;
|
||||
}
|
||||
return res.ToArray();
|
||||
}
|
||||
|
||||
private static int[] tickCounts = new int[] { 20, 10, 5, 4, 2, 1 };
|
||||
|
||||
public const int DefaultPreferredTicksCount = 10;
|
||||
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
return tickCounts.FirstOrDefault(tick => tick < ticksCount);
|
||||
}
|
||||
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
int newTickCount = tickCounts.Reverse().FirstOrDefault(tick => tick > ticksCount);
|
||||
if (newTickCount == 0)
|
||||
newTickCount = tickCounts[0];
|
||||
|
||||
return newTickCount;
|
||||
}
|
||||
|
||||
private readonly MinorNumericTicksProvider minorProvider;
|
||||
public ITicksProvider<double> MinorProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ticks != null)
|
||||
{
|
||||
minorProvider.SetRanges(ticks.GetPairs());
|
||||
}
|
||||
|
||||
return minorProvider;
|
||||
}
|
||||
}
|
||||
|
||||
public ITicksProvider<double> MajorProvider
|
||||
{
|
||||
get { return null; }
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Charts/Axes/Numeric/ToStringLabelProvider.cs
Normal file
53
Charts/Axes/Numeric/ToStringLabelProvider.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a simple label provider for double ticks, which simply returns result of .ToString() method, called for rounded ticks.
|
||||
/// </summary>
|
||||
public class ToStringLabelProvider : NumericLabelProviderBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ToStringLabelProvider"/> class.
|
||||
/// </summary>
|
||||
public ToStringLabelProvider() { }
|
||||
|
||||
public override UIElement[] CreateLabels(ITicksInfo<double> ticksInfo)
|
||||
{
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
Init(ticks);
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
LabelTickInfo<double> tickInfo = new LabelTickInfo<double> { Info = ticksInfo.Info };
|
||||
for (int i = 0; i < res.Length; i++)
|
||||
{
|
||||
tickInfo.Tick = ticks[i];
|
||||
tickInfo.Index = i;
|
||||
|
||||
string labelText = GetString(tickInfo);
|
||||
|
||||
TextBlock label = (TextBlock)GetResourceFromPool();
|
||||
if (label == null)
|
||||
{
|
||||
label = new TextBlock();
|
||||
}
|
||||
|
||||
label.Text = labelText;
|
||||
label.ToolTip = ticks[i].ToString();
|
||||
|
||||
res[i] = label;
|
||||
|
||||
ApplyCustomView(tickInfo, label);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Charts/Axes/Numeric/UnroundingLabelProvider.cs
Normal file
11
Charts/Axes/Numeric/UnroundingLabelProvider.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes.Numeric
|
||||
{
|
||||
public class UnroundingLabelProvider : LabelProvider<double>
|
||||
{
|
||||
}
|
||||
}
|
||||
35
Charts/Axes/Numeric/VerticalAxis.cs
Normal file
35
Charts/Axes/Numeric/VerticalAxis.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a vertical axis with values of System.Double type.
|
||||
/// Can be placed only from left or right side of plotter.
|
||||
/// By default is placed from the left side.
|
||||
/// </summary>
|
||||
public class VerticalAxis : NumericAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VerticalAxis"/> class.
|
||||
/// </summary>
|
||||
public VerticalAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the placement - e.g., vertical axis should not be placed from top or bottom, etc.
|
||||
/// If proposed placement if wrong, throws an ArgumentException.
|
||||
/// </summary>
|
||||
/// <param name="newPlacement">The new placement.</param>
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Charts/Axes/Numeric/VerticalNumericAxis.cs
Normal file
21
Charts/Axes/Numeric/VerticalNumericAxis.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts.Axes
|
||||
{
|
||||
public class VerticalNumericAxis : NumericAxis
|
||||
{
|
||||
public VerticalNumericAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Charts/Axes/RoundingHelper.cs
Normal file
67
Charts/Axes/RoundingHelper.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal static class RoundingHelper
|
||||
{
|
||||
internal static int GetDifferenceLog(double min, double max)
|
||||
{
|
||||
return (int)Math.Round(Math.Log10(Math.Abs(max - min)));
|
||||
}
|
||||
|
||||
internal static double Round(double number, int rem)
|
||||
{
|
||||
if (rem <= 0)
|
||||
{
|
||||
rem = MathHelper.Clamp(-rem, 0, 15);
|
||||
return Math.Round(number, rem);
|
||||
}
|
||||
else
|
||||
{
|
||||
double pow = Math.Pow(10, rem - 1);
|
||||
double val = pow * Math.Round(number / Math.Pow(10, rem - 1));
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
internal static double Round(double value, Range<double> range)
|
||||
{
|
||||
int log = GetDifferenceLog(range.Min, range.Max);
|
||||
|
||||
return Round(value, log);
|
||||
}
|
||||
|
||||
internal static RoundingInfo CreateRoundedRange(double min, double max)
|
||||
{
|
||||
double delta = max - min;
|
||||
|
||||
if (delta == 0)
|
||||
return new RoundingInfo { Min = min, Max = max, Log = 0 };
|
||||
|
||||
int log = (int)Math.Round(Math.Log10(Math.Abs(delta))) + 1;
|
||||
|
||||
double newMin = Round(min, log);
|
||||
double newMax = Round(max, log);
|
||||
if (newMin == newMax)
|
||||
{
|
||||
log--;
|
||||
newMin = Round(min, log);
|
||||
newMax = Round(max, log);
|
||||
}
|
||||
|
||||
return new RoundingInfo { Min = newMin, Max = newMax, Log = log };
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("{Min} - {Max}, Log = {Log}")]
|
||||
internal sealed class RoundingInfo
|
||||
{
|
||||
public double Min { get; set; }
|
||||
public double Max { get; set; }
|
||||
public int Log { get; set; }
|
||||
}
|
||||
}
|
||||
237
Charts/Axes/StackCanvas.cs
Normal file
237
Charts/Axes/StackCanvas.cs
Normal file
@@ -0,0 +1,237 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
using System.Windows.Media;
|
||||
using System.Diagnostics;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class StackCanvas : Panel
|
||||
{
|
||||
public StackCanvas()
|
||||
{
|
||||
//ClipToBounds = true;
|
||||
}
|
||||
|
||||
#region EndCoordinate attached property
|
||||
|
||||
[AttachedPropertyBrowsableForChildren]
|
||||
public static double GetEndCoordinate(DependencyObject obj)
|
||||
{
|
||||
return (double)obj.GetValue(EndCoordinateProperty);
|
||||
}
|
||||
|
||||
public static void SetEndCoordinate(DependencyObject obj, double value)
|
||||
{
|
||||
obj.SetValue(EndCoordinateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty EndCoordinateProperty = DependencyProperty.RegisterAttached(
|
||||
"EndCoordinate",
|
||||
typeof(double),
|
||||
typeof(StackCanvas),
|
||||
new PropertyMetadata(Double.NaN, OnCoordinateChanged));
|
||||
|
||||
#endregion
|
||||
|
||||
#region Coordinate attached property
|
||||
|
||||
[AttachedPropertyBrowsableForChildren]
|
||||
public static double GetCoordinate(DependencyObject obj)
|
||||
{
|
||||
return (double)obj.GetValue(CoordinateProperty);
|
||||
}
|
||||
|
||||
public static void SetCoordinate(DependencyObject obj, double value)
|
||||
{
|
||||
obj.SetValue(CoordinateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty CoordinateProperty = DependencyProperty.RegisterAttached(
|
||||
"Coordinate",
|
||||
typeof(double),
|
||||
typeof(StackCanvas),
|
||||
new PropertyMetadata(0.0, OnCoordinateChanged));
|
||||
|
||||
private static void OnCoordinateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
UIElement reference = d as UIElement;
|
||||
if (reference != null)
|
||||
{
|
||||
StackCanvas parent = VisualTreeHelper.GetParent(reference) as StackCanvas;
|
||||
if (parent != null)
|
||||
{
|
||||
parent.InvalidateArrange();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region AxisPlacement property
|
||||
|
||||
public AxisPlacement Placement
|
||||
{
|
||||
get { return (AxisPlacement)GetValue(PlacementProperty); }
|
||||
set { SetValue(PlacementProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PlacementProperty =
|
||||
DependencyProperty.Register(
|
||||
"Placement",
|
||||
typeof(AxisPlacement),
|
||||
typeof(StackCanvas),
|
||||
new FrameworkPropertyMetadata(
|
||||
AxisPlacement.Bottom,
|
||||
FrameworkPropertyMetadataOptions.AffectsArrange));
|
||||
|
||||
#endregion
|
||||
|
||||
private bool IsHorizontal
|
||||
{
|
||||
get { return Placement == AxisPlacement.Top || Placement == AxisPlacement.Bottom; }
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size constraint)
|
||||
{
|
||||
Size availableSize = constraint;
|
||||
Size size = new Size();
|
||||
|
||||
bool isHorizontal = IsHorizontal;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
availableSize.Width = Double.PositiveInfinity;
|
||||
size.Width = constraint.Width;
|
||||
}
|
||||
else
|
||||
{
|
||||
availableSize.Height = Double.PositiveInfinity;
|
||||
size.Height = constraint.Height;
|
||||
}
|
||||
|
||||
// measuring all children and determinimg self width and height
|
||||
foreach (UIElement element in base.Children)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
Size childSize = GetChildSize(element, availableSize);
|
||||
element.Measure(childSize);
|
||||
Size desiredSize = element.DesiredSize;
|
||||
|
||||
if (isHorizontal)
|
||||
{
|
||||
size.Height = Math.Max(size.Height, desiredSize.Height);
|
||||
}
|
||||
else
|
||||
{
|
||||
size.Width = Math.Max(size.Width, desiredSize.Width);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Double.IsPositiveInfinity(size.Width)) size.Width = 0;
|
||||
if (Double.IsPositiveInfinity(size.Height)) size.Height = 0;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
private Size GetChildSize(UIElement element, Size availableSize)
|
||||
{
|
||||
var coordinate = GetCoordinate(element);
|
||||
var endCoordinate = GetEndCoordinate(element);
|
||||
|
||||
if (coordinate.IsNotNaN() && endCoordinate.IsNotNaN())
|
||||
{
|
||||
if (Placement.IsBottomOrTop())
|
||||
{
|
||||
availableSize.Width = endCoordinate - coordinate;
|
||||
}
|
||||
else
|
||||
{
|
||||
availableSize.Height = endCoordinate - coordinate;
|
||||
}
|
||||
}
|
||||
|
||||
return availableSize;
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
bool isHorizontal = IsHorizontal;
|
||||
|
||||
foreach (FrameworkElement element in base.Children)
|
||||
{
|
||||
if (element == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Size elementSize = element.DesiredSize;
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
|
||||
switch (Placement)
|
||||
{
|
||||
case AxisPlacement.Left:
|
||||
x = finalSize.Width - elementSize.Width;
|
||||
break;
|
||||
case AxisPlacement.Right:
|
||||
x = 0;
|
||||
break;
|
||||
case AxisPlacement.Top:
|
||||
y = finalSize.Height - elementSize.Height;
|
||||
break;
|
||||
case AxisPlacement.Bottom:
|
||||
y = 0;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
double coordinate = GetCoordinate(element);
|
||||
|
||||
if (!Double.IsNaN(GetEndCoordinate(element)))
|
||||
{
|
||||
double endCoordinate = GetEndCoordinate(element);
|
||||
double size = endCoordinate - coordinate;
|
||||
if (size < 0)
|
||||
{
|
||||
size = -size;
|
||||
coordinate -= size;
|
||||
}
|
||||
if (isHorizontal)
|
||||
elementSize.Width = size;
|
||||
else
|
||||
elementSize.Height = size;
|
||||
}
|
||||
|
||||
|
||||
// shift for common tick labels, not for major ones.
|
||||
if (isHorizontal)
|
||||
{
|
||||
x = coordinate;
|
||||
if (element.HorizontalAlignment == HorizontalAlignment.Center)
|
||||
x = coordinate - elementSize.Width / 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (element.VerticalAlignment == VerticalAlignment.Center)
|
||||
y = coordinate - elementSize.Height / 2;
|
||||
else if (element.VerticalAlignment == VerticalAlignment.Bottom)
|
||||
y = coordinate - elementSize.Height;
|
||||
else if (element.VerticalAlignment == VerticalAlignment.Top)
|
||||
y = coordinate;
|
||||
}
|
||||
|
||||
Rect bounds = new Rect(new Point(x, y), elementSize);
|
||||
element.Arrange(bounds);
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Charts/Axes/TimeSpan/HorizontalTimeSpanAxis.cs
Normal file
27
Charts/Axes/TimeSpan/HorizontalTimeSpanAxis.cs
Normal file
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a horizontal axis with values of <see cref="TimeSpan"/> type.
|
||||
/// </summary>
|
||||
public class HorizontalTimeSpanAxis : TimeSpanAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HorizontalTimeSpanAxis"/> class, placed on the bottom of <see cref="ChartPlotter"/>.
|
||||
/// </summary>
|
||||
public HorizontalTimeSpanAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Bottom;
|
||||
}
|
||||
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Left || newPlacement == AxisPlacement.Right)
|
||||
throw new ArgumentException(Strings.Exceptions.HorizontalAxisCannotBeVertical);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Charts/Axes/TimeSpan/MinorTimeSpanProvider.cs
Normal file
17
Charts/Axes/TimeSpan/MinorTimeSpanProvider.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal sealed class MinorTimeSpanTicksProvider : MinorTimeProviderBase<TimeSpan>
|
||||
{
|
||||
public MinorTimeSpanTicksProvider(ITicksProvider<TimeSpan> owner) : base(owner) { }
|
||||
|
||||
protected override bool IsInside(TimeSpan value, Range<TimeSpan> range)
|
||||
{
|
||||
return range.Min < value && value < range.Max;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
Charts/Axes/TimeSpan/TimeSpanAxis.cs
Normal file
57
Charts/Axes/TimeSpan/TimeSpanAxis.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents axis with values of type TimeSpan.
|
||||
/// </summary>
|
||||
public class TimeSpanAxis : AxisBase<TimeSpan>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TimeSpanAxis"/> class with default values conversion.
|
||||
/// </summary>
|
||||
public TimeSpanAxis()
|
||||
: base(new TimeSpanAxisControl(),
|
||||
DoubleToTimeSpan, TimeSpanToDouble)
|
||||
{ }
|
||||
|
||||
private static readonly long minTicks = TimeSpan.MinValue.Ticks;
|
||||
private static readonly long maxTicks = TimeSpan.MaxValue.Ticks;
|
||||
private static TimeSpan DoubleToTimeSpan(double value)
|
||||
{
|
||||
long ticks = (long)(value * 10000000000L);
|
||||
|
||||
// todo should we throw an exception if number of ticks is too big or small?
|
||||
if (ticks < minTicks)
|
||||
ticks = minTicks;
|
||||
else if (ticks > maxTicks)
|
||||
ticks = maxTicks;
|
||||
|
||||
return new TimeSpan(ticks);
|
||||
}
|
||||
|
||||
private static double TimeSpanToDouble(TimeSpan time)
|
||||
{
|
||||
return time.Ticks / 10000000000.0;
|
||||
}
|
||||
|
||||
/// <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 override void SetConversion(double min, TimeSpan minValue, double max, TimeSpan maxValue)
|
||||
{
|
||||
var conversion = new TimeSpanToDoubleConversion(min, minValue, max, maxValue);
|
||||
|
||||
ConvertToDouble = conversion.ToDouble;
|
||||
ConvertFromDouble = conversion.FromDouble;
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Charts/Axes/TimeSpan/TimeSpanAxisControl.cs
Normal file
20
Charts/Axes/TimeSpan/TimeSpanAxisControl.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class TimeSpanAxisControl : AxisControl<TimeSpan>
|
||||
{
|
||||
public TimeSpanAxisControl()
|
||||
{
|
||||
LabelProvider = new TimeSpanLabelProvider();
|
||||
TicksProvider = new TimeSpanTicksProvider();
|
||||
|
||||
ConvertToDouble = time => time.Ticks;
|
||||
|
||||
Range = new Range<TimeSpan>(new TimeSpan(), new TimeSpan(1, 0, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Charts/Axes/TimeSpan/TimeSpanLabelProvider.cs
Normal file
33
Charts/Axes/TimeSpan/TimeSpanLabelProvider.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using Microsoft.Research.DynamicDataDisplay.Charts.Axes;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class TimeSpanLabelProvider : LabelProviderBase<TimeSpan>
|
||||
{
|
||||
public override UIElement[] CreateLabels(ITicksInfo<TimeSpan> ticksInfo)
|
||||
{
|
||||
object info = ticksInfo.Info;
|
||||
var ticks = ticksInfo.Ticks;
|
||||
|
||||
LabelTickInfo<TimeSpan> tickInfo = new LabelTickInfo<TimeSpan>();
|
||||
|
||||
UIElement[] res = new UIElement[ticks.Length];
|
||||
for (int i = 0; i < ticks.Length; i++)
|
||||
{
|
||||
tickInfo.Tick = ticks[i];
|
||||
tickInfo.Info = info;
|
||||
|
||||
string tickText = GetString(tickInfo);
|
||||
UIElement label = new TextBlock { Text = tickText, ToolTip = ticks[i] };
|
||||
res[i] = label;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Charts/Axes/TimeSpan/TimeSpanTicksProvider.cs
Normal file
34
Charts/Axes/TimeSpan/TimeSpanTicksProvider.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public class TimeSpanTicksProvider : TimeTicksProviderBase<TimeSpan>
|
||||
{
|
||||
static TimeSpanTicksProvider()
|
||||
{
|
||||
Providers.Add(DifferenceIn.Year, new DayTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Month, new DayTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Day, new DayTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Hour, new HourTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Minute, new MinuteTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Second, new SecondTimeSpanProvider());
|
||||
Providers.Add(DifferenceIn.Millisecond, new MillisecondTimeSpanProvider());
|
||||
|
||||
MinorProviders.Add(DifferenceIn.Year, new MinorTimeSpanTicksProvider(new DayTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Month, new MinorTimeSpanTicksProvider(new DayTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Day, new MinorTimeSpanTicksProvider(new DayTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Hour, new MinorTimeSpanTicksProvider(new HourTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Minute, new MinorTimeSpanTicksProvider(new MinuteTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Second, new MinorTimeSpanTicksProvider(new SecondTimeSpanProvider()));
|
||||
MinorProviders.Add(DifferenceIn.Millisecond, new MinorTimeSpanTicksProvider(new MillisecondTimeSpanProvider()));
|
||||
}
|
||||
|
||||
protected override TimeSpan GetDifference(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
return end - start;
|
||||
}
|
||||
}
|
||||
}
|
||||
319
Charts/Axes/TimeSpan/TimeSpanTicksProviderBase.cs
Normal file
319
Charts/Axes/TimeSpan/TimeSpanTicksProviderBase.cs
Normal file
@@ -0,0 +1,319 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal abstract class TimeSpanTicksProviderBase : TimePeriodTicksProvider<TimeSpan>
|
||||
{
|
||||
protected sealed override bool Continue(TimeSpan current, TimeSpan end)
|
||||
{
|
||||
return current < end;
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan RoundDown(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
return RoundDown(start, Difference);
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan RoundUp(TimeSpan start, TimeSpan end)
|
||||
{
|
||||
return RoundUp(end, Difference);
|
||||
}
|
||||
|
||||
protected static TimeSpan Shift(TimeSpan span, DifferenceIn diff)
|
||||
{
|
||||
TimeSpan res = span;
|
||||
|
||||
TimeSpan shift = new TimeSpan();
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
case DifferenceIn.Month:
|
||||
case DifferenceIn.Day:
|
||||
shift = TimeSpan.FromDays(1);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
shift = TimeSpan.FromHours(1);
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
shift = TimeSpan.FromMinutes(1);
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
shift = TimeSpan.FromSeconds(1);
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
shift = TimeSpan.FromMilliseconds(1);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
res = res.Add(shift);
|
||||
return res;
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan RoundDown(TimeSpan timeSpan, DifferenceIn diff)
|
||||
{
|
||||
TimeSpan res = timeSpan;
|
||||
|
||||
if (timeSpan.Ticks < 0)
|
||||
{
|
||||
res = RoundUp(timeSpan.Duration(), diff).Negate();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (diff)
|
||||
{
|
||||
case DifferenceIn.Year:
|
||||
case DifferenceIn.Month:
|
||||
case DifferenceIn.Day:
|
||||
res = TimeSpan.FromDays(timeSpan.Days);
|
||||
break;
|
||||
case DifferenceIn.Hour:
|
||||
res = TimeSpan.FromDays(timeSpan.Days).
|
||||
Add(TimeSpan.FromHours(timeSpan.Hours));
|
||||
break;
|
||||
case DifferenceIn.Minute:
|
||||
res = TimeSpan.FromDays(timeSpan.Days).
|
||||
Add(TimeSpan.FromHours(timeSpan.Hours)).
|
||||
Add(TimeSpan.FromMinutes(timeSpan.Minutes));
|
||||
break;
|
||||
case DifferenceIn.Second:
|
||||
res = TimeSpan.FromDays(timeSpan.Days).
|
||||
Add(TimeSpan.FromHours(timeSpan.Hours)).
|
||||
Add(TimeSpan.FromMinutes(timeSpan.Minutes)).
|
||||
Add(TimeSpan.FromSeconds(timeSpan.Seconds));
|
||||
break;
|
||||
case DifferenceIn.Millisecond:
|
||||
res = timeSpan;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected sealed override TimeSpan RoundUp(TimeSpan dateTime, DifferenceIn diff)
|
||||
{
|
||||
TimeSpan res = RoundDown(dateTime, diff);
|
||||
res = Shift(res, diff);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected override List<TimeSpan> Trim(List<TimeSpan> ticks, Range<TimeSpan> range)
|
||||
{
|
||||
int startIndex = 0;
|
||||
for (int i = 0; i < ticks.Count - 1; i++)
|
||||
{
|
||||
if (ticks[i] <= range.Min && range.Min <= ticks[i + 1])
|
||||
{
|
||||
startIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int endIndex = ticks.Count - 1;
|
||||
for (int i = ticks.Count - 1; i >= 1; i--)
|
||||
{
|
||||
if (ticks[i] >= range.Max && range.Max > ticks[i - 1])
|
||||
{
|
||||
endIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<TimeSpan> res = new List<TimeSpan>(endIndex - startIndex + 1);
|
||||
for (int i = startIndex; i <= endIndex; i++)
|
||||
{
|
||||
res.Add(ticks[i]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected sealed override bool IsMinDate(TimeSpan dt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class DayTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Day;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 20, 10, 5, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (dt - start).Days;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double days = start.TotalDays;
|
||||
double newDays = ((int)(days / step)) * step;
|
||||
if (newDays > days) {
|
||||
newDays -= step;
|
||||
}
|
||||
return TimeSpan.FromDays(newDays);
|
||||
//return TimeSpan.FromDays(start.Days);
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromDays(step));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class HourTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Hour;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 24, 12, 6, 4, 3, 2, 1 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (int)(dt - start).TotalHours;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double hours = start.TotalHours;
|
||||
double newHours = ((int)(hours / step)) * step;
|
||||
if (newHours > hours)
|
||||
{
|
||||
newHours -= step;
|
||||
}
|
||||
return TimeSpan.FromHours(newHours);
|
||||
//return TimeSpan.FromDays(start.Days);
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromHours(step));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MinuteTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Minute;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (int)(dt - start).TotalMinutes;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double minutes = start.TotalMinutes;
|
||||
double newMinutes = ((int)(minutes / step)) * step;
|
||||
if (newMinutes > minutes)
|
||||
{
|
||||
newMinutes -= step;
|
||||
}
|
||||
|
||||
return TimeSpan.FromMinutes(newMinutes);
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromMinutes(step));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class SecondTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Second;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (int)(dt - start).TotalSeconds;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double seconds = start.TotalSeconds;
|
||||
double newSeconds = ((int)(seconds / step)) * step;
|
||||
if (newSeconds > seconds) {
|
||||
newSeconds -= step;
|
||||
}
|
||||
|
||||
return TimeSpan.FromSeconds(newSeconds);
|
||||
//return new TimeSpan(start.Days, start.Hours, start.Minutes, 0);
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromSeconds(step));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class MillisecondTimeSpanProvider : TimeSpanTicksProviderBase
|
||||
{
|
||||
protected override DifferenceIn GetDifferenceCore()
|
||||
{
|
||||
return DifferenceIn.Millisecond;
|
||||
}
|
||||
|
||||
protected override int[] GetTickCountsCore()
|
||||
{
|
||||
return new int[] { 100, 50, 40, 25, 20, 10, 5, 4, 2 };
|
||||
}
|
||||
|
||||
protected override int GetSpecificValue(TimeSpan start, TimeSpan dt)
|
||||
{
|
||||
return (int)(dt - start).TotalMilliseconds;
|
||||
}
|
||||
|
||||
protected override TimeSpan GetStart(TimeSpan start, int value, int step)
|
||||
{
|
||||
double millis = start.TotalMilliseconds;
|
||||
double newMillis = ((int)(millis / step)) * step;
|
||||
if (newMillis > millis) {
|
||||
newMillis -= step;
|
||||
}
|
||||
|
||||
return TimeSpan.FromMilliseconds(newMillis);
|
||||
//return start;
|
||||
}
|
||||
|
||||
protected override TimeSpan AddStep(TimeSpan dt, int step)
|
||||
{
|
||||
return dt.Add(TimeSpan.FromMilliseconds(step));
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Charts/Axes/TimeSpan/TimeSpanToDoubleConversion.cs
Normal file
44
Charts/Axes/TimeSpan/TimeSpanToDoubleConversion.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
internal sealed class TimeSpanToDoubleConversion
|
||||
{
|
||||
public TimeSpanToDoubleConversion(TimeSpan minSpan, TimeSpan maxSpan)
|
||||
: this(0, minSpan, 1, maxSpan)
|
||||
{ }
|
||||
|
||||
public TimeSpanToDoubleConversion(double min, TimeSpan minSpan, double max, TimeSpan maxSpan)
|
||||
{
|
||||
this.min = min;
|
||||
this.length = max - min;
|
||||
this.ticksMin = minSpan.Ticks;
|
||||
this.ticksLength = maxSpan.Ticks - ticksMin;
|
||||
}
|
||||
|
||||
private double min;
|
||||
private double length;
|
||||
private long ticksMin;
|
||||
private long ticksLength;
|
||||
|
||||
internal TimeSpan FromDouble(double d)
|
||||
{
|
||||
double ratio = (d - min) / length;
|
||||
long ticks = (long)(ticksMin + ticksLength * ratio);
|
||||
|
||||
ticks = MathHelper.Clamp(ticks, TimeSpan.MinValue.Ticks, TimeSpan.MaxValue.Ticks);
|
||||
|
||||
return new TimeSpan(ticks);
|
||||
}
|
||||
|
||||
internal double ToDouble(TimeSpan span)
|
||||
{
|
||||
double ratio = (span.Ticks - ticksMin) / (double)ticksLength;
|
||||
return min + ratio * length;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
159
Charts/Axes/TimeSpan/TimeTicksProviderBase.cs
Normal file
159
Charts/Axes/TimeSpan/TimeTicksProviderBase.cs
Normal file
@@ -0,0 +1,159 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
public abstract class TimeTicksProviderBase<T> : ITicksProvider<T>
|
||||
{
|
||||
public event EventHandler Changed;
|
||||
protected void RaiseChanged()
|
||||
{
|
||||
if (Changed != null)
|
||||
{
|
||||
Changed(this, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<DifferenceIn, ITicksProvider<T>> providers =
|
||||
new Dictionary<DifferenceIn, ITicksProvider<T>>();
|
||||
|
||||
protected static Dictionary<DifferenceIn, ITicksProvider<T>> Providers
|
||||
{
|
||||
get { return TimeTicksProviderBase<T>.providers; }
|
||||
}
|
||||
|
||||
private static readonly Dictionary<DifferenceIn, ITicksProvider<T>> minorProviders =
|
||||
new Dictionary<DifferenceIn, ITicksProvider<T>>();
|
||||
|
||||
protected static Dictionary<DifferenceIn, ITicksProvider<T>> MinorProviders
|
||||
{
|
||||
get { return TimeTicksProviderBase<T>.minorProviders; }
|
||||
}
|
||||
|
||||
protected abstract TimeSpan GetDifference(T start, T end);
|
||||
|
||||
#region ITicksProvider<T> Members
|
||||
|
||||
private IDateTimeTicksStrategy strategy = new DefaultDateTimeTicksStrategy();
|
||||
public IDateTimeTicksStrategy Strategy
|
||||
{
|
||||
get { return strategy; }
|
||||
set
|
||||
{
|
||||
if (strategy != value)
|
||||
{
|
||||
strategy = value;
|
||||
RaiseChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ITicksInfo<T> result;
|
||||
private DifferenceIn diff;
|
||||
|
||||
public ITicksInfo<T> GetTicks(Range<T> range, int ticksCount)
|
||||
{
|
||||
Verify.IsTrue(ticksCount > 0);
|
||||
|
||||
T start = range.Min;
|
||||
T end = range.Max;
|
||||
TimeSpan length = GetDifference(start, end);
|
||||
|
||||
diff = strategy.GetDifference(length);
|
||||
|
||||
TicksInfo<T> result = new TicksInfo<T> { Info = diff };
|
||||
if (providers.ContainsKey(diff))
|
||||
{
|
||||
ITicksInfo<T> innerResult = providers[diff].GetTicks(range, ticksCount);
|
||||
T[] ticks = ModifyTicksGuard(innerResult.Ticks, diff);
|
||||
|
||||
result.Ticks = ticks;
|
||||
this.result = result;
|
||||
return result;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException(Strings.Exceptions.UnsupportedRangeInAxis);
|
||||
}
|
||||
|
||||
private T[] ModifyTicksGuard(T[] ticks, object info)
|
||||
{
|
||||
var result = ModifyTicks(ticks, info);
|
||||
if (result == null)
|
||||
throw new ArgumentNullException("ticks");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected virtual T[] ModifyTicks(T[] ticks, object info)
|
||||
{
|
||||
return ticks;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decreases the tick count.
|
||||
/// </summary>
|
||||
/// <param name="tickCount">The tick count.</param>
|
||||
/// <returns></returns>
|
||||
public int DecreaseTickCount(int ticksCount)
|
||||
{
|
||||
if (providers.ContainsKey(diff))
|
||||
return providers[diff].DecreaseTickCount(ticksCount);
|
||||
|
||||
int res = ticksCount / 2;
|
||||
if (res < 2) res = 2;
|
||||
return res;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increases the tick count.
|
||||
/// </summary>
|
||||
/// <param name="ticksCount">The tick count.</param>
|
||||
/// <returns></returns>
|
||||
public int IncreaseTickCount(int ticksCount)
|
||||
{
|
||||
DebugVerify.Is(ticksCount < 2000);
|
||||
|
||||
if (providers.ContainsKey(diff))
|
||||
return providers[diff].IncreaseTickCount(ticksCount);
|
||||
|
||||
return ticksCount * 2;
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MinorProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
DifferenceIn smallerDiff = DifferenceIn.Smallest;
|
||||
if (strategy.TryGetLowerDiff(diff, out smallerDiff) && minorProviders.ContainsKey(smallerDiff))
|
||||
{
|
||||
var minorProvider = (MinorTimeProviderBase<T>)minorProviders[smallerDiff];
|
||||
minorProvider.SetTicks(result.Ticks);
|
||||
return minorProvider;
|
||||
}
|
||||
|
||||
return null;
|
||||
// todo What to do if this already is the smallest provider?
|
||||
}
|
||||
}
|
||||
|
||||
public ITicksProvider<T> MajorProvider
|
||||
{
|
||||
get
|
||||
{
|
||||
DifferenceIn biggerDiff = DifferenceIn.Smallest;
|
||||
if (strategy.TryGetBiggerDiff(diff, out biggerDiff))
|
||||
{
|
||||
return providers[biggerDiff];
|
||||
}
|
||||
|
||||
return null;
|
||||
// todo What to do if this already is the biggest provider?
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
32
Charts/Axes/TimeSpan/VerticalTimeSpanAxis.cs
Normal file
32
Charts/Axes/TimeSpan/VerticalTimeSpanAxis.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.Charts
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a vertical axis with values of <see cref="TimeSpan"/> type.
|
||||
/// </summary>
|
||||
public class VerticalTimeSpanAxis : TimeSpanAxis
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VerticalTimeSpanAxis"/> class, placed (by default) on the left side of <see cref="ChartPlotter"/>.
|
||||
/// </summary>
|
||||
public VerticalTimeSpanAxis()
|
||||
{
|
||||
Placement = AxisPlacement.Left;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Validates the placement - e.g., vertical axis should not be placed from top or bottom, etc.
|
||||
/// If proposed placement is wrong, throws an ArgumentException.
|
||||
/// </summary>
|
||||
/// <param name="newPlacement">The new placement.</param>
|
||||
protected override void ValidatePlacement(AxisPlacement newPlacement)
|
||||
{
|
||||
if (newPlacement == AxisPlacement.Bottom || newPlacement == AxisPlacement.Top)
|
||||
throw new ArgumentException(Strings.Exceptions.VerticalAxisCannotBeHorizontal);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user