Initial Commit

This commit is contained in:
2024-02-23 00:46:06 -05:00
commit 2bbedc0178
470 changed files with 46035 additions and 0 deletions

411
Charts/Axes/AxisBase.cs Normal file
View 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&lt;T&gt;"/> 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

File diff suppressed because it is too large Load Diff

View 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
}
}

View 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
View 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
}
}

View 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
}
}

View 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;
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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
}
}

View 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;
}
}
}

View 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
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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; }
}
}
}

View File

@@ -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;
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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);
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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
}
}

View 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;
}
}
}

View 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
View 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
}
}

View 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&lt;T&gt;"/> 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
}
}

View File

@@ -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]);
}
}
}

View 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&lt;T&gt;"/> class.
/// </summary>
public GenericLocationalTicksProvider() { }
/// <summary>
/// Initializes a new instance of the <see cref="GenericLocationalTicksProvider&lt;T&gt;"/> 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
}
}

View 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
View 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; }
}
}

View 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; }
}
}

View 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&lt;T&gt;"/> 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;
}
}
}

View 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);
}
}
}

View 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)
{
}
}
}

View 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);
}
}
}

View 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
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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();
}
}
}

View 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));
}
}

View 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;
}
}
}

View 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
}
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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
}
}

View 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
}
}

View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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; }
}
}
}

View 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;
}
}
}

View 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>
{
}
}

View 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);
}
}
}

View 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);
}
}
}

View 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
View 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;
}
}
}

View 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);
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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;
}
}
}

View 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));
}
}
}

View 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;
}
}
}

View 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
}
}

View 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);
}
}
}