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

View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.DynamicDataDisplay.Charts;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class ArrayExtensions
{
internal static T Last<T>(this T[] array) {
return array[array.Length - 1];
}
internal static T[] CreateArray<T>(int length, T defaultValue)
{
T[] res = new T[length];
for (int i = 0; i < res.Length; i++)
{
res[i] = defaultValue;
}
return res;
}
internal static IEnumerable<Range<T>> GetPairs<T>(this IList<T> array)
{
if (array == null)
throw new ArgumentNullException("array");
for (int i = 0; i < array.Count - 1; i++)
{
yield return new Range<T>(array[i], array[i + 1]);
}
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class BindingHelper
{
public static Binding CreateAttachedPropertyBinding(DependencyProperty attachedProperty)
{
return new Binding { Path = new PropertyPath("(0)", attachedProperty) };
}
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Windows;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class BoundsHelper
{
/// <summary>Computes bounding rectangle for sequence of points</summary>
/// <param name="points">Points sequence</param>
/// <returns>Minimal axis-aligned bounding rectangle</returns>
public static DataRect GetViewportBounds(IEnumerable<Point> viewportPoints)
{
DataRect bounds = DataRect.Empty;
double xMin = Double.PositiveInfinity;
double xMax = Double.NegativeInfinity;
double yMin = Double.PositiveInfinity;
double yMax = Double.NegativeInfinity;
foreach (Point p in viewportPoints)
{
xMin = Math.Min(xMin, p.X);
xMax = Math.Max(xMax, p.X);
yMin = Math.Min(yMin, p.Y);
yMax = Math.Max(yMax, p.Y);
}
// were some points in collection
if (!Double.IsInfinity(xMin))
{
bounds = DataRect.Create(xMin, yMin, xMax, yMax);
}
return bounds;
}
public static DataRect GetViewportBounds(IEnumerable<Point> dataPoints, DataTransform transform)
{
return GetViewportBounds(dataPoints.DataToViewport(transform));
}
}
}

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class BrushHelper
{
/// <summary>
/// Creates a SolidColorBrush with random hue of its color.
/// </summary>
/// <returns>A SolicColorBrush with random hue of its color.</returns>
public static SolidColorBrush CreateBrushWithRandomHue()
{
return new SolidColorBrush { Color = ColorHelper.CreateColorWithRandomHue() };
}
/// <summary>
/// Makes SolidColorBrush transparent.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="alpha">The alpha, [0..255]</param>
/// <returns></returns>
public static SolidColorBrush MakeTransparent(this SolidColorBrush brush, int alpha)
{
Color color = brush.Color;
color.A = (byte)alpha;
return new SolidColorBrush(color);
}
/// <summary>
/// Makes SolidColorBrush transparent.
/// </summary>
/// <param name="brush">The brush.</param>
/// <param name="alpha">The alpha, [0.0 .. 1.0].</param>
/// <returns></returns>
public static SolidColorBrush MakeTransparent(this SolidColorBrush brush, double opacity)
{
return MakeTransparent(brush, (int)(opacity * 255));
}
public static SolidColorBrush ChangeLightness(this SolidColorBrush brush, double lightnessFactor)
{
Color color = brush.Color;
HsbColor hsbColor = HsbColor.FromArgbColor(color);
hsbColor.Brightness *= lightnessFactor;
if (hsbColor.Brightness > 1.0) hsbColor.Brightness = 1.0;
SolidColorBrush result = new SolidColorBrush(hsbColor.ToArgbColor());
return result;
}
public static SolidColorBrush ChangeSaturation(this SolidColorBrush brush, double saturationFactor)
{
Color color = brush.Color;
HsbColor hsbColor = HsbColor.FromArgbColor(color);
hsbColor.Saturation *= saturationFactor;
if (hsbColor.Saturation > 1.0) hsbColor.Saturation = 1.0;
SolidColorBrush result = new SolidColorBrush(hsbColor.ToArgbColor());
return result;
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class ColorExtensions
{
public static Color MakeTransparent(this Color color, int alpha)
{
color.A = (byte)alpha;
return color;
}
public static Color MakeTransparent(this Color color, double opacity)
{
return MakeTransparent(color, (int)(255 * opacity));
}
}
}

View File

@@ -0,0 +1,104 @@
using System;
using System.Windows.Media;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class ColorHelper
{
private readonly static Random random = new Random();
/// <summary>
/// Creates color from HSB color space with random hue and saturation and brighness equal to 1.
/// </summary>
/// <returns></returns>
public static Color CreateColorWithRandomHue()
{
double hue = random.NextDouble() * 360;
HsbColor hsbColor = new HsbColor(hue, 1, 1);
return hsbColor.ToArgbColor();
}
public static Color[] CreateRandomColors(int colorNum)
{
double startHue = random.NextDouble() * 360;
Color[] res = new Color[colorNum];
double hueStep = 360.0 / colorNum;
for (int i = 0; i < res.Length; i++)
{
double hue = startHue + i * hueStep;
res[i] = new HsbColor(hue, 1, 1).ToArgbColor();
}
return res;
}
/// <summary>
/// Creates color with fully random hue and slightly random saturation and brightness.
/// </summary>
/// <returns></returns>
public static Color CreateRandomHsbColor()
{
double h = random.NextDouble() * 360;
double s = random.NextDouble() * 0.5 + 0.5;
double b = random.NextDouble() * 0.25 + 0.75;
return new HsbColor(h, s, b).ToArgbColor();
}
/// <summary>
/// Creates color with random hue.
/// </summary>
/// <param name="saturation">The saturation, [0..1].</param>
/// <param name="brightness">The brightness, [0..1]</param>
/// <returns></returns>
public static Color CreateColorWithRandomHue(double saturation, double brightness)
{
double h = random.NextDouble() * 360;
return new HsbColor(h, saturation, brightness).ToArgbColor();
}
/// <summary>
/// Creates brush with random hue.
/// </summary>
/// <param name="saturation">The saturation, [0..1].</param>
/// <param name="brightness">The brightness, [0..1].</param>
/// <returns></returns>
public static Brush CreateBrushWithRandomHue(double saturation, double brightness)
{
Color color = CreateColorWithRandomHue(saturation, brightness);
return new SolidColorBrush(color);
}
/// <summary>
/// Gets the random color (this property is created to use it from Xaml).
/// </summary>
/// <value>The random color.</value>
public static Color RandomColor
{
get { return CreateRandomHsbColor(); }
}
/// <summary>
/// Gets the random brush.
/// </summary>
/// <value>The random brush.</value>
public static SolidColorBrush RandomBrush
{
get { return new SolidColorBrush(CreateRandomHsbColor()); }
}
public static int ToArgb(this Color color)
{
int result =
color.A << 24 |
color.R << 16 |
color.G << 8 |
color.B;
return result;
}
}
}

View File

@@ -0,0 +1,76 @@
using System.Collections.Generic;
using System.Windows;
using System;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class CoordinateUtilities
{
public static Rect RectZoom(Rect rect, double ratio)
{
return RectZoom(rect, rect.GetCenter(), ratio);
}
public static Rect RectZoom(Rect rect, double horizontalRatio, double verticalRatio)
{
return RectZoom(rect, rect.GetCenter(), horizontalRatio, verticalRatio);
}
public static Rect RectZoom(Rect rect, Point zoomCenter, double ratio)
{
return RectZoom(rect, zoomCenter, ratio, ratio);
}
public static Rect RectZoom(Rect rect, Point zoomCenter, double horizontalRatio, double verticalRatio)
{
Rect res = new Rect();
res.X = zoomCenter.X - (zoomCenter.X - rect.X) * horizontalRatio;
res.Y = zoomCenter.Y - (zoomCenter.Y - rect.Y) * verticalRatio;
res.Width = rect.Width * horizontalRatio;
res.Height = rect.Height * verticalRatio;
return res;
}
public static DataRect RectZoom(DataRect rect, double ratio)
{
return RectZoom(rect, rect.GetCenter(), ratio);
}
public static DataRect RectZoom(DataRect rect, double horizontalRatio, double verticalRatio)
{
return RectZoom(rect, rect.GetCenter(), horizontalRatio, verticalRatio);
}
public static DataRect RectZoom(DataRect rect, Point zoomCenter, double ratio)
{
return RectZoom(rect, zoomCenter, ratio, ratio);
}
public static DataRect RectZoom(DataRect rect, Point zoomCenter, double horizontalRatio, double verticalRatio)
{
DataRect res = new DataRect();
res.XMin = zoomCenter.X - (zoomCenter.X - rect.XMin) * horizontalRatio;
res.YMin = zoomCenter.Y - (zoomCenter.Y - rect.YMin) * verticalRatio;
res.Width = rect.Width * horizontalRatio;
res.Height = rect.Height * verticalRatio;
return res;
}
public static DataRect RectZoomX(DataRect rect, Point zoomCenter, double ratio)
{
DataRect res = rect;
res.XMin = zoomCenter.X - (zoomCenter.X - rect.XMin) * ratio;
res.Width = rect.Width * ratio;
return res;
}
public static DataRect RectZoomY(DataRect rect, Point zoomCenter, double ratio)
{
DataRect res = rect;
res.YMin = zoomCenter.Y - (zoomCenter.Y - rect.YMin) * ratio;
res.Height = rect.Height * ratio;
return res;
}
}
}

View File

@@ -0,0 +1,82 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class DataRectExtensions
{
internal static bool IsNaN(this DataRect rect)
{
return !rect.IsEmpty &&
(
rect.XMin.IsNaN() ||
rect.YMin.IsNaN() ||
rect.XMax.IsNaN() ||
rect.YMax.IsNaN()
);
}
public static Point GetCenter(this DataRect rect)
{
return new Point(rect.XMin + rect.Width * 0.5, rect.YMin + rect.Height * 0.5);
}
public static DataRect Zoom(this DataRect rect, Point to, double ratio)
{
return CoordinateUtilities.RectZoom(rect, to, ratio);
}
public static DataRect ZoomOutFromCenter(this DataRect rect, double ratio)
{
return CoordinateUtilities.RectZoom(rect, rect.GetCenter(), ratio);
}
public static DataRect ZoomInToCenter(this DataRect rect, double ratio)
{
return CoordinateUtilities.RectZoom(rect, rect.GetCenter(), 1 / ratio);
}
public static DataRect ZoomX(this DataRect rect, Point to, double ratio)
{
return CoordinateUtilities.RectZoomX(rect, to, ratio);
}
public static DataRect ZoomY(this DataRect rect, Point to, double ratio)
{
return CoordinateUtilities.RectZoomY(rect, to, ratio);
}
public static double GetSquare(this DataRect rect)
{
if (rect.IsEmpty)
return 0;
return rect.Width * rect.Height;
}
/// <summary>
/// Determines whether one DataRect is close to another DataRect.
/// </summary>
/// <param name="rect1">The rect1.</param>
/// <param name="rect2">The rect2.</param>
/// <param name="difference">The difference.</param>
/// <returns>
/// <c>true</c> if [is close to] [the specified rect1]; otherwise, <c>false</c>.
/// </returns>
public static bool IsCloseTo(this DataRect rect1, DataRect rect2, double difference)
{
DataRect intersection = DataRect.Intersect(rect1, rect2);
double square1 = rect1.GetSquare();
double square2 = rect2.GetSquare();
double intersectionSquare = intersection.GetSquare();
bool areClose = MathHelper.AreClose(square1, intersectionSquare, difference) &&
MathHelper.AreClose(square2, intersectionSquare, difference);
return areClose;
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay.Common.DataSearch
{
internal sealed class GenericSearcher1d<TCollection, TMember> where TMember : IComparable<TMember>
{
private readonly Func<TCollection, TMember> selector;
private readonly IList<TCollection> collection;
public GenericSearcher1d(IList<TCollection> collection, Func<TCollection, TMember> selector)
{
if (collection == null)
throw new ArgumentNullException("collection");
if (selector == null)
throw new ArgumentNullException("selector");
this.collection = collection;
this.selector = selector;
}
public SearchResult1d SearchBetween(TMember x)
{
return SearchBetween(x, SearchResult1d.Empty);
}
public SearchResult1d SearchBetween(TMember x, SearchResult1d result)
{
if (collection.Count == 0)
return SearchResult1d.Empty;
int lastIndex = collection.Count - 1;
if (x.CompareTo(selector(collection[0])) < 0)
return SearchResult1d.Empty;
else if (selector(collection[lastIndex]).CompareTo(x) < 0)
return SearchResult1d.Empty;
int startIndex = !result.IsEmpty ? Math.Min(result.Index, lastIndex) : 0;
// searching ascending
if (selector(collection[startIndex]).CompareTo(x) < 0)
{
for (int i = startIndex + 1; i <= lastIndex; i++)
if (selector(collection[i]).CompareTo(x) >= 0)
return new SearchResult1d { Index = i - 1 };
}
else // searching descending
{
for (int i = startIndex - 1; i >= 0; i--)
if (selector(collection[i]).CompareTo(x) <= 0)
return new SearchResult1d { Index = i };
}
throw new InvalidOperationException("Should not appear here.");
}
public SearchResult1d SearchFirstLess(TMember x)
{
if (collection.Count == 0)
return SearchResult1d.Empty;
SearchResult1d result = SearchResult1d.Empty;
for (int i = 0; i < collection.Count; i++)
{
if (selector(collection[i]).CompareTo(x) >= 0)
{
result.Index = i;
break;
}
}
return result;
}
public SearchResult1d SearchGreater(TMember x)
{
if (collection.Count == 0)
return SearchResult1d.Empty;
SearchResult1d result = SearchResult1d.Empty;
for (int i = collection.Count - 1; i >= 0; i--)
{
if (selector(collection[i]).CompareTo(x) <= 0)
{
result.Index = i;
break;
}
}
return result;
}
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay.Common.DataSearch
{
internal struct SearchResult1d
{
public static SearchResult1d Empty
{
get { return new SearchResult1d { Index = -1 }; }
}
public int Index { get; internal set; }
public bool IsEmpty
{
get { return Index == -1; }
}
public override string ToString()
{
if (IsEmpty)
return "Empty";
return String.Format("Index = {0}", Index);
}
}
}

View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common.DataSearch
{
internal class SortedXSearcher1d
{
private readonly IList<Point> collection;
public SortedXSearcher1d(IList<Point> collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
this.collection = collection;
}
public SearchResult1d SearchXBetween(double x)
{
return SearchXBetween(x, SearchResult1d.Empty);
}
public SearchResult1d SearchXBetween(double x, SearchResult1d result)
{
if (collection.Count == 0)
return SearchResult1d.Empty;
int lastIndex = collection.Count - 1;
if (x < collection[0].X)
return SearchResult1d.Empty;
else if (collection[lastIndex].X < x)
return SearchResult1d.Empty;
int startIndex = !result.IsEmpty ? Math.Min(result.Index, lastIndex) : 0;
// searching ascending
if (collection[startIndex].X < x)
{
for (int i = startIndex + 1; i <= lastIndex; i++)
if (collection[i].X >= x)
return new SearchResult1d { Index = i - 1 };
}
else // searching descending
{
for (int i = startIndex - 1; i >= 0; i--)
if (collection[i].X <= x)
return new SearchResult1d { Index = i };
}
throw new InvalidOperationException("Should not appear here.");
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class DebugVerify
{
[Conditional("DEBUG")]
[DebuggerStepThrough]
public static void Is(bool condition)
{
if (!condition)
{
throw new ArgumentException(Strings.Exceptions.AssertionFailed);
}
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public static void IsNotNaN(double d)
{
DebugVerify.Is(!Double.IsNaN(d));
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public static void IsNotNaN(Vector vec)
{
DebugVerify.IsNotNaN(vec.X);
DebugVerify.IsNotNaN(vec.Y);
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public static void IsNotNaN(Point point)
{
DebugVerify.IsNotNaN(point.X);
DebugVerify.IsNotNaN(point.Y);
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public static void IsFinite(double d)
{
DebugVerify.Is(!Double.IsInfinity(d) && !(Double.IsNaN(d)));
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public static void IsNotNull(object obj)
{
DebugVerify.Is(obj != null);
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Threading;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class DependencyObjectExtensions
{
public static T GetValueSync<T>(this DependencyObject d, DependencyProperty property)
{
object value = null;
d.Dispatcher.Invoke(() => { value = d.GetValue(property); }, DispatcherPriority.Send);
return (T)value;
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.DynamicDataDisplay.Charts.Isolines;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class DictionaryExtensions
{
internal static void Add<TKey, TValue>(this Dictionary<TKey, TValue> dict, TValue value, params TKey[] keys)
{
foreach (var key in keys)
{
dict.Add(key, value);
}
}
internal static void Add(this Dictionary<int, Edge> dict, Edge value, params CellBitmask[] keys)
{
foreach (var key in keys)
{
dict.Add((int)key, value);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Threading;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class DispatcherExtensions
{
public static DispatcherOperation BeginInvoke(this Dispatcher dispatcher, Action action)
{
return dispatcher.BeginInvoke((Delegate)action);
}
public static DispatcherOperation BeginInvoke(this Dispatcher dispatcher, Action action, DispatcherPriority priority)
{
return dispatcher.BeginInvoke(action, priority);
}
public static void Invoke(this Dispatcher dispatcher, Action action, DispatcherPriority priority)
{
dispatcher.Invoke(action, priority);
}
}
}

View File

@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public sealed class DisposableTimer : IDisposable
{
private bool isActive = true;
private readonly string name;
Stopwatch timer;
public DisposableTimer(string name) : this(name, true) { }
public DisposableTimer(string name, bool isActive)
{
this.name = name;
this.isActive = isActive;
if (isActive)
{
timer = Stopwatch.StartNew();
Trace.WriteLine(name + ": started " + DateTime.Now.TimeOfDay);
}
}
#region IDisposable Members
public void Dispose()
{
//#if DEBUG
if (isActive)
{
var duration = timer.ElapsedMilliseconds;
Trace.WriteLine(name + ": elapsed " + duration + " ms.");
timer.Stop();
}
//#endif
}
#endregion
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class DoubleCollectionHelper
{
public static DoubleCollection Create(params double[] collection)
{
return new DoubleCollection(collection);
}
}
}

View File

@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Windows;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class EventExtensions
{
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise<T>(this EventHandler<T> @event, object sender, T args) where T : EventArgs
{
if (@event != null)
{
@event(sender, args);
}
}
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise(this EventHandler @event, object sender)
{
if (@event != null)
{
@event(sender, EventArgs.Empty);
}
}
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise(this EventHandler @event, object sender, EventArgs args)
{
if (@event != null)
{
@event(sender, args);
}
}
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise(this PropertyChangedEventHandler @event, object sender, string propertyName)
{
if (@event != null)
{
@event(sender, new PropertyChangedEventArgs(propertyName));
}
}
/// <summary>
/// Raises the specified event with Reset action.
/// </summary>
/// <param name="event">The event.</param>
/// <param name="sender">The sender.</param>
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise(this NotifyCollectionChangedEventHandler @event, object sender)
{
if (@event != null)
{
@event(sender, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise(this NotifyCollectionChangedEventHandler @event, object sender, NotifyCollectionChangedAction action)
{
if (@event != null)
{
@event(sender, new NotifyCollectionChangedEventArgs(action));
}
}
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise(this NotifyCollectionChangedEventHandler @event, object sender, NotifyCollectionChangedEventArgs e)
{
if (e == null)
throw new ArgumentNullException("e");
if (@event != null)
{
@event(sender, e);
}
}
[DebuggerStepThrough]
[DebuggerHidden]
public static void RaiseRoutedEvent(this UIElement sender, RoutedEvent routedEvent)
{
sender.RaiseEvent(new RoutedEventArgs(routedEvent));
}
/// <summary>
/// Raises the specified value changed event.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="event">The event.</param>
/// <param name="sender">The sender of event.</param>
/// <param name="prevValue">The previous value.</param>
/// <param name="currValue">The current value.</param>
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise<TValue>(this EventHandler<ValueChangedEventArgs<TValue>> @event, object sender, TValue prevValue, TValue currValue)
{
if (@event != null)
{
ValueChangedEventArgs<TValue> args = new ValueChangedEventArgs<TValue>(prevValue, currValue);
@event(sender, args);
}
}
/// <summary>
/// Raises the specified value changed event.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <typeparam name="TValue">The type of the value.</typeparam>
/// <param name="event">The event.</param>
/// <param name="sender">The sender of event.</param>
/// <param name="prevValue">The previous value.</param>
/// <param name="currValue">The current value.</param>
[DebuggerStepThrough]
[DebuggerHidden]
public static void Raise<TValue>(this EventHandler<ValueChangedEventArgs<TValue>> @event, object sender, object prevValue, object currValue)
{
if (@event != null)
{
ValueChangedEventArgs<TValue> args = new ValueChangedEventArgs<TValue>((TValue)prevValue, (TValue)currValue);
@event(sender, args);
}
}
}
}

View File

@@ -0,0 +1,301 @@
using System;
using System.Windows.Media;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>
/// Represents color in Hue Saturation Brightness color space.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Hsb")]
[DebuggerDisplay("HSBColor A={Alpha} H={Hue} S={Saturation} B={Brightness}")]
public struct HsbColor
{
private double hue;
private double saturation;
private double brightness;
private double alpha;
/// <summary>Hue; [0, 360]</summary>
public double Hue
{
get { return hue; }
set
{
if (value < 0)
value = 360 - value;
hue = value % 360;
}
}
/// <summary>Saturation; [0, 1]</summary>
public double Saturation
{
get { return saturation; }
set { saturation = value; }
}
/// <summary>Brightness; [0, 1]</summary>
public double Brightness
{
get { return brightness; }
set { brightness = value; }
}
/// <summary>Alpha; [0, 1]</summary>
public double Alpha
{
get { return alpha; }
set { alpha = value; }
}
/// <summary>
/// Initializes a new instance of the <see cref="HSBColor"/> struct.
/// </summary>
/// <param name="hue">The hue; [0; 360]</param>
/// <param name="saturation">The saturation; [0, 1]</param>
/// <param name="brightness">The brightness; [0, 1]</param>
public HsbColor(double hue, double saturation, double brightness)
{
this.hue = hue;
this.saturation = saturation;
this.brightness = brightness;
alpha = 1;
}
/// <summary>
/// Initializes a new instance of the <see cref="HSBColor"/> struct.
/// </summary>
/// <param name="hue">The hue; [0, 360]</param>
/// <param name="saturation">The saturation; [0, 1]</param>
/// <param name="brightness">The brightness; [0, 1]</param>
/// <param name="alpha">The alpha; [0, 1]</param>
public HsbColor(double hue, double saturation, double brightness, double alpha)
{
this.hue = hue;
this.saturation = saturation;
this.brightness = brightness;
this.alpha = alpha;
}
/// <summary>
/// Creates HSBColor from the ARGB color.
/// </summary>
/// <param name="color">The color.</param>
/// <returns></returns>
public static HsbColor FromArgbColor(Color color)
{
double limit255 = 255;
double r = color.R / limit255;
double g = color.G / limit255;
double b = color.B / limit255;
double max = Math.Max(Math.Max(r, g), b);
double min = Math.Min(Math.Min(r, g), b);
double len = max - min;
double brightness = max; // 0.5 * (max + min);
double sat;
double hue;
if (max == 0 || len == 0)
{
sat = hue = 0;
}
else
{
sat = len / max;
if (r == max)
{
hue = (g - b) / len;
}
else if (g == max)
{
hue = 2 + (b - r) / len;
}
else
{
hue = 4 + (r - g) / len;
}
}
hue *= 60;
if (hue < 0)
hue += 360;
HsbColor res = new HsbColor();
res.hue = hue;
res.saturation = sat;
res.brightness = brightness;
res.alpha = color.A / limit255;
return res;
}
public static HsbColor FromArgb(int argb)
{
byte a = (byte)(argb >> 24);
byte r = (byte)((argb >> 16) & 0xFF);
byte g = (byte)((argb >> 8) & 0xFF);
byte b = (byte)(argb & 0xFF);
return FromArgbColor(Color.FromArgb(a, r, g, b));
}
/// <summary>
/// Converts HSBColor to ARGB color space.
/// </summary>
/// <returns></returns>
public Color ToArgbColor()
{
double r = 0.0;
double g = 0.0;
double b = 0.0;
double hue = this.hue % 360.0;
if (saturation == 0.0)
{
r = g = b = brightness;
}
else
{
double smallHue = hue / 60.0;
int smallHueInt = (int)Math.Floor(smallHue);
double smallHueFrac = smallHue - smallHueInt;
double val1 = brightness * (1.0 - saturation);
double val2 = brightness * (1.0 - (saturation * smallHueFrac));
double val3 = brightness * (1.0 - (saturation * (1.0 - smallHueFrac)));
switch (smallHueInt)
{
case 0:
r = brightness;
g = val3;
b = val1;
break;
case 1:
r = val2;
g = brightness;
b = val1;
break;
case 2:
r = val1;
g = brightness;
b = val3;
break;
case 3:
r = val1;
g = val2;
b = brightness;
break;
case 4:
r = val3;
g = val1;
b = brightness;
break;
case 5:
r = brightness;
g = val1;
b = val2;
break;
}
}
return Color.FromArgb(
(byte)(Math.Round(alpha * 255)),
(byte)(Math.Round(r * 255)),
(byte)(Math.Round(g * 255)),
(byte)(Math.Round(b * 255)));
}
public int ToArgb()
{
return ToArgbColor().ToArgb();
}
/// <summary>
/// Determines whether the specified <see cref="System.Object"/> is equal to this instance.
/// </summary>
/// <param name="obj">The <see cref="System.Object"/> to compare with this instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object"/> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object obj)
{
if (obj is HsbColor)
{
HsbColor c = (HsbColor)obj;
return (c.alpha == alpha &&
c.brightness == brightness &&
c.hue == hue &&
c.saturation == saturation);
}
else
return false;
}
/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode()
{
return alpha.GetHashCode() ^
brightness.GetHashCode() ^
hue.GetHashCode() ^
saturation.GetHashCode();
}
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="first">The first.</param>
/// <param name="second">The second.</param>
/// <returns>The result of the operator.</returns>
public static bool operator ==(HsbColor first, HsbColor second)
{
return (first.alpha == second.alpha &&
first.brightness == second.brightness &&
first.hue == second.hue &&
first.saturation == second.saturation);
}
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="first">The first.</param>
/// <param name="second">The second.</param>
/// <returns>The result of the operator.</returns>
public static bool operator !=(HsbColor first, HsbColor second)
{
return (first.alpha != second.alpha ||
first.brightness != second.brightness ||
first.hue != second.hue ||
first.saturation != second.saturation);
}
}
public static class ColorExtensions
{
/// <summary>
/// Converts the ARGB color to the HSB color.
/// </summary>
/// <param name="color">The color.</param>
/// <returns></returns>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Hsb")]
public static HsbColor ToHsbColor(this Color color)
{
return HsbColor.FromArgbColor(color);
}
}
}

View File

@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.DynamicDataDisplay.Charts;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using System.Windows;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class IDataSource2DExtensions
{
public static Range<double> GetMinMax(this double[,] data)
{
data.VerifyNotNull("data");
int width = data.GetLength(0);
int height = data.GetLength(1);
Verify.IsTrueWithMessage(width > 0, Strings.Exceptions.ArrayWidthShouldBePositive);
Verify.IsTrueWithMessage(height > 0, Strings.Exceptions.ArrayHeightShouldBePositive);
double min = data[0, 0];
double max = data[0, 0];
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (data[x, y] < min)
min = data[x, y];
if (data[x, y] > max)
max = data[x, y];
}
}
Range<double> res = new Range<double>(min, max);
return res;
}
public static Range<double> GetMinMax(this double[,] data, double missingValue)
{
data.VerifyNotNull("data");
int width = data.GetLength(0);
int height = data.GetLength(1);
Verify.IsTrueWithMessage(width > 0, Strings.Exceptions.ArrayWidthShouldBePositive);
Verify.IsTrueWithMessage(height > 0, Strings.Exceptions.ArrayHeightShouldBePositive);
double min = Double.MaxValue;
double max = Double.MinValue;
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (data[x, y] != missingValue && data[x, y] < min)
min = data[x, y];
if (data[x, y] != missingValue && data[x, y] > max)
max = data[x, y];
}
}
Range<double> res = new Range<double>(min, max);
return res;
}
public static Range<double> GetMinMax(this IDataSource2D<double> dataSource)
{
dataSource.VerifyNotNull("dataSource");
return GetMinMax(dataSource.Data);
}
public static Range<double> GetMinMax(this IDataSource2D<double> dataSource, double missingValue)
{
dataSource.VerifyNotNull("dataSource");
return GetMinMax(dataSource.Data, missingValue);
}
public static Range<double> GetMinMax(this IDataSource2D<double> dataSource, DataRect area)
{
if (dataSource == null)
throw new ArgumentNullException("dataSource");
double min = Double.PositiveInfinity;
double max = Double.NegativeInfinity;
int width = dataSource.Width;
int height = dataSource.Height;
var grid = dataSource.Grid;
var data = dataSource.Data;
for (int ix = 0; ix < width; ix++)
{
for (int iy = 0; iy < height; iy++)
{
if (area.Contains(grid[ix, iy]))
{
var value = data[ix, iy];
if (value < min)
min = value;
if (value > max)
max = value;
}
}
}
if (min < max)
return new Range<double>(min, max);
else
return new Range<double>();
}
public static Rect GetGridBounds(this Point[,] grid)
{
double minX = grid[0, 0].X;
double maxX = minX;
double minY = grid[0, 0].Y;
double maxY = minY;
int width = grid.GetLength(0);
int height = grid.GetLength(1);
for (int ix = 0; ix < width; ix++)
{
for (int iy = 0; iy < height; iy++)
{
Point pt = grid[ix, iy];
double x = pt.X;
double y = pt.Y;
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
}
return new Rect(new Point(minX, minY), new Point(maxX, maxY));
}
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters")]
public static Rect GetGridBounds<T>(this IDataSource2D<T> dataSource) where T : struct
{
return dataSource.Grid.GetGridBounds();
}
}
}

View File

@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class IEnumerableExtensions
{
public static bool CountGreaterOrEqual<T>(this IEnumerable<T> enumerable, int count)
{
int counter = 0;
using (var enumerator = enumerable.GetEnumerator())
{
while (counter < count && enumerator.MoveNext())
{
counter++;
}
}
return counter == count;
}
public static IEnumerable<IEnumerable<T>> Split<T>(this IEnumerable<T> source, int maxCount)
{
using (var enumerator = new FixedEnumeratorWrapper<T>(source.GetEnumerator()))
{
do
{
var enumerable = new FixedEnumerable<T>(enumerator);
yield return enumerable.Take(maxCount);
}
while (enumerator.CanMoveNext);
}
}
private sealed class FixedEnumeratorWrapper<T> : IEnumerator<T>
{
private readonly IEnumerator<T> enumerator;
public FixedEnumeratorWrapper(IEnumerator<T> enumerator)
{
this.enumerator = enumerator;
}
#region IEnumerator<T> Members
public T Current
{
get { return enumerator.Current; }
}
#endregion
#region IDisposable Members
public void Dispose()
{
//enumerator.Dispose();
}
#endregion
#region IEnumerator Members
object IEnumerator.Current
{
get { throw new NotImplementedException(); }
}
private bool canMoveNext = false;
public bool CanMoveNext
{
get { return canMoveNext; }
}
public bool MoveNext()
{
canMoveNext = enumerator.MoveNext();
return canMoveNext;
}
public void Reset()
{
enumerator.Reset();
}
#endregion
}
private sealed class FixedEnumerable<T> : IEnumerable<T>
{
private readonly IEnumerator<T> enumerator;
public FixedEnumerable(IEnumerator<T> enumerator)
{
this.enumerator = enumerator;
}
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return enumerator;
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
throw new NotSupportedException();
}
#endregion
}
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class IListExtensions
{
public static void AddMany<T>(this IList<T> collection, IEnumerable<T> addingItems)
{
foreach (var item in addingItems)
{
collection.Add(item);
}
}
public static void AddMany<T>(this IList<T> collection, params T[] children)
{
foreach (var child in children)
{
collection.Add(child);
}
}
public static void RemoveAll<T>(this IList<T> collection, Type type)
{
var children = collection.Where(el => type.IsAssignableFrom(el.GetType())).ToArray();
foreach (var child in children)
{
collection.Remove((T)child);
}
}
public static void RemoveAll<T, TDelete>(this IList<T> collection)
{
var children = collection.OfType<TDelete>().ToArray();
foreach (var child in children)
{
collection.Remove((T)(object)child);
}
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class IPlotterElementExtensions
{
public static void RemoveFromPlotter(this IPlotterElement element)
{
if (element == null)
throw new ArgumentNullException("element");
if (element.Plotter != null)
{
element.Plotter.Children.Remove(element);
}
}
public static void AddToPlotter(this IPlotterElement element, Plotter plotter)
{
if (element == null)
throw new ArgumentNullException("element");
if (plotter == null)
throw new ArgumentNullException("plotter");
plotter.Children.Add(element);
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class IPointCollectionExtensions
{
public static DataRect GetBounds(this IEnumerable<Point> points)
{
return BoundsHelper.GetViewportBounds(points);
}
public static IEnumerable<Point> Skip(this IList<Point> points, int skipCount)
{
for (int i = skipCount; i < points.Count; i++)
{
yield return points[i];
}
}
}
}

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
namespace Microsoft.Research.DynamicDataDisplay
{
internal static class ListExtensions
{
/// <summary>
/// Gets last element of list.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="list"></param>
/// <returns></returns>
internal static T GetLast<T>(this List<T> list)
{
if (list == null) throw new ArgumentNullException("list");
if (list.Count == 0) throw new InvalidOperationException(Strings.Exceptions.CannotGetLastElement);
return list[list.Count - 1];
}
internal static void ForEach<T>(this IEnumerable<T> source, Action<T> action) {
if (action == null)
throw new ArgumentNullException("action");
if (source == null)
throw new ArgumentNullException("source");
foreach (var item in source)
{
action(item);
}
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class ListGenerator {
public static IEnumerable<Point> GeneratePoints(int length, Func<int, Point> generator) {
for (int i = 0; i < length; i++) {
yield return generator(i);
}
}
public static IEnumerable<Point> GeneratePoints(int length, Func<int, double> x, Func<int, double> y) {
for (int i = 0; i < length; i++) {
yield return new Point(x(i), y(i));
}
}
public static IEnumerable<T> Generate<T>(int length, Func<int, T> generator) {
for (int i = 0; i < length; i++) {
yield return generator(i);
}
}
}
}

View File

@@ -0,0 +1,53 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Resources;
namespace Microsoft.Research.DynamicDataDisplay.MarkupExtensions
{
/// <summary>
/// Represents a markup extension, which allows to get an access to application resource files.
/// </summary>
[MarkupExtensionReturnType(typeof(string))]
public class ResourceExtension : MarkupExtension
{
/// <summary>
/// Initializes a new instance of the <see cref="ResourceExtension"/> class.
/// </summary>
public ResourceExtension() { }
private string resourceKey;
//[ConstructorArgument("resourceKey")]
public string ResourceKey
{
get { return resourceKey; }
set
{
if (resourceKey == null)
throw new ArgumentNullException("resourceKey");
resourceKey = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ResourceExtension"/> class.
/// </summary>
/// <param name="resourceKey">The resource key.</param>
public ResourceExtension(string resourceKey)
{
if (resourceKey == null)
throw new ArgumentNullException("resourceKey");
this.resourceKey = resourceKey;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
return Strings.UIResources.ResourceManager.GetString(resourceKey);
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.MarkupExtensions
{
public class SelfBinding : Binding
{
public SelfBinding()
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.Self };
}
public SelfBinding(string propertyPath)
: this()
{
Path = new PropertyPath(propertyPath);
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
namespace Microsoft.Research.DynamicDataDisplay.MarkupExtensions
{
public class TemplateBinding : Binding
{
public TemplateBinding()
{
RelativeSource = new RelativeSource { Mode = RelativeSourceMode.TemplatedParent };
}
public TemplateBinding(string propertyPath)
: this()
{
Path = new System.Windows.PropertyPath(propertyPath);
}
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Windows;
using System.ComponentModel;
namespace Microsoft.Research.DynamicDataDisplay.MarkupExtensions
{
[EditorBrowsable(EditorBrowsableState.Never)]
public class XbapConditionalExpression : MarkupExtension
{
public XbapConditionalExpression() { }
public XbapConditionalExpression(object value)
{
this.Value = value;
}
[ConstructorArgument("value")]
public object Value { get; set; }
public override object ProvideValue(IServiceProvider serviceProvider)
{
#if RELEASEXBAP
return null;
#else
return ((ResourceDictionary)Application.LoadComponent(new Uri("/DynamicDataDisplay;component/Themes/Generic.xaml", UriKind.Relative)))[Value];
#endif
}
}
}

View File

@@ -0,0 +1,91 @@
using System;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class MathHelper
{
public static long Clamp(long value, long min, long max)
{
return Math.Max(min, Math.Min(value, max));
}
public static double Clamp(double value, double min, double max)
{
return Math.Max(min, Math.Min(value, max));
}
/// <summary>Clamps specified value to [0,1]</summary>
/// <param name="d">Value to clamp</param>
/// <returns>Value in range [0,1]</returns>
public static double Clamp(double value)
{
return Math.Max(0, Math.Min(value, 1));
}
public static int Clamp(int value, int min, int max)
{
return Math.Max(min, Math.Min(value, max));
}
public static Rect CreateRectByPoints(double xMin, double yMin, double xMax, double yMax)
{
return new Rect(new Point(xMin, yMin), new Point(xMax, yMax));
}
public static double Interpolate(double start, double end, double ratio)
{
return start * (1 - ratio) + end * ratio;
}
public static double RadiansToDegrees(this double radians)
{
return radians * 180 / Math.PI;
}
public static double DegreesToRadians(this double degrees)
{
return degrees / 180 * Math.PI;
}
/// <summary>
/// Converts vector into angle.
/// </summary>
/// <param name="vector">The vector.</param>
/// <returns>Angle in degrees.</returns>
public static double ToAngle(this Vector vector)
{
return Math.Atan2(-vector.Y, vector.X).RadiansToDegrees();
}
public static Point ToPoint(this Vector v)
{
return new Point(v.X, v.Y);
}
public static bool IsNaN(this double d)
{
return Double.IsNaN(d);
}
public static bool IsNotNaN(this double d)
{
return !Double.IsNaN(d);
}
public static bool IsFinite(this double d)
{
return !Double.IsNaN(d) && !Double.IsInfinity(d);
}
public static bool IsInfinite(this double d)
{
return Double.IsInfinity(d);
}
public static bool AreClose(double d1, double d2, double diffRatio)
{
return Math.Abs(d1 / d2 - 1) < diffRatio;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class MenuItemExtensions
{
public static MenuItem FindChildByHeader(this MenuItem parent, string header)
{
return parent.Items.OfType<MenuItem>().Where(subMenu => subMenu.Header.Equals(header)).FirstOrDefault();
}
}
}

View File

@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class ObservableCollectionHelper
{
public static void ApplyChanges<T>(this ObservableCollection<T> collection, NotifyCollectionChangedEventArgs args)
{
if (args.NewItems != null)
{
int startingIndex = args.NewStartingIndex;
var newItems = args.NewItems;
for (int i = 0; i < newItems.Count; i++)
{
T addedItem = (T)newItems[i];
collection.Insert(startingIndex + i, addedItem);
}
}
if (args.OldItems != null)
{
for (int i = 0; i < args.OldItems.Count; i++)
{
T removedItem = (T)args.OldItems[i];
collection.Remove(removedItem);
}
}
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.DynamicDataDisplay.Charts;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class PlacementExtensions
{
public static bool IsBottomOrTop(this AxisPlacement placement)
{
return placement == AxisPlacement.Bottom || placement == AxisPlacement.Top;
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.DynamicDataDisplay.Common;
using System.Windows.Threading;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class PlotterChildrenCollectionExtensions
{
public static void RemoveAll<T>(this PlotterChildrenCollection children)
{
var childrenToDelete = children.OfType<T>().ToList();
foreach (var child in childrenToDelete)
{
children.Remove(child as IPlotterElement);
}
}
public static void BeginAdd(this PlotterChildrenCollection children, IPlotterElement child)
{
children.Plotter.Dispatcher.BeginInvoke(((Action)(() => { children.Add(child); })), DispatcherPriority.Send);
}
public static void BeginRemove(this PlotterChildrenCollection children, IPlotterElement child)
{
children.Plotter.Dispatcher.BeginInvoke(((Action)(() => { children.Remove(child); })), DispatcherPriority.Send);
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class PlotterExtensions
{
public static void AddChild(this Plotter plotter, IPlotterElement child)
{
plotter.Children.Add(child);
}
}
}

View File

@@ -0,0 +1,19 @@
using System.Windows;
using System;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class PointExtensions
{
public static Vector ToVector(this Point pt)
{
return new Vector(pt.X, pt.Y);
}
public static bool IsFinite(this Point pt)
{
return pt.X.IsFinite() && pt.Y.IsFinite();
}
}
}

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class RandomExtensions
{
public static double NextDouble(this Random rnd, double min, double max)
{
return min + (max - min) * rnd.NextDouble();
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using Microsoft.Research.DynamicDataDisplay.Charts;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class RangeExtensions
{
public static double GetLength(this Range<Point> range)
{
Point p1 = range.Min;
Point p2 = range.Max;
return (p1 - p2).Length;
}
public static double GetLength(this Range<double> range)
{
return range.Max - range.Min;
}
}
}

View File

@@ -0,0 +1,67 @@
using System.Windows;
using Microsoft.Research.DynamicDataDisplay.Common;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay
{
public static class RectExtensions
{
public static Point GetCenter(this Rect rect)
{
return new Point(rect.Left + rect.Width * 0.5, rect.Top + rect.Height * 0.5);
}
public static Rect FromCenterSize(Point center, Size size)
{
return FromCenterSize(center, size.Width, size.Height);
}
public static Rect FromCenterSize(Point center, double width, double height)
{
Rect res = new Rect(center.X - width / 2, center.Y - height / 2, width, height);
return res;
}
public static Rect Zoom(this Rect rect, Point to, double ratio)
{
return CoordinateUtilities.RectZoom(rect, to, ratio);
}
public static Rect ZoomOutFromCenter(this Rect rect, double ratio)
{
return CoordinateUtilities.RectZoom(rect, rect.GetCenter(), ratio);
}
public static Rect ZoomInToCenter(this Rect rect, double ratio)
{
return CoordinateUtilities.RectZoom(rect, rect.GetCenter(), 1 / ratio);
}
public static Int32Rect ToInt32Rect(this Rect rect)
{
Int32Rect intRect = new Int32Rect(
(int)rect.X,
(int)rect.Y,
(int)rect.Width,
(int)rect.Height);
return intRect;
}
[DebuggerStepThrough]
public static DataRect ToDataRect(this Rect rect)
{
return new DataRect(rect);
}
internal static bool IsNaN(this Rect rect)
{
return !rect.IsEmpty && (
rect.X.IsNaN() ||
rect.Y.IsNaN() ||
rect.Width.IsNaN() ||
rect.Height.IsNaN()
);
}
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class ResourcePoolExtensions
{
public static T GetOrCreate<T>(this ResourcePool<T> pool) where T : new()
{
T instance = pool.Get();
if (instance == null)
{
instance = new T();
}
return instance;
}
}
}

View File

@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media.Imaging;
using System.Windows;
using System.Windows.Media;
using System.IO;
using System.Diagnostics;
using System.Windows.Threading;
using System.Windows.Shapes;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class ScreenshotHelper
{
/// <summary>Gets the encoder by extension</summary>
/// <param name="extension">The extension</param>
/// <returns>BitmapEncoder object</returns>
internal static BitmapEncoder GetEncoderByExtension(string extension)
{
switch (extension)
{
case "bmp":
return new BmpBitmapEncoder();
case "jpg":
return new JpegBitmapEncoder();
case "gif":
return new GifBitmapEncoder();
case "png":
return new PngBitmapEncoder();
case "tiff":
return new TiffBitmapEncoder();
case "wmp":
return new WmpBitmapEncoder();
default:
throw new ArgumentException(Strings.Exceptions.CannotDetermineImageTypeByExtension, "extension");
}
}
/// <summary>Creates the screenshot of entire plotter element</summary>
/// <returns></returns>
internal static BitmapSource CreateScreenshot(UIElement uiElement, Int32Rect screenshotSource)
{
Window window = Window.GetWindow(uiElement);
if (window == null)
{
return CreateElementScreenshot(uiElement);
}
Size size = window.RenderSize;
//double dpiCoeff = 32 / SystemParameters.CursorWidth;
//int dpi = (int)(dpiCoeff * 96);
double dpiCoeff = 1;
int dpi = 96;
RenderTargetBitmap bmp = new RenderTargetBitmap(
(int)(size.Width * dpiCoeff), (int)(size.Height * dpiCoeff),
dpi, dpi, PixelFormats.Default);
// white background
Rectangle whiteRect = new Rectangle { Width = size.Width, Height = size.Height, Fill = Brushes.White };
whiteRect.Measure(size);
whiteRect.Arrange(new Rect(size));
bmp.Render(whiteRect);
// the very element
bmp.Render(uiElement);
CroppedBitmap croppedBmp = new CroppedBitmap(bmp, screenshotSource);
return croppedBmp;
}
private static BitmapSource CreateElementScreenshot(UIElement uiElement)
{
bool measureValid = uiElement.IsMeasureValid;
if (!measureValid)
{
double width = 300;
double height = 300;
FrameworkElement frElement = uiElement as FrameworkElement;
if (frElement != null)
{
if (!Double.IsNaN(frElement.Width))
width = frElement.Width;
if (!Double.IsNaN(frElement.Height))
height = frElement.Height;
}
Size size = new Size(width, height);
uiElement.Measure(size);
uiElement.Arrange(new Rect(size));
}
RenderTargetBitmap bmp = new RenderTargetBitmap(
(int)uiElement.RenderSize.Width, (int)uiElement.RenderSize.Height,
96, 96, PixelFormats.Default);
// this is waiting for dispatcher to perform measure, arrange and render passes
uiElement.Dispatcher.Invoke(((Action)(() => { })), DispatcherPriority.Background);
Size elementSize = uiElement.DesiredSize;
// white background
Rectangle whiteRect = new Rectangle { Width = elementSize.Width, Height = elementSize.Height, Fill = Brushes.White };
whiteRect.Measure(elementSize);
whiteRect.Arrange(new Rect(elementSize));
bmp.Render(whiteRect);
bmp.Render(uiElement);
return bmp;
}
private static Dictionary<BitmapSource, string> pendingBitmaps = new Dictionary<BitmapSource, string>();
internal static void SaveBitmapToStream(BitmapSource bitmap, Stream stream, string fileExtension)
{
if (bitmap == null)
throw new ArgumentNullException("bitmap");
if (stream == null)
throw new ArgumentNullException("stream");
if (String.IsNullOrEmpty(fileExtension))
throw new ArgumentException(Strings.Exceptions.ExtensionCannotBeNullOrEmpty, fileExtension);
BitmapEncoder encoder = ScreenshotHelper.GetEncoderByExtension(fileExtension);
encoder.Frames.Add(BitmapFrame.Create(bitmap, null, new BitmapMetadata(fileExtension.Trim('.')), null));
encoder.Save(stream);
}
internal static void SaveBitmapToFile(BitmapSource bitmap, string filePath)
{
if (String.IsNullOrEmpty(filePath))
throw new ArgumentException(Strings.Exceptions.FilePathCannotbeNullOrEmpty, "filePath");
if (bitmap.IsDownloading)
{
pendingBitmaps[bitmap] = filePath;
bitmap.DownloadCompleted += OnBitmapDownloadCompleted;
return;
}
string dirPath = System.IO.Path.GetDirectoryName(filePath);
if (!String.IsNullOrEmpty(dirPath) && !Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
bool fileExistedBefore = File.Exists(filePath);
try
{
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
string extension = System.IO.Path.GetExtension(filePath).TrimStart('.');
SaveBitmapToStream(bitmap, fs, extension);
}
}
catch (ArgumentException)
{
if (!fileExistedBefore && File.Exists(filePath))
{
try
{
File.Delete(filePath);
}
catch { }
}
}
catch (IOException exc)
{
Debug.WriteLine("Exception while saving bitmap to file: " + exc.Message);
}
}
public static void SaveStreamToFile(Stream stream, string filePath)
{
string dirPath = System.IO.Path.GetDirectoryName(filePath);
if (!String.IsNullOrEmpty(dirPath) && !Directory.Exists(dirPath))
{
Directory.CreateDirectory(dirPath);
}
using (FileStream fs = new FileStream(filePath, FileMode.Create, FileAccess.Write))
{
string extension = System.IO.Path.GetExtension(filePath).TrimStart('.');
if (stream.CanSeek)
stream.Seek(0, SeekOrigin.Begin);
stream.CopyTo(fs);
}
stream.Dispose();
}
private static void OnBitmapDownloadCompleted(object sender, EventArgs e)
{
BitmapSource bmp = (BitmapSource)sender;
bmp.DownloadCompleted -= OnBitmapDownloadCompleted;
string filePath = pendingBitmaps[bmp];
pendingBitmaps.Remove(bmp);
SaveBitmapToFile(bmp, filePath);
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class SizeExtensions
{
private const double sizeRatio = 1e-7;
public static bool EqualsApproximately(this Size size1, Size size2)
{
bool widthEquals = Math.Abs(size1.Width - size2.Width) / size1.Width < sizeRatio;
bool heightEquals = Math.Abs(size1.Height - size2.Height) / size1.Height < sizeRatio;
return widthEquals && heightEquals;
}
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class SizeHelper
{
public static Size CreateInfiniteSize()
{
return new Size(Double.PositiveInfinity, Double.PositiveInfinity);
}
}
}

View File

@@ -0,0 +1,23 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class StreamExtensions
{
public static void CopyTo(this Stream input, Stream output)
{
byte[] buffer = new byte[32768];
while (true)
{
int read = input.Read(buffer, 0, buffer.Length);
if (read <= 0)
return;
output.Write(buffer, 0, read);
}
}
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class StringExtensions
{
public static string Format(this string formatString, object param)
{
return String.Format(formatString, param);
}
public static string Format(this string formatString, object param1, object param2)
{
return String.Format(formatString, param1, param2);
}
}
}

View File

@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;
using System.Windows.Threading;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class TaskExtensions
{
/// <summary>
/// Logs exceptions that occur during task execution.
/// </summary>
/// <param name="task">The task.</param>
/// <returns></returns>
public static Task WithExceptionLogging(this Task task)
{
return task.ContinueWith(t =>
{
var exception = t.Exception;
if (exception != null)
{
if (exception.InnerException != null)
exception = (AggregateException)exception.InnerException;
Debug.WriteLine("Failure in async task: " + exception.Message);
}
});
}
/// <summary>
/// Rethrows exceptions thrown during task execution in thespecified dispatcher thread.
/// </summary>
/// <param name="task">The task.</param>
/// <param name="dispatcher">The dispatcher.</param>
/// <returns></returns>
public static Task WithExceptionThrowingInDispatcher(this Task task, Dispatcher dispatcher)
{
return task.ContinueWith(t =>
{
dispatcher.BeginInvoke(() =>
{
throw t.Exception;
}, DispatcherPriority.Send);
});
}
}
}

View File

@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media.Media3D;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
public static class TriangleMath
{
public static bool TriangleContains(Point a, Point b, Point c, Point m)
{
double a0 = a.X - c.X;
double a1 = b.X - c.X;
double a2 = a.Y - c.Y;
double a3 = b.Y - c.Y;
if (AreClose(a0 * a3, a1 * a2))
{
// determinant is too close to zero => apexes are on one line
Vector ab = a - b;
Vector ac = a - c;
Vector bc = b - c;
Vector ax = a - m;
Vector bx = b - m;
bool res = AreClose(ab.X * ax.Y, ab.Y * ax.X) && !AreClose(ab.LengthSquared, 0) ||
AreClose(ac.X * ax.Y, ac.Y * ax.X) && !AreClose(ac.LengthSquared, 0) ||
AreClose(bc.X * bx.Y, bc.Y * bx.X) && !AreClose(bc.LengthSquared, 0);
return res;
}
else
{
double b1 = m.X - c.X;
double b2 = m.Y - c.Y;
// alpha, beta and gamma - are baricentric coordinates of v
// in triangle with apexes a, b and c
double beta = (b2 / a2 * a0 - b1) / (a3 / a2 * a0 - a1);
double alpha = (b1 - a1 * beta) / a0;
double gamma = 1 - beta - alpha;
return alpha >= 0 && beta >= 0 && gamma >= 0;
}
}
private const double eps = 0.00001;
private static bool AreClose(double x, double y)
{
return Math.Abs(x - y) < eps;
}
public static Vector3D GetBaricentricCoordinates(Point a, Point b, Point c, Point m)
{
double Sac = GetSquare(a, c, m);
double Sbc = GetSquare(b, c, m);
double Sab = GetSquare(a, b, m);
double sum = (Sab + Sac + Sbc) / 3;
return new Vector3D(Sbc / sum, Sac / sum, Sab / sum);
}
public static double GetSquare(Point a, Point b, Point c)
{
double ab = (a - b).Length;
double ac = (a - c).Length;
double bc = (b - c).Length;
double p = 0.5 * (ab + ac + bc); // half of perimeter
return Math.Sqrt(p * (p - ab) * (p - ac) * (p - bc));
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay
{
public sealed class ValueStore : CustomTypeDescriptor, INotifyPropertyChanged
{
public ValueStore(params string[] propertiesNames)
{
foreach (var propertyName in propertiesNames)
{
this[propertyName] = "";
}
}
private Dictionary<string, object> cache = new Dictionary<string, object>();
public object this[string propertyName]
{
get { return cache[propertyName]; }
set { SetValue(propertyName, value); }
}
public ValueStore SetValue(string propertyName, object value)
{
cache[propertyName] = value;
PropertyChanged.Raise(this, propertyName);
return this;
}
private PropertyDescriptorCollection collection;
public override PropertyDescriptorCollection GetProperties()
{
PropertyDescriptor[] propertyDescriptors = new PropertyDescriptor[cache.Count];
var keys = cache.Keys.ToArray();
for (int i = 0; i < keys.Length; i++)
{
propertyDescriptors[i] = new ValueStorePropertyDescriptor(keys[i]);
}
collection = new PropertyDescriptorCollection(propertyDescriptors);
return collection;
}
private sealed class ValueStorePropertyDescriptor : PropertyDescriptor
{
private readonly string name;
public ValueStorePropertyDescriptor(string name)
: base(name, null)
{
this.name = name;
}
public override bool CanResetValue(object component)
{
return false;
}
public override Type ComponentType
{
get { return typeof(ValueStore); }
}
public override object GetValue(object component)
{
ValueStore store = (ValueStore)component;
return store[name];
}
public override bool IsReadOnly
{
get { return false; }
}
public override Type PropertyType
{
get { return typeof(string); }
}
public override void ResetValue(object component)
{
}
public override void SetValue(object component, object value)
{
}
public override bool ShouldSerializeValue(object component)
{
return false;
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Data;
using System.Globalization;
namespace Microsoft.Research.DynamicDataDisplay
{
public sealed class ValueStoreConverter : IValueConverter
{
#region IValueConverter Members
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
ValueStore store = (ValueStore)value;
string key = (string)parameter;
return store[key];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
#endregion
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class VectorExtensions
{
public static Point ToPoint(this Vector vector)
{
return new Point(vector.X, vector.Y);
}
}
}

View File

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class Verify
{
[DebuggerStepThrough]
public static void IsTrue(this bool condition)
{
if (!condition)
{
throw new ArgumentException(Strings.Exceptions.AssertionFailedSearch);
}
}
[DebuggerStepThrough]
public static void IsTrue(this bool condition, string paramName)
{
if (!condition)
{
throw new ArgumentException(Strings.Exceptions.AssertionFailedSearch, paramName);
}
}
public static void IsTrueWithMessage(this bool condition, string message)
{
if (!condition)
throw new ArgumentException(message);
}
[DebuggerStepThrough]
public static void AssertNotNull(object obj)
{
Verify.IsTrue(obj != null);
}
public static void VerifyNotNull(this object obj, string paramName)
{
if (obj == null)
throw new ArgumentNullException(paramName);
}
public static void VerifyNotNull(this object obj)
{
VerifyNotNull(obj, "value");
}
[DebuggerStepThrough]
public static void AssertIsNotNaN(this double d)
{
Verify.IsTrue(!Double.IsNaN(d));
}
[DebuggerStepThrough]
public static void AssertIsFinite(this double d)
{
Verify.IsTrue(!Double.IsInfinity(d) && !(Double.IsNaN(d)));
}
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Auxiliary
{
internal static class VisualTreeHelperHelper
{
public static DependencyObject GetParent(DependencyObject target, int depth)
{
DependencyObject parent = target;
do
{
parent = VisualTreeHelper.GetParent(parent);
if (parent == null)
{
break;
}
} while (--depth > 0);
return parent;
}
}
}

120
Common/BezierBuilder.cs Normal file
View File

@@ -0,0 +1,120 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Charts.NewLine
{
// todo check a license
public static class BezierBuilder
{
public static IEnumerable<Point> GetBezierPoints(Point[] points)
{
if (points == null)
throw new ArgumentNullException("points");
Point[] firstControlPoints;
Point[] secondControlPoints;
int n = points.Length - 1;
if (n < 1)
throw new ArgumentException("At least two knot points required", "points");
if (n == 1)
{ // Special case: Bezier curve should be a straight line.
firstControlPoints = new Point[1];
// 3P1 = 2P0 + P3
firstControlPoints[0].X = (2 * points[0].X + points[1].X) / 3;
firstControlPoints[0].Y = (2 * points[0].Y + points[1].Y) / 3;
secondControlPoints = new Point[1];
// P2 = 2P1 P0
secondControlPoints[0].X = 2 *
firstControlPoints[0].X - points[0].X;
secondControlPoints[0].Y = 2 *
firstControlPoints[0].Y - points[0].Y;
return Join(points, firstControlPoints, secondControlPoints);
}
// Calculate first Bezier control points
// Right hand side vector
double[] rhs = new double[n];
// Set right hand side X values
for (int i = 1; i < n - 1; ++i)
rhs[i] = 4 * points[i].X + 2 * points[i + 1].X;
rhs[0] = points[0].X + 2 * points[1].X;
rhs[n - 1] = (8 * points[n - 1].X + points[n].X) / 2.0;
// Get first control points X-values
double[] x = GetFirstControlPoints(rhs);
// Set right hand side Y values
for (int i = 1; i < n - 1; ++i)
rhs[i] = 4 * points[i].Y + 2 * points[i + 1].Y;
rhs[0] = points[0].Y + 2 * points[1].Y;
rhs[n - 1] = (8 * points[n - 1].Y + points[n].Y) / 2.0;
// Get first control points Y-values
double[] y = GetFirstControlPoints(rhs);
// Fill output arrays.
firstControlPoints = new Point[n];
secondControlPoints = new Point[n];
for (int i = 0; i < n; ++i)
{
// First control point
firstControlPoints[i] = new Point(x[i], y[i]);
// Second control point
if (i < n - 1)
secondControlPoints[i] = new Point(2 * points
[i + 1].X - x[i + 1], 2 *
points[i + 1].Y - y[i + 1]);
else
secondControlPoints[i] = new Point((points
[n].X + x[n - 1]) / 2,
(points[n].Y + y[n - 1]) / 2);
}
return Join(points, firstControlPoints, secondControlPoints);
}
private static IEnumerable<Point> Join(Point[] points, Point[] firstControlPoints, Point[] secondControlPoints)
{
var length = firstControlPoints.Length;
for (int i = 0; i < length; i++)
{
yield return points[i];
yield return firstControlPoints[i];
yield return secondControlPoints[i];
}
yield return points[length];
}
/// <summary>
/// Solves a tridiagonal system for one of coordinates (x or y)
/// of first Bezier control points.
/// </summary>
/// <param name="rhs">Right hand side vector.</param>
/// <returns>Solution vector.</returns>
private static double[] GetFirstControlPoints(double[] rhs)
{
int n = rhs.Length;
double[] x = new double[n]; // Solution vector.
double[] tmp = new double[n]; // Temp workspace.
double b = 2.0;
x[0] = rhs[0] / b;
for (int i = 1; i < n; i++) // Decomposition and forward substitution.
{
tmp[i] = 1 / b;
b = (i < n - 1 ? 4.0 : 3.5) - tmp[i];
x[i] = (rhs[i] - x[i - 1]) / b;
}
for (int i = 1; i < n; i++)
x[n - i - 1] -= tmp[n - i] * x[n - i]; // Backsubstitution.
return x;
}
}
}

132
Common/D3Collection.cs Normal file
View File

@@ -0,0 +1,132 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
/// <summary>
/// This is a base class for some of collections in DynamicDataDisplay assembly.
/// It provides means to be notified when item adding and added events, which enables successors to, for example,
/// check if adding item is not equal to null.
/// </summary>
/// <typeparam name="T"></typeparam>
public abstract class D3Collection<T> : ObservableCollection<T>
{
#region Overrides
protected override void InsertItem(int index, T item)
{
OnItemAdding(item);
base.InsertItem(index, item);
OnItemAdded(item);
}
protected override void ClearItems()
{
foreach (var item in Items)
{
OnItemRemoving(item);
}
base.ClearItems();
}
protected override void RemoveItem(int index)
{
T item = Items[index];
OnItemRemoving(item);
base.RemoveItem(index);
}
protected override void SetItem(int index, T item)
{
T oldItem = Items[index];
OnItemRemoving(oldItem);
OnItemAdding(item);
base.SetItem(index, item);
OnItemAdded(item);
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
attemptsToRaiseEvent++;
if (raiseCollectionChangedEvent)
{
base.OnCollectionChanged(e);
}
}
#endregion // end of Overrides
/// <summary>
/// Called before item added to collection. Enables to perform validation.
/// </summary>
/// <param name="item">The adding item.</param>
protected virtual void OnItemAdding(T item) { }
/// <summary>
/// Called when item is added.
/// </summary>
/// <param name="item">The added item.</param>
protected virtual void OnItemAdded(T item) { }
/// <summary>
/// Called when item is being removed, but before it is actually removed.
/// </summary>
/// <param name="item">The removing item.</param>
protected virtual void OnItemRemoving(T item) { }
#region Public
int attemptsToRaiseEvent = 0;
bool raiseCollectionChangedEvent = true;
public void BeginUpdate()
{
attemptsToRaiseEvent = 0;
raiseCollectionChangedEvent = false;
}
public void EndUpdate(bool raiseReset)
{
raiseCollectionChangedEvent = true;
if (attemptsToRaiseEvent > 0 && raiseReset)
{
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public IDisposable BlockEvents(bool raiseReset)
{
return new EventBlocker<T>(this, raiseReset);
}
private sealed class EventBlocker<TT> : IDisposable
{
private readonly D3Collection<TT> collection;
private readonly bool raiseReset = true;
public EventBlocker(D3Collection<TT> collection, bool raiseReset)
{
this.collection = collection;
this.raiseReset = raiseReset;
collection.BeginUpdate();
}
#region IDisposable Members
public void Dispose()
{
collection.EndUpdate(raiseReset);
}
#endregion
}
#endregion // end of Public
}
}

687
Common/DataRect.cs Normal file
View File

@@ -0,0 +1,687 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Diagnostics;
using Microsoft.Research.DynamicDataDisplay.Common;
using System.Windows.Markup;
using System.Globalization;
using System.ComponentModel;
namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>
/// Describes a rectangle in viewport or data coordinates.
/// </summary>
[Serializable]
[ValueSerializer(typeof(DataRectSerializer))]
[TypeConverter(typeof(DataRectConverter))]
public struct DataRect : IEquatable<DataRect>, IFormattable
{
#region Ctors
/// <summary>
/// Initializes a new instance of the <see cref="DataRect"/> struct.
/// </summary>
/// <param name="rect">Source rect.</param>
public DataRect(Rect rect)
{
xMin = rect.X;
yMin = rect.Y;
width = rect.Width;
height = rect.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="DataRect"/> struct.
/// </summary>
/// <param name="size">The size.</param>
public DataRect(Size size)
{
if (size.IsEmpty)
{
this = emptyRect;
}
else
{
xMin = yMin = 0.0;
width = size.Width;
height = size.Height;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="DataRect"/> struct.
/// </summary>
/// <param name="location">The location.</param>
/// <param name="size">The size.</param>
public DataRect(Point location, Size size)
{
if (size.IsEmpty)
{
this = emptyRect;
}
else
{
xMin = location.X;
yMin = location.Y;
width = size.Width;
height = size.Height;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="DataRect"/> struct.
/// </summary>
/// <param name="point1">The point1.</param>
/// <param name="point2">The point2.</param>
public DataRect(Point point1, Point point2)
{
xMin = Math.Min(point1.X, point2.X);
yMin = Math.Min(point1.Y, point2.Y);
width = Math.Max((double)(Math.Max(point1.X, point2.X) - xMin), 0);
height = Math.Max((double)(Math.Max(point1.Y, point2.Y) - yMin), 0);
}
/// <summary>
/// Initializes a new instance of the <see cref="DataRect"/> struct.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="vector">The vector.</param>
public DataRect(Point point, Vector vector) : this(point, point + vector) { }
/// <summary>
/// Initializes a new instance of the <see cref="DataRect"/> struct.
/// </summary>
/// <param name="xMin">The minimal x.</param>
/// <param name="yMin">The minimal y.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public DataRect(double xMin, double yMin, double width, double height)
{
if ((width < 0) || (height < 0))
throw new ArgumentException(Strings.Exceptions.WidthAndHeightCannotBeNegative);
this.xMin = xMin;
this.yMin = yMin;
this.width = width;
this.height = height;
}
#endregion
#region Static
/// <summary>
/// Creates the DataRect from minimal and maximal 'x' and 'y' coordinates.
/// </summary>
/// <param name="xMin">The x min.</param>
/// <param name="yMin">The y min.</param>
/// <param name="xMax">The x max.</param>
/// <param name="yMax">The y max.</param>
/// <returns></returns>
public static DataRect Create(double xMin, double yMin, double xMax, double yMax)
{
DataRect rect = new DataRect(xMin, yMin, xMax - xMin, yMax - yMin);
return rect;
}
public static DataRect FromPoints(double x1, double y1, double x2, double y2)
{
return new DataRect(new Point(x1, y1), new Point(x2, y2));
}
public static DataRect FromCenterSize(Point center, double width, double height)
{
DataRect rect = new DataRect(center.X - width / 2, center.Y - height / 2, width, height);
return rect;
}
public static DataRect FromCenterSize(Point center, Size size)
{
return FromCenterSize(center, size.Width, size.Height);
}
public static DataRect Intersect(DataRect rect1, DataRect rect2)
{
rect1.Intersect(rect2);
return rect1;
}
public static implicit operator DataRect(Rect rect)
{
return new DataRect(rect);
}
#endregion
public Rect ToRect()
{
return new Rect(xMin, yMin, width, height);
}
public void Intersect(DataRect rect)
{
if (!IntersectsWith(rect))
{
this = DataRect.Empty;
return;
}
DataRect res = new DataRect();
double x = Math.Max(this.XMin, rect.XMin);
double y = Math.Max(this.YMin, rect.YMin);
res.width = Math.Max((double)(Math.Min(this.XMax, rect.XMax) - x), 0.0);
res.height = Math.Max((double)(Math.Min(this.YMax, rect.YMax) - y), 0.0);
res.xMin = x;
res.yMin = y;
this = res;
}
public bool IntersectsWith(DataRect rect)
{
if (IsEmpty || rect.IsEmpty)
return false;
return ((((rect.XMin <= this.XMax) && (rect.XMax >= this.XMin)) && (rect.YMax >= this.YMin)) && (rect.YMin <= this.YMax));
}
private double xMin;
private double yMin;
private double width;
private double height;
/// <summary>
/// Gets a value indicating whether this instance is empty.
/// </summary>
/// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value>
public bool IsEmpty
{
get { return width < 0; }
}
/// <summary>
/// Gets the bottom.
/// </summary>
/// <value>The bottom.</value>
public double YMin
{
get { return yMin; }
set
{
if (this.IsEmpty)
throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
yMin = value;
}
}
/// <summary>
/// Gets the maximal y value.
/// </summary>
/// <value>The top.</value>
public double YMax
{
get
{
if (IsEmpty)
return Double.PositiveInfinity;
return yMin + height;
}
}
/// <summary>
/// Gets the minimal x value.
/// </summary>
/// <value>The left.</value>
public double XMin
{
get { return xMin; }
set
{
if (this.IsEmpty)
throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
xMin = value;
}
}
/// <summary>
/// Gets the maximal x value.
/// </summary>
/// <value>The right.</value>
public double XMax
{
get
{
if (IsEmpty)
return Double.PositiveInfinity;
return xMin + width;
}
}
/// <summary>
/// Gets or sets the location.
/// </summary>
/// <value>The location.</value>
public Point Location
{
get { return new Point(xMin, yMin); }
set
{
if (IsEmpty)
throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
xMin = value.X;
yMin = value.Y;
}
}
public Point XMaxYMax
{
get { return new Point(XMax, YMax); }
}
public Point XMinYMin
{
get { return new Point(xMin, yMin); }
}
/// <summary>
/// Gets or sets the size.
/// </summary>
/// <value>The size.</value>
public Size Size
{
get
{
if (IsEmpty)
return Size.Empty;
return new Size(width, height);
}
set
{
if (value.IsEmpty)
{
this = emptyRect;
}
else
{
if (IsEmpty)
throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
width = value.Width;
height = value.Height;
}
}
}
public double Width
{
get { return width; }
set
{
if (this.IsEmpty)
throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
if (value < 0)
throw new ArgumentOutOfRangeException(Strings.Exceptions.DataRectSizeCannotBeNegative);
width = value;
}
}
public double Height
{
get { return height; }
set
{
if (this.IsEmpty)
throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
if (value < 0)
throw new ArgumentOutOfRangeException(Strings.Exceptions.DataRectSizeCannotBeNegative);
height = value;
}
}
private static readonly DataRect emptyRect = CreateEmptyRect();
public static DataRect Empty
{
get { return DataRect.emptyRect; }
}
private static DataRect CreateEmptyRect()
{
DataRect rect = new DataRect();
rect.xMin = Double.PositiveInfinity;
rect.yMin = Double.PositiveInfinity;
rect.width = Double.NegativeInfinity;
rect.height = Double.NegativeInfinity;
return rect;
}
private static readonly DataRect infinite = new DataRect(Double.MinValue / 2, Double.MinValue / 2, Double.MaxValue, Double.MaxValue);
public static DataRect Infinite
{
get { return infinite; }
}
#region Object overrides
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <param name="obj">Another object to compare to.</param>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
public override bool Equals(object obj)
{
if (obj == null)
return false;
if (!(obj is DataRect))
return false;
DataRect other = (DataRect)obj;
return Equals(other);
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
if (IsEmpty)
return 0;
return xMin.GetHashCode() ^
width.GetHashCode() ^
yMin.GetHashCode() ^
height.GetHashCode();
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
public override string ToString()
{
if (IsEmpty)
return "Empty";
return String.Format("({0};{1}) -> {2}*{3}", xMin, yMin, width, height);
}
/// <summary>
/// Implements the operator ==.
/// </summary>
/// <param name="rect1">The rect1.</param>
/// <param name="rect2">The rect2.</param>
/// <returns>The result of the operator.</returns>
public static bool operator ==(DataRect rect1, DataRect rect2)
{
return rect1.Equals(rect2);
}
/// <summary>
/// Implements the operator !=.
/// </summary>
/// <param name="rect1">The rect1.</param>
/// <param name="rect2">The rect2.</param>
/// <returns>The result of the operator.</returns>
public static bool operator !=(DataRect rect1, DataRect rect2)
{
return !rect1.Equals(rect2);
}
public static bool EqualEps(DataRect rect1, DataRect rect2, double eps)
{
double width = Math.Min(rect1.Width, rect2.Width);
double height = Math.Min(rect1.Height, rect2.Height);
return Math.Abs(rect1.XMin - rect2.XMin) < width * eps &&
Math.Abs(rect1.XMax - rect2.XMax) < width * eps &&
Math.Abs(rect1.YMin - rect2.YMin) < height * eps &&
Math.Abs(rect1.YMax - rect2.YMax) < height * eps;
}
#endregion
#region IEquatable<DataRect> Members
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
public bool Equals(DataRect other)
{
if (this.IsEmpty)
return other.IsEmpty;
return xMin == other.xMin &&
width == other.width &&
yMin == other.yMin &&
height == other.height;
}
#endregion
/// <summary>
/// Determines whether this DataRect contains point with specified coordinates.
/// </summary>
/// <param name="x">The x coordinate of point.</param>
/// <param name="y">The y coordinate of point.</param>
/// <returns>
/// <c>true</c> if contains point with specified coordinates; otherwise, <c>false</c>.
/// </returns>
public bool Contains(double x, double y)
{
if (this.IsEmpty)
return false;
return x >= xMin &&
x <= XMax &&
y >= yMin &&
y <= YMax;
}
public bool Contains(Point point)
{
return Contains(point.X, point.Y);
}
public bool Contains(DataRect rect)
{
if (this.IsEmpty || rect.IsEmpty)
return false;
return
this.xMin <= rect.xMin &&
this.yMin <= rect.yMin &&
this.XMax >= rect.XMax &&
this.YMax >= rect.YMax;
}
public void Offset(Vector offsetVector)
{
if (this.IsEmpty)
throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
this.xMin += offsetVector.X;
this.yMin += offsetVector.Y;
}
public void Offset(double offsetX, double offsetY)
{
if (this.IsEmpty)
throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect);
this.xMin += offsetX;
this.yMin += offsetY;
}
public static DataRect Offset(DataRect rect, double offsetX, double offsetY)
{
rect.Offset(offsetX, offsetY);
return rect;
}
internal void UnionFinite(DataRect rect)
{
if (!rect.IsEmpty)
{
if (rect.xMin.IsInfinite())
rect.xMin = 0;
if (rect.yMin.IsInfinite())
rect.yMin = 0;
if (rect.width.IsInfinite())
rect.width = 0;
if (rect.height.IsInfinite())
rect.height = 0;
}
Union(rect);
}
public void Union(DataRect rect)
{
if (IsEmpty)
{
this = rect;
return;
}
else if (!rect.IsEmpty)
{
double minX = Math.Min(xMin, rect.xMin);
double minY = Math.Min(yMin, rect.yMin);
if (rect.width == Double.PositiveInfinity || this.width == Double.PositiveInfinity)
{
this.width = Double.PositiveInfinity;
}
else
{
double maxX = Math.Max(XMax, rect.XMax);
this.width = Math.Max(maxX - minX, 0.0);
}
if (rect.height == Double.PositiveInfinity || this.height == Double.PositiveInfinity)
{
this.height = Double.PositiveInfinity;
}
else
{
double maxY = Math.Max(YMax, rect.YMax);
this.height = Math.Max(maxY - minY, 0.0);
}
this.xMin = minX;
this.yMin = minY;
}
}
public void Union(Point point)
{
this.Union(new DataRect(point, point));
}
public static DataRect Union(DataRect rect, Point point)
{
rect.Union(point);
return rect;
}
public static DataRect Union(DataRect rect1, DataRect rect2)
{
rect1.Union(rect2);
return rect1;
}
internal string ConvertToString(string format, IFormatProvider provider)
{
if (IsEmpty)
return "Empty";
char listSeparator = TokenizerHelper.GetNumericListSeparator(provider);
return String.Format(provider, "{1:" + format + "}{0}{2:" + format + "}{0}{3:" + format + "}{0}{4:" + format + "}", listSeparator, xMin, yMin, width, height);
}
/// <summary>
/// Parses the specified string as a DataRect.
/// </summary>
/// <remarks>
/// There are three possible string patterns, that are recognized as string representation of DataRect:
/// 1) Literal string "Empty" - represents an DataRect.Empty rect;
/// 2) String in format "d,d,d,d", where d is a floating-point number with '.' as decimal separator - is considered as a string
/// of "XMin,YMin,Width,Height";
/// 3) String in format "d,d d,d", where d is a floating-point number with '.' as decimal separator - is considered as a string
/// of "XMin,YMin XMax,YMax".
/// </remarks>
/// <param name="source">The source.</param>
/// <returns>DataRect, parsed from the given input string.</returns>
public static DataRect Parse(string source)
{
DataRect rect;
IFormatProvider cultureInfo = CultureInfo.GetCultureInfo("en-us");
if (source == "Empty")
{
rect = DataRect.Empty;
}
else
{
// format X,Y,Width,Height
string[] values = source.Split(',');
if (values.Length == 4)
{
rect = new DataRect(
Convert.ToDouble(values[0], cultureInfo),
Convert.ToDouble(values[1], cultureInfo),
Convert.ToDouble(values[2], cultureInfo),
Convert.ToDouble(values[3], cultureInfo)
);
}
else
{
// format XMin, YMin - XMax, YMax
values = source.Split(new Char[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries);
rect = DataRect.Create(
Convert.ToDouble(values[0], cultureInfo),
Convert.ToDouble(values[1], cultureInfo),
Convert.ToDouble(values[2], cultureInfo),
Convert.ToDouble(values[3], cultureInfo)
);
}
}
return rect;
}
#region IFormattable Members
string IFormattable.ToString(string format, IFormatProvider formatProvider)
{
return ConvertToString(format, formatProvider);
}
#endregion
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Globalization;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public sealed class DataRectConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(string)) || base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
{
throw base.GetConvertFromException(value);
}
string source = value as string;
if (source != null)
{
return DataRect.Parse(source);
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType != null && value is DataRect)
{
DataRect rect = (DataRect)value;
if (destinationType == typeof(string))
{
return rect.ConvertToString(null, culture);
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}

View File

@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Globalization;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public sealed class DataRectSerializer : ValueSerializer
{
public override bool CanConvertFromString(string value, IValueSerializerContext context)
{
return true;
}
public override bool CanConvertToString(object value, IValueSerializerContext context)
{
return value is DataRect;
}
public override object ConvertFromString(string value, IValueSerializerContext context)
{
if (value != null)
{
return DataRect.Parse(value);
}
return base.ConvertFromString(value, context);
}
public override string ConvertToString(object value, IValueSerializerContext context)
{
if (value is DataRect)
{
DataRect rect = (DataRect)value;
return rect.ConvertToString(null, CultureInfo.GetCultureInfo("en-us"));
}
return base.ConvertToString(value, context);
}
}
}

42
Common/Footer.cs Normal file
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
{
/// <summary>
/// Represents a text in ChartPlotter's footer.
/// </summary>
public class Footer : ContentControl, IPlotterElement
{
/// <summary>
/// Initializes a new instance of the <see cref="Footer"/> class.
/// </summary>
public Footer()
{
HorizontalAlignment = HorizontalAlignment.Center;
Margin = new Thickness(0, 0, 0, 3);
}
void IPlotterElement.OnPlotterAttached(Plotter plotter)
{
this.plotter = plotter;
plotter.FooterPanel.Children.Add(this);
}
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
{
plotter.FooterPanel.Children.Remove(this);
this.plotter = null;
}
private Plotter plotter;
Plotter IPlotterElement.Plotter
{
get { return plotter; ; }
}
}
}

42
Common/Header.cs Normal file
View File

@@ -0,0 +1,42 @@
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Markup;
namespace Microsoft.Research.DynamicDataDisplay
{
public class Header : ContentControl, IPlotterElement
{
public Header()
{
FontSize = 18;
HorizontalAlignment = HorizontalAlignment.Center;
Margin = new Thickness(0, 0, 0, 3);
}
private Plotter plotter;
public Plotter Plotter
{
get { return plotter; }
}
public void OnPlotterAttached(Plotter plotter)
{
this.plotter = plotter;
plotter.HeaderPanel.Children.Add(this);
}
public void OnPlotterDetaching(Plotter plotter)
{
this.plotter = null;
plotter.HeaderPanel.Children.Remove(this);
}
}
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Markup;
using Microsoft.Research.DynamicDataDisplay.Charts;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>
/// Represents a title of horizontal axis. Can be placed from top or bottom of Plotter.
/// </summary>
public class HorizontalAxisTitle : ContentControl, IPlotterElement
{
/// <summary>
/// Initializes a new instance of the <see cref="HorizontalAxisTitle"/> class.
/// </summary>
public HorizontalAxisTitle()
{
FontSize = 16;
HorizontalAlignment = HorizontalAlignment.Center;
}
private Plotter plotter;
public Plotter Plotter
{
get { return plotter; }
}
public void OnPlotterAttached(Plotter plotter)
{
this.plotter = plotter;
AddToPlotter();
}
public void OnPlotterDetaching(Plotter plotter)
{
RemoveFromPlotter();
this.plotter = null;
}
private Panel GetHostPanel(Plotter plotter)
{
if (placement == AxisPlacement.Bottom)
return plotter.BottomPanel;
else
return plotter.TopPanel;
}
private int GetInsertPosition(Panel panel)
{
if (placement == AxisPlacement.Bottom)
return panel.Children.Count;
else
return 0;
}
private AxisPlacement placement = AxisPlacement.Bottom;
/// <summary>
/// Gets or sets the placement of axis title.
/// </summary>
/// <value>The placement.</value>
public AxisPlacement Placement
{
get { return placement; }
set
{
if (!value.IsBottomOrTop())
throw new ArgumentException(String.Format("HorizontalAxisTitle only supports Top and Bottom values of AxisPlacement, you passed '{0}'", value), "Placement");
if (placement != value)
{
if (plotter != null)
{
RemoveFromPlotter();
}
placement = value;
if (plotter != null)
{
AddToPlotter();
}
}
}
}
private void RemoveFromPlotter()
{
var oldPanel = GetHostPanel(plotter);
oldPanel.Children.Remove(this);
}
private void AddToPlotter()
{
var hostPanel = GetHostPanel(plotter);
var index = GetInsertPosition(hostPanel);
hostPanel.Children.Insert(index, this);
}
}
}

View File

@@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Media;
using System.Collections.Specialized;
using System.Collections;
using System.ComponentModel;
using System.Windows.Controls;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
/// <summary>
/// Represents a custom Panel, which performs Arrange of its children independently, and does not remeasure or rearrange itself or all children when one child is
/// added or removed.
/// Is intended to be a base class for special layout panels, in which each childr is arranged independently from each other child,
/// e.g. panel with child's position viewport bound to a rectangle in viewport coordinates.
/// </summary>
public abstract class IndividualArrangePanel : Panel
{
/// <summary>
/// Initializes a new instance of the <see cref="IndependentArrangePanel"/> class.
/// </summary>
protected IndividualArrangePanel() { }
private UIChildrenCollection children;
/// <summary>
/// Creates a new <see cref="T:System.Windows.Controls.UIElementCollection"/>.
/// </summary>
/// <param name="logicalParent">The logical parent element of the collection to be created.</param>
/// <returns>
/// An ordered collection of elements that have the specified logical parent.
/// </returns>
protected sealed override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
{
children = new UIChildrenCollection(this, logicalParent);
return children;
}
internal bool InBatchAdd
{
get { return children.IsAddingMany; }
}
internal virtual void BeginBatchAdd()
{
children.IsAddingMany = true;
}
internal virtual void EndBatchAdd()
{
children.IsAddingMany = false;
}
/// <summary>
/// Called when child is added.
/// </summary>
/// <param name="child">The added child.</param>
protected internal virtual void OnChildAdded(FrameworkElement child) { }
#region Overrides
/// <summary>
/// Overrides <see cref="M:System.Windows.Media.Visual.GetVisualChild(System.Int32)"/>, and returns a child at the specified index from a collection of child elements.
/// </summary>
/// <param name="index">The zero-based index of the requested child element in the collection.</param>
/// <returns>
/// The requested child element. This should not return null; if the provided index is out of range, an exception is thrown.
/// </returns>
protected sealed override Visual GetVisualChild(int index)
{
return Children[index];
}
/// <summary>
/// Gets the number of visual child elements within this element.
/// </summary>
/// <value></value>
/// <returns>
/// The number of visual child elements for this element.
/// </returns>
protected sealed override int VisualChildrenCount
{
get { return Children.Count; }
}
/// <summary>
/// Gets an enumerator for logical child elements of this element.
/// </summary>
/// <value></value>
/// <returns>
/// An enumerator for logical child elements of this element.
/// </returns>
protected sealed override IEnumerator LogicalChildren
{
get
{
return Children.GetEnumerator();
}
}
#endregion
internal Vector InternalVisualOffset
{
get { return VisualOffset; }
set { VisualOffset = value; }
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay
{
[Conditional("DEBUG")]
[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=false)]
internal sealed class NotNullAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
internal interface INotifyingPanel
{
NotifyingUIElementCollection NotifyingChildren { get; }
event EventHandler ChildrenCreated;
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
internal sealed class NotifyingCanvas : Canvas, INotifyingPanel
{
#region INotifyingPanel Members
private NotifyingUIElementCollection notifyingChildren;
public NotifyingUIElementCollection NotifyingChildren
{
get { return notifyingChildren; }
}
protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
{
notifyingChildren = new NotifyingUIElementCollection(this, logicalParent);
ChildrenCreated.Raise(this);
return notifyingChildren;
}
public event EventHandler ChildrenCreated;
#endregion
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
internal sealed class NotifyingGrid : Grid, INotifyingPanel
{
#region INotifyingPanel Members
private NotifyingUIElementCollection notifyingChildren;
public NotifyingUIElementCollection NotifyingChildren
{
get { return notifyingChildren; }
}
protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
{
notifyingChildren = new NotifyingUIElementCollection(this, logicalParent);
ChildrenCreated.Raise(this);
return notifyingChildren;
}
public event EventHandler ChildrenCreated;
#endregion
}
}

View File

@@ -0,0 +1,37 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
internal sealed class NotifyingStackPanel : StackPanel, INotifyingPanel
{
#region INotifyingPanel Members
private NotifyingUIElementCollection notifyingChildren;
public NotifyingUIElementCollection NotifyingChildren
{
get { return notifyingChildren; }
}
protected override UIElementCollection CreateUIElementCollection(FrameworkElement logicalParent)
{
notifyingChildren = new NotifyingUIElementCollection(this, logicalParent);
ChildrenCreated.Raise(this);
return notifyingChildren;
}
public event EventHandler ChildrenCreated;
#endregion
public override string ToString()
{
return typeof(NotifyingStackPanel).Name + " Name: " + Name;
}
}
}

View File

@@ -0,0 +1,106 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
internal sealed class NotifyingUIElementCollection : UIElementCollection, INotifyCollectionChanged
{
public NotifyingUIElementCollection(UIElement visualParent, FrameworkElement logicalParent)
: base(visualParent, logicalParent)
{
collection.CollectionChanged += OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
CollectionChanged.Raise(this, e);
}
#region Overrides
private readonly D3UIElementCollection collection = new D3UIElementCollection();
public override int Add(UIElement element)
{
collection.Add(element);
return base.Add(element);
}
public override void Clear()
{
collection.Clear();
base.Clear();
}
public override void Insert(int index, UIElement element)
{
collection.Insert(index, element);
base.Insert(index, element);
}
public override void Remove(UIElement element)
{
collection.Remove(element);
base.Remove(element);
}
public override void RemoveAt(int index)
{
collection.RemoveAt(index);
base.RemoveAt(index);
}
public override void RemoveRange(int index, int count)
{
for (int i = index; i < index + count; i++)
{
collection.RemoveAt(i);
}
base.RemoveRange(index, count);
}
public override UIElement this[int index]
{
get
{
return base[index];
}
set
{
collection[index] = value;
base[index] = value;
}
}
public override int Count
{
get
{
return collection.Count;
}
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
}
internal sealed class D3UIElementCollection : D3Collection<UIElement>
{
protected override void OnItemAdding(UIElement item)
{
if (item == null)
throw new ArgumentNullException("item");
}
}
}

View File

@@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Specialized;
using System.Collections.ObjectModel;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public class ObservableCollectionWrapper<T> : INotifyCollectionChanged, IList<T>
{
public ObservableCollectionWrapper() : this(new ObservableCollection<T>()) { }
private readonly ObservableCollection<T> collection;
public ObservableCollectionWrapper(ObservableCollection<T> collection)
{
if (collection == null)
throw new ArgumentNullException("collection");
this.collection = collection;
collection.CollectionChanged += new NotifyCollectionChangedEventHandler(collection_CollectionChanged);
}
private int attemptsToRaiseChanged = 0;
private bool raiseEvents = true;
public bool RaisingEvents
{
get { return raiseEvents; }
}
private void collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
attemptsToRaiseChanged++;
if (raiseEvents)
{
CollectionChanged.Raise(this, e);
}
}
#region Update methods
public void BeginUpdate()
{
attemptsToRaiseChanged = 0;
raiseEvents = false;
}
public void EndUpdate()
{
raiseEvents = true;
if (attemptsToRaiseChanged > 0)
CollectionChanged.Raise(this);
}
public IDisposable BlockEvents()
{
return new EventBlocker<T>(this);
}
private sealed class EventBlocker<TT> : IDisposable
{
private readonly ObservableCollectionWrapper<TT> collection;
public EventBlocker(ObservableCollectionWrapper<TT> collection)
{
this.collection = collection;
collection.BeginUpdate();
}
#region IDisposable Members
public void Dispose()
{
collection.EndUpdate();
}
#endregion
}
#endregion // end of Update methods
#region IList<T> Members
public int IndexOf(T item)
{
return collection.IndexOf(item);
}
public void Insert(int index, T item)
{
collection.Insert(index, item);
}
public void RemoveAt(int index)
{
collection.RemoveAt(index);
}
public T this[int index]
{
get
{
return collection[index];
}
set
{
collection[index] = value;
}
}
#endregion
#region ICollection<T> Members
public void Add(T item)
{
collection.Add(item);
}
public void Clear()
{
collection.Clear();
}
public bool Contains(T item)
{
return collection.Contains(item);
}
public void CopyTo(T[] array, int arrayIndex)
{
collection.CopyTo(array, arrayIndex);
}
public int Count
{
get { return collection.Count; }
}
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public bool Remove(T item)
{
return collection.Remove(item);
}
#endregion
#region IEnumerable<T> Members
public IEnumerator<T> GetEnumerator()
{
return collection.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Windows;
using System.Windows.Controls;
namespace Microsoft.Research.DynamicDataDisplay
{
public sealed class UIElementCollectionChangedEventArgs : EventArgs
{
private readonly UIElement element;
public UIElementCollectionChangedEventArgs(UIElement element)
{
this.element = element;
}
public UIElement Element
{
get
{
return element;
}
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
/// <summary>
/// Represents a base class for decorating palette, which wraps another palette and intercepts calls to it.
/// </summary>
public abstract class DecoratorPaletteBase : PaletteBase
{
/// <summary>
/// Initializes a new instance of the <see cref="DecoratorPaletteBase"/> class.
/// </summary>
protected DecoratorPaletteBase() { }
/// <summary>
/// Initializes a new instance of the <see cref="DecoratorPaletteBase"/> class.
/// </summary>
/// <param name="palette">The palette.</param>
public DecoratorPaletteBase(IPalette palette)
{
Palette = palette;
}
private IPalette palette = null;
/// <summary>
/// Gets or sets the palette being decorated.
/// </summary>
/// <value>The palette.</value>
public IPalette Palette
{
get { return palette; }
set
{
if (value == null)
throw new ArgumentNullException("value");
if (palette != null)
palette.Changed -= OnChildPaletteChanged;
palette = value;
palette.Changed += OnChildPaletteChanged;
RaiseChanged();
}
}
void OnChildPaletteChanged(object sender, EventArgs e)
{
RaiseChanged();
}
/// <summary>
/// Gets the color by interpolation coefficient.
/// </summary>
/// <param name="t">Interpolation coefficient, should belong to [0..1].</param>
/// <returns>Color.</returns>
public override Color GetColor(double t)
{
return palette.GetColor(t);
}
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
public sealed class DelegatePalette : PaletteBase
{
public DelegatePalette(Func<double, Color> func)
{
if (func == null)
throw new ArgumentNullException("func");
this.func = func;
}
private readonly Func<double, Color> func;
public override Color GetColor(double t)
{
return func(t);
}
}
}

View File

@@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.ComponentModel;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
public sealed class HSBPalette : IPalette
{
/// <summary>
/// Initializes a new instance of the <see cref="HSBPalette"/> class.
/// </summary>
public HSBPalette() { }
private double start = 0;
[DefaultValue(0.0)]
public double Start
{
get { return start; }
set
{
if (start != value)
{
start = value;
Changed.Raise(this);
}
}
}
private double width = 360;
[DefaultValue(360.0)]
public double Width
{
get { return width; }
set
{
if (width != value)
{
width = value;
Changed.Raise(this);
}
}
}
#region IPalette Members
public Color GetColor(double t)
{
Verify.IsTrue(0 <= t && t <= 1);
return new HsbColor(start + t * width, 1, 1).ToArgbColor();
}
public event EventHandler Changed;
#endregion
}
}

View File

@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
/// <summary>
/// Represents a color palette, which can generate color by interpolation coefficient.
/// </summary>
public interface IPalette
{
/// <summary>
/// Gets the color by interpolation coefficient.
/// </summary>
/// <param name="t">Interpolation coefficient, should belong to [0..1].</param>
/// <returns>Color.</returns>
Color GetColor(double t);
/// <summary>
/// Occurs when palette changes.
/// </summary>
event EventHandler Changed;
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
/// <summary>
/// Represents a color step with its offset in limits [0..1].
/// </summary>
[DebuggerDisplay("Color={Color}, Offset={Offset}")]
public class LinearPaletteColorStep
{
/// <summary>
/// Initializes a new instance of the <see cref="LinearPaletteColorStep"/> class.
/// </summary>
public LinearPaletteColorStep() { }
/// <summary>
/// Initializes a new instance of the <see cref="LinearPaletteColorStep"/> class.
/// </summary>
/// <param name="color">The color.</param>
/// <param name="offset">The offset.</param>
public LinearPaletteColorStep(Color color, double offset)
{
this.Color = color;
this.Offset = offset;
}
/// <summary>
/// Gets or sets the color.
/// </summary>
/// <value>The color.</value>
public Color Color { get; set; }
/// <summary>
/// Gets or sets the offset.
/// </summary>
/// <value>The offset.</value>
public double Offset { get; set; }
}
/// <summary>
/// Represents a palette with start and stop colors and intermediate colors with their custom offsets.
/// </summary>
public class LinearPalette : IPalette
{
/// <summary>
/// Initializes a new instance of the <see cref="LinearPalette"/> class.
/// </summary>
/// <param name="startColor">The start color.</param>
/// <param name="endColor">The end color.</param>
/// <param name="steps">The steps.</param>
public LinearPalette(Color startColor, Color endColor, params LinearPaletteColorStep[] steps)
{
this.steps.Add(new LinearPaletteColorStep(startColor, 0));
if (steps != null)
this.steps.AddMany(steps);
this.steps.Add(new LinearPaletteColorStep(endColor, 1));
}
private readonly List<LinearPaletteColorStep> steps = new List<LinearPaletteColorStep>();
#region IPalette Members
/// <summary>
/// Gets the color by interpolation coefficient.
/// </summary>
/// <param name="t">Interpolation coefficient, should belong to [0..1].</param>
/// <returns>Color.</returns>
public Color GetColor(double t)
{
if (t < 0) return steps[0].Color;
if (t > 1) return steps[steps.Count - 1].Color;
int i = 0;
double x = 0;
while (x <= t)
{
x = steps[i + 1].Offset;
i++;
}
Color c0 = steps[i - 1].Color;
Color c1 = steps[i].Color;
double ratio = (t - steps[i - 1].Offset) / (steps[i].Offset - steps[i - 1].Offset);
Color result = Color.FromRgb(
(byte)((1 - ratio) * c0.R + ratio * c1.R),
(byte)((1 - ratio) * c0.G + ratio * c1.G),
(byte)((1 - ratio) * c0.B + ratio * c1.B));
return result;
}
#pragma warning disable 0067
public event EventHandler Changed;
#pragma warning restore 0067
#endregion
}
}

View File

@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
/// <summary>
/// Contains some predefined linear palettes.
/// </summary>
public static class LinearPalettes
{
private const double geoHeight = 8845 + 400;
private static LinearPalette geoHeightsPalette = new LinearPalette(
Color.FromRgb(1, 99, 69), Colors.White,
new LinearPaletteColorStep(Color.FromRgb(28, 128, 52), (50 + 400) / geoHeight),
new LinearPaletteColorStep(Color.FromRgb(229, 209, 119), (200 + 400) / geoHeight),
new LinearPaletteColorStep(Color.FromRgb(160, 66, 1), (1000 + 400) / geoHeight),
new LinearPaletteColorStep(Color.FromRgb(129, 32, 32), (2000 + 400) / geoHeight),
new LinearPaletteColorStep(Color.FromRgb(119, 119, 119), (4000 + 400) / geoHeight),
new LinearPaletteColorStep(Color.FromRgb(244, 244, 244), (6000 + 400) / geoHeight));
/// <summary>
/// Gets the palette for geo height map visualization.
/// </summary>
/// <value>The geo heights palette.</value>
public static LinearPalette GeoHeightsPalette { get { return geoHeightsPalette; } }
}
}

View File

@@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
/// <summary>
/// Represents a palette that calculates minimal and maximal values of interpolation coefficient and every 100000 calls writes these values
/// to DEBUG console.
/// </summary>
public class MinMaxLoggingPalete : DecoratorPaletteBase
{
double min = Double.MaxValue;
double max = Double.MinValue;
int counter = 0;
public override Color GetColor(double t)
{
if (t < min) min = t;
if (t > max) max = t;
counter++;
if (counter % 100000 == 0)
{
Debug.WriteLine("Palette: Min = " + min + ", max = " + max);
}
return base.GetColor(t);
}
}
}

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.Common.Palettes
{
/// <summary>
/// Represents a simple base class for a palette. Contains an abstract merhod for creation of color and method to raise changed event.
/// </summary>
public abstract class PaletteBase : IPalette
{
#region IPalette Members
/// <summary>
/// Gets the color by interpolation coefficient.
/// </summary>
/// <param name="t">Interpolation coefficient, should belong to [0..1].</param>
/// <returns>Color.</returns>
public abstract Color GetColor(double t);
protected void RaiseChanged()
{
Changed.Raise(this);
}
/// <summary>
/// Occurs when palette changes.
/// </summary>
public event EventHandler Changed;
#endregion
}
}

View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
public class TransparentLimitsPalette : DecoratorPaletteBase
{
public TransparentLimitsPalette()
{
}
public TransparentLimitsPalette(IPalette palette) : base(palette) { }
public override Color GetColor(double t)
{
if (t < 0 || t > 1) return Colors.Transparent;
return Palette.GetColor(t);
}
}
}

View File

@@ -0,0 +1,133 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.ComponentModel;
using System.Windows.Markup;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
public class UniformPaletteColorStep
{
public Color Color { get; set; }
}
[ContentProperty("ColorSteps_XAML")]
public sealed class UniformLinearPalette : IPalette, ISupportInitialize
{
private double[] points;
private ObservableCollection<Color> colors = new ObservableCollection<Color>();
public ObservableCollection<Color> ColorSteps
{
get { return colors; }
}
private List<UniformPaletteColorStep> colorSteps_XAML = new List<UniformPaletteColorStep>();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
[EditorBrowsable(EditorBrowsableState.Never)]
public List<UniformPaletteColorStep> ColorSteps_XAML
{
get { return colorSteps_XAML; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
private void RaiseChanged()
{
Changed.Raise(this);
}
public event EventHandler Changed;
public UniformLinearPalette() { }
public UniformLinearPalette(params Color[] colors)
{
if (colors == null) throw new ArgumentNullException("colors");
if (colors.Length < 2) throw new ArgumentException(Strings.Exceptions.PaletteTooFewColors);
this.colors = new ObservableCollection<Color>(colors);
FillPoints(colors.Length);
}
private void FillPoints(int size)
{
points = new double[size];
for (int i = 0; i < size; i++)
{
points[i] = i / (double)(size - 1);
}
}
private bool increaseBrightness = true;
[DefaultValue(true)]
public bool IncreaseBrightness
{
get { return increaseBrightness; }
set { increaseBrightness = value; }
}
public Color GetColor(double t)
{
Verify.AssertIsFinite(t);
Verify.IsTrue(0 <= t && t <= 1);
if (t <= 0)
return colors[0];
else if (t >= 1)
return colors[colors.Count - 1];
else
{
int i = 0;
while (points[i] < t)
i++;
double ratio = (points[i] - t) / (points[i] - points[i - 1]);
Verify.IsTrue(0 <= ratio && ratio <= 1);
Color c0 = colors[i - 1];
Color c1 = colors[i];
Color res = Color.FromRgb(
(byte)(c0.R * ratio + c1.R * (1 - ratio)),
(byte)(c0.G * ratio + c1.G * (1 - ratio)),
(byte)(c0.B * ratio + c1.B * (1 - ratio)));
// Increasing saturation and brightness
if (increaseBrightness)
{
HsbColor hsb = res.ToHsbColor();
//hsb.Saturation = 0.5 * (1 + hsb.Saturation);
hsb.Brightness = 0.5 * (1 + hsb.Brightness);
return hsb.ToArgbColor();
}
else
{
return res;
}
}
}
#region ISupportInitialize Members
bool beganInit = false;
public void BeginInit()
{
beganInit = true;
}
public void EndInit()
{
if (beganInit)
{
colors = new ObservableCollection<Color>(colorSteps_XAML.Select(step => step.Color));
FillPoints(colors.Count);
}
}
#endregion
}
}

View File

@@ -0,0 +1,45 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
namespace Microsoft.Research.DynamicDataDisplay.Common.Palettes
{
public static class UniformLinearPalettes
{
static UniformLinearPalettes()
{
blackAndWhitePalette.IncreaseBrightness = false;
rgbPalette.IncreaseBrightness = false;
blueOrangePalette.IncreaseBrightness = false;
}
private static readonly UniformLinearPalette blackAndWhitePalette =
new UniformLinearPalette(Colors.Black, Colors.White);
public static UniformLinearPalette BlackAndWhitePalette
{
get { return blackAndWhitePalette; }
}
private static readonly UniformLinearPalette rgbPalette =
new UniformLinearPalette(Colors.Blue, Color.FromRgb(0, 255, 0), Colors.Red);
public static UniformLinearPalette RedGreenBluePalette
{
get { return rgbPalette; }
}
private static readonly UniformLinearPalette blueOrangePalette = new UniformLinearPalette(
Colors.Blue,
Colors.Cyan,
Colors.Yellow,
Colors.Orange);
public static UniformLinearPalette BlueOrangePalette
{
get { return blueOrangePalette; }
}
}
}

952
Common/Plotter.cs Normal file
View File

@@ -0,0 +1,952 @@
using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Research.DynamicDataDisplay.Common;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Windows.Markup;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Windows.Input;
using Microsoft.Research.DynamicDataDisplay.Common.UndoSystem;
using Microsoft.Research.DynamicDataDisplay.Navigation;
using System.Windows.Data;
using Microsoft.Research.DynamicDataDisplay.Charts.Navigation;
using System.Windows.Automation.Peers;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>Plotter is a base control for displaying various graphs. It provides
/// means to draw chart itself and side space for axes, annotations, etc</summary>
[ContentProperty("Children")]
[TemplatePart(Name = "PART_HeaderPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_FooterPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_BottomPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_LeftPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_RightPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_TopPanel", Type = typeof(StackPanel))]
[TemplatePart(Name = "PART_MainCanvas", Type = typeof(Canvas))]
[TemplatePart(Name = "PART_CentralGrid", Type = typeof(Grid))]
[TemplatePart(Name = "PART_MainGrid", Type = typeof(Grid))]
[TemplatePart(Name = "PART_ContentsGrid", Type = typeof(Grid))]
[TemplatePart(Name = "PART_ParallelCanvas", Type = typeof(Canvas))]
public abstract class Plotter : ContentControl
{
private PlotterLoadMode loadMode = PlotterLoadMode.Normal;
protected PlotterLoadMode LoadMode
{
get { return loadMode; }
}
protected Plotter() : this(PlotterLoadMode.Normal) { }
/// <summary>
/// Initializes a new instance of the <see cref="Plotter"/> class.
/// </summary>
protected Plotter(PlotterLoadMode loadMode)
{
this.loadMode = loadMode;
SetPlotter(this, this);
if (loadMode == PlotterLoadMode.Normal)
{
UpdateUIParts();
}
children = new PlotterChildrenCollection(this);
children.CollectionChanged += OnChildrenCollectionChanged;
Loaded += Plotter_Loaded;
Unloaded += Plotter_Unloaded;
genericResources = (ResourceDictionary)Application.LoadComponent(new Uri("/DynamicDataDisplay;component/Themes/Generic.xaml", UriKind.Relative));
ContextMenu = null;
}
void Plotter_Unloaded(object sender, RoutedEventArgs e)
{
OnUnloaded();
}
protected virtual void OnUnloaded() { }
protected override AutomationPeer OnCreateAutomationPeer()
{
return new PlotterAutomationPeer(this);
}
[EditorBrowsable(EditorBrowsableState.Never)]
public override bool ShouldSerializeContent()
{
return false;
}
protected override bool ShouldSerializeProperty(DependencyProperty dp)
{
// do not serialize context menu if it was created by DefaultContextMenu, because that context menu items contains references of plotter
if (dp == ContextMenuProperty && children.Any(el => el is DefaultContextMenu)) return false;
if (dp == TemplateProperty) return false;
if (dp == ContentProperty) return false;
return base.ShouldSerializeProperty(dp);
}
private const string templateKey = "defaultPlotterTemplate";
private const string styleKey = "defaultPlotterStyle";
private void UpdateUIParts()
{
ResourceDictionary dict = new ResourceDictionary
{
Source = new Uri("/DynamicDataDisplay;component/Common/PlotterStyle.xaml", UriKind.Relative)
};
Resources.MergedDictionaries.Add(dict);
Style = (Style)dict[styleKey];
ControlTemplate template = (ControlTemplate)dict[templateKey];
Template = template;
ApplyTemplate();
}
private ResourceDictionary genericResources;
protected ResourceDictionary GenericResources
{
get { return genericResources; }
}
/// <summary>
/// Forces plotter to load.
/// </summary>
public void PerformLoad()
{
isLoadedIntensionally = true;
ApplyTemplate();
Plotter_Loaded(null, null);
}
private bool isLoadedIntensionally = false;
protected virtual bool IsLoadedInternal
{
get { return isLoadedIntensionally || IsLoaded; }
}
private void Plotter_Loaded(object sender, RoutedEventArgs e)
{
ExecuteWaitingChildrenAdditions();
OnLoaded();
}
protected internal void ExecuteWaitingChildrenAdditions()
{
foreach (var action in waitingForExecute)
{
action();
}
waitingForExecute.Clear();
}
protected virtual void OnLoaded()
{
// this is done to enable keyboard shortcuts
Focus();
//foreach (var plotterElement in elementsWaitingForBeingAttached)
//{
// if (Children.Contains(plotterElement))
// {
// plotterElement.OnPlotterAttached(this);
// }
//}
//elementsWaitingForBeingAttached.Clear();
}
protected override void OnTemplateChanged(ControlTemplate oldTemplate, ControlTemplate newTemplate)
{
base.OnTemplateChanged(oldTemplate, newTemplate);
}
private Grid contentsGrid;
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
addedVisualElements.Clear();
foreach (var item in GetAllPanels())
{
INotifyingPanel panel = item as INotifyingPanel;
if (panel != null)
{
panel.ChildrenCreated -= notifyingItem_ChildrenCreated;
if (panel.NotifyingChildren != null)
{
panel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
}
}
}
var headerPanel = GetPart<StackPanel>("PART_HeaderPanel");
MigrateChildren(this.headerPanel, headerPanel);
this.headerPanel = headerPanel;
var footerPanel = GetPart<StackPanel>("PART_FooterPanel");
MigrateChildren(this.footerPanel, footerPanel);
this.footerPanel = footerPanel;
var leftPanel = GetPart<StackPanel>("PART_LeftPanel");
MigrateChildren(this.leftPanel, leftPanel);
this.leftPanel = leftPanel;
var bottomPanel = GetPart<StackPanel>("PART_BottomPanel");
MigrateChildren(this.bottomPanel, bottomPanel);
this.bottomPanel = bottomPanel;
var rightPanel = GetPart<StackPanel>("PART_RightPanel");
MigrateChildren(this.rightPanel, rightPanel);
this.rightPanel = rightPanel;
var topPanel = GetPart<StackPanel>("PART_TopPanel");
MigrateChildren(this.topPanel, topPanel);
this.topPanel = topPanel;
var mainCanvas = GetPart<Canvas>("PART_MainCanvas");
MigrateChildren(this.mainCanvas, mainCanvas);
this.mainCanvas = mainCanvas;
var centralGrid = GetPart<Grid>("PART_CentralGrid");
MigrateChildren(this.centralGrid, centralGrid);
this.centralGrid = centralGrid;
var mainGrid = GetPart<Grid>("PART_MainGrid");
MigrateChildren(this.mainGrid, mainGrid);
this.mainGrid = mainGrid;
var parallelCanvas = GetPart<Canvas>("PART_ParallelCanvas");
MigrateChildren(this.parallelCanvas, parallelCanvas);
this.parallelCanvas = parallelCanvas;
var contentsGrid = GetPart<Grid>("PART_ContentsGrid");
MigrateChildren(this.contentsGrid, contentsGrid);
this.contentsGrid = contentsGrid;
Content = contentsGrid;
AddLogicalChild(contentsGrid);
foreach (var notifyingItem in GetAllPanels())
{
INotifyingPanel panel = notifyingItem as INotifyingPanel;
if (panel != null)
{
if (panel.NotifyingChildren == null)
panel.ChildrenCreated += notifyingItem_ChildrenCreated;
else
panel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
}
}
}
private void MigrateChildren(Panel previousParent, Panel currentParent)
{
if (previousParent != null && currentParent != null)
{
UIElement[] children = new UIElement[previousParent.Children.Count];
previousParent.Children.CopyTo(children, 0);
previousParent.Children.Clear();
foreach (var child in children)
{
if (!currentParent.Children.Contains(child))
{
currentParent.Children.Add(child);
}
}
}
else if (previousParent != null)
{
previousParent.Children.Clear();
}
}
private void notifyingItem_ChildrenCreated(object sender, EventArgs e)
{
INotifyingPanel panel = (INotifyingPanel)sender;
SubscribePanelEvents(panel);
}
private void SubscribePanelEvents(INotifyingPanel panel)
{
panel.ChildrenCreated -= notifyingItem_ChildrenCreated;
panel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
panel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
}
private void OnVisualCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
foreach (var item in e.NewItems)
{
INotifyingPanel notifyingPanel = item as INotifyingPanel;
if (notifyingPanel != null)
{
if (notifyingPanel.NotifyingChildren != null)
{
notifyingPanel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
notifyingPanel.NotifyingChildren.CollectionChanged += OnVisualCollectionChanged;
}
else
{
notifyingPanel.ChildrenCreated += notifyingItem_ChildrenCreated;
}
}
OnVisualChildAdded((UIElement)item, (UIElementCollection)sender);
}
}
if (e.OldItems != null)
{
foreach (var item in e.OldItems)
{
INotifyingPanel notifyingPanel = item as INotifyingPanel;
if (notifyingPanel != null)
{
notifyingPanel.ChildrenCreated -= notifyingItem_ChildrenCreated;
if (notifyingPanel.NotifyingChildren != null)
{
notifyingPanel.NotifyingChildren.CollectionChanged -= OnVisualCollectionChanged;
}
}
OnVisualChildRemoved((UIElement)item, (UIElementCollection)sender);
}
}
}
private readonly VisualBindingCollection visualBindingCollection = new VisualBindingCollection();
public VisualBindingCollection VisualBindings
{
get { return visualBindingCollection; }
}
protected virtual void OnVisualChildAdded(UIElement target, UIElementCollection uIElementCollection)
{
IPlotterElement element = null;
if (addingElements.Count > 0)
{
element = addingElements.Peek();
var dict = visualBindingCollection.Cache;
var proxy = dict[element];
List<UIElement> visualElements;
if (!addedVisualElements.ContainsKey(element))
{
visualElements = new List<UIElement>();
addedVisualElements.Add(element, visualElements);
}
else
{
visualElements = addedVisualElements[element];
}
visualElements.Add(target);
SetBindings(proxy, target);
}
}
private void SetBindings(UIElement proxy, UIElement target)
{
if (proxy != target)
{
foreach (var property in GetPropertiesToSetBindingOn())
{
BindingOperations.SetBinding(target, property, new Binding { Path = new PropertyPath(property.Name), Source = proxy, Mode = BindingMode.TwoWay });
}
}
}
private void RemoveBindings(UIElement proxy, UIElement target)
{
if (proxy != target)
{
foreach (var property in GetPropertiesToSetBindingOn())
{
BindingOperations.ClearBinding(target, property);
}
}
}
private IEnumerable<DependencyProperty> GetPropertiesToSetBindingOn()
{
yield return UIElement.OpacityProperty;
yield return UIElement.VisibilityProperty;
yield return UIElement.IsHitTestVisibleProperty;
//yield return FrameworkElement.DataContextProperty;
}
protected virtual void OnVisualChildRemoved(UIElement target, UIElementCollection uiElementCollection)
{
IPlotterElement element = null;
if (removingElements.Count > 0)
{
element = removingElements.Peek();
var dict = visualBindingCollection.Cache;
var proxy = dict[element];
if (addedVisualElements.ContainsKey(element))
{
var list = addedVisualElements[element];
list.Remove(target);
if (list.Count == 0)
{
dict.Remove(element);
}
addedVisualElements.Remove(element);
}
RemoveBindings(proxy, target);
}
}
internal virtual IEnumerable<Panel> GetAllPanels()
{
yield return headerPanel;
yield return footerPanel;
yield return leftPanel;
yield return bottomPanel;
yield return rightPanel;
yield return topPanel;
yield return mainCanvas;
yield return centralGrid;
yield return mainGrid;
yield return parallelCanvas;
yield return contentsGrid;
}
private T GetPart<T>(string name)
{
return (T)Template.FindName(name, this);
}
#region Children and add/removed events handling
private readonly PlotterChildrenCollection children;
/// <summary>
/// Provides access to Plotter's children charts.
/// </summary>
/// <value>The children.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public PlotterChildrenCollection Children
{
[DebuggerStepThrough]
get { return children; }
}
private List<Action> waitingForExecute = new List<Action>();
bool executedWaitingChildrenAdding = false;
private void OnChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (IsLoadedInternal && !executedWaitingChildrenAdding)
{
executedWaitingChildrenAdding = true;
ExecuteWaitingChildrenAdditions();
}
if (e.NewItems != null)
{
foreach (IPlotterElement item in e.NewItems)
{
if (IsLoadedInternal)
{
OnChildAdded(item);
}
else
{
waitingForExecute.Add(() => OnChildAdded(item));
}
}
}
if (e.OldItems != null)
{
foreach (IPlotterElement item in e.OldItems)
{
if (IsLoadedInternal)
{
OnChildRemoving(item);
}
else
{
waitingForExecute.Add(() => OnChildRemoving(item));
}
}
}
}
private readonly Stack<IPlotterElement> addingElements = new Stack<IPlotterElement>();
bool performChildChecks = true;
internal bool PerformChildChecks
{
get { return performChildChecks; }
set { performChildChecks = value; }
}
private IPlotterElement currentChild = null;
protected IPlotterElement CurrentChild
{
get { return currentChild; }
}
protected virtual void OnChildAdded(IPlotterElement child)
{
if (child != null)
{
addingElements.Push(child);
currentChild = child;
try
{
UIElement visualProxy = CreateVisualProxy(child);
visualBindingCollection.Cache.Add(child, visualProxy);
if (performChildChecks && child.Plotter != null)
{
throw new InvalidOperationException(Strings.Exceptions.PlotterElementAddedToAnotherPlotter);
}
FrameworkElement styleableElement = child as FrameworkElement;
if (styleableElement != null)
{
Type key = styleableElement.GetType();
if (genericResources.Contains(key))
{
Style elementStyle = (Style)genericResources[key];
styleableElement.Style = elementStyle;
}
}
if (performChildChecks)
{
child.OnPlotterAttached(this);
if (child.Plotter != this)
{
throw new InvalidOperationException(Strings.Exceptions.InvalidParentPlotterValue);
}
}
DependencyObject dependencyObject = child as DependencyObject;
if (dependencyObject != null)
{
SetPlotter(dependencyObject, this);
}
}
finally
{
addingElements.Pop();
currentChild = null;
}
}
}
private UIElement CreateVisualProxy(IPlotterElement child)
{
if (visualBindingCollection.Cache.ContainsKey(child))
throw new InvalidOperationException(Strings.Exceptions.VisualBindingsWrongState);
UIElement result = child as UIElement;
if (result == null)
{
result = new UIElement();
}
return result;
}
private readonly Stack<IPlotterElement> removingElements = new Stack<IPlotterElement>();
protected virtual void OnChildRemoving(IPlotterElement child)
{
if (child != null)
{
currentChild = child;
removingElements.Push(child);
try
{
// todo probably here child.Plotter can be null.
if (performChildChecks && child.Plotter != this && child.Plotter != null)
{
throw new InvalidOperationException(Strings.Exceptions.InvalidParentPlotterValueRemoving);
}
if (performChildChecks)
{
if (child.Plotter != null)
child.OnPlotterDetaching(this);
if (child.Plotter != null)
{
throw new InvalidOperationException(Strings.Exceptions.ParentPlotterNotNull);
}
}
DependencyObject dependencyObject = child as DependencyObject;
if (dependencyObject != null)
{
SetPlotter(dependencyObject, null);
}
visualBindingCollection.Cache.Remove(child);
if (addedVisualElements.ContainsKey(child) && addedVisualElements[child].Count > 0)
{
throw new InvalidOperationException(String.Format(Strings.Exceptions.PlotterElementDidnotCleanedAfterItself, child.ToString()));
}
}
finally
{
currentChild = null;
removingElements.Pop();
}
}
}
private readonly Dictionary<IPlotterElement, List<UIElement>> addedVisualElements = new Dictionary<IPlotterElement, List<UIElement>>();
#endregion
#region Layout zones
private Panel parallelCanvas;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel ParallelCanvas
{
get { return parallelCanvas; }
protected set { parallelCanvas = value; }
}
private Panel headerPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel HeaderPanel
{
get { return headerPanel; }
protected set { headerPanel = value; }
}
private Panel footerPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel FooterPanel
{
get { return footerPanel; }
protected set { footerPanel = value; }
}
private Panel leftPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel LeftPanel
{
get { return leftPanel; }
protected set { leftPanel = value; }
}
private Panel rightPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel RightPanel
{
get { return rightPanel; }
protected set { rightPanel = value; }
}
private Panel topPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel TopPanel
{
get { return topPanel; }
protected set { topPanel = value; }
}
private Panel bottomPanel;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel BottomPanel
{
get { return bottomPanel; }
protected set { bottomPanel = value; }
}
private Panel mainCanvas;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel MainCanvas
{
get { return mainCanvas; }
protected set { mainCanvas = value; }
}
private Panel centralGrid;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel CentralGrid
{
get { return centralGrid; }
protected set { centralGrid = value; }
}
private Panel mainGrid;
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Panel MainGrid
{
get { return mainGrid; }
protected set { mainGrid = value; }
}
#endregion
#region Screenshots & copy to clipboard
public BitmapSource CreateScreenshot()
{
UIElement parent = (UIElement)Parent;
Rect renderBounds = new Rect(RenderSize);
Point p1 = renderBounds.TopLeft;
Point p2 = renderBounds.BottomRight;
if (parent != null)
{
p1 = TranslatePoint(p1, parent);
p2 = TranslatePoint(p2, parent);
}
Int32Rect rect = new Rect(p1, p2).ToInt32Rect();
return ScreenshotHelper.CreateScreenshot(this, rect);
}
/// <summary>Saves screenshot to file.</summary>
/// <param name="filePath">File path.</param>
public void SaveScreenshot(string filePath)
{
ScreenshotHelper.SaveBitmapToFile(CreateScreenshot(), filePath);
}
/// <summary>
/// Saves screenshot to stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <param name="fileExtension">The file type extension.</param>
public void SaveScreenshotToStream(Stream stream, string fileExtension)
{
ScreenshotHelper.SaveBitmapToStream(CreateScreenshot(), stream, fileExtension);
}
/// <summary>Copies the screenshot to clipboard.</summary>
public void CopyScreenshotToClipboard()
{
Clipboard.Clear();
Clipboard.SetImage(CreateScreenshot());
}
#endregion
#region IsDefaultElement attached property
protected void SetAllChildrenAsDefault()
{
foreach (var child in Children.OfType<DependencyObject>())
{
child.SetValue(IsDefaultElementProperty, true);
}
}
/// <summary>Gets a value whether specified graphics object is default to this plotter or not</summary>
/// <param name="obj">Graphics object to check</param>
/// <returns>True if it is default or false otherwise</returns>
public static bool GetIsDefaultElement(DependencyObject obj)
{
return (bool)obj.GetValue(IsDefaultElementProperty);
}
public static void SetIsDefaultElement(DependencyObject obj, bool value)
{
obj.SetValue(IsDefaultElementProperty, value);
}
public static readonly DependencyProperty IsDefaultElementProperty = DependencyProperty.RegisterAttached(
"IsDefaultElement",
typeof(bool),
typeof(Plotter),
new UIPropertyMetadata(false));
/// <summary>Removes all user graphs from given UIElementCollection,
/// leaving only default graphs</summary>
protected static void RemoveUserElements(IList<IPlotterElement> elements)
{
int index = 0;
while (index < elements.Count)
{
DependencyObject d = elements[index] as DependencyObject;
if (d != null && !GetIsDefaultElement(d))
{
elements.RemoveAt(index);
}
else
{
index++;
}
}
}
public void RemoveUserElements()
{
RemoveUserElements(Children);
}
#endregion
#region IsDefaultAxis
public static bool GetIsDefaultAxis(DependencyObject obj)
{
return (bool)obj.GetValue(IsDefaultAxisProperty);
}
public static void SetIsDefaultAxis(DependencyObject obj, bool value)
{
obj.SetValue(IsDefaultAxisProperty, value);
}
public static readonly DependencyProperty IsDefaultAxisProperty = DependencyProperty.RegisterAttached(
"IsDefaultAxis",
typeof(bool),
typeof(Plotter),
new UIPropertyMetadata(false, OnIsDefaultAxisChanged));
private static void OnIsDefaultAxisChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Plotter parentPlotter = null;
IPlotterElement plotterElement = d as IPlotterElement;
if (plotterElement != null)
{
parentPlotter = plotterElement.Plotter;
if (parentPlotter != null)
{
parentPlotter.OnIsDefaultAxisChangedCore(d, e);
}
}
}
protected virtual void OnIsDefaultAxisChangedCore(DependencyObject d, DependencyPropertyChangedEventArgs e) { }
#endregion
#region Undo
private readonly UndoProvider undoProvider = new UndoProvider();
public UndoProvider UndoProvider
{
get { return undoProvider; }
}
#endregion
#region Plotter attached property
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public static Plotter GetPlotter(DependencyObject obj)
{
return (Plotter)obj.GetValue(PlotterProperty);
}
public static void SetPlotter(DependencyObject obj, Plotter value)
{
obj.SetValue(PlotterProperty, value);
}
public static readonly DependencyProperty PlotterProperty = DependencyProperty.RegisterAttached(
"Plotter",
typeof(Plotter),
typeof(Plotter),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnPlotterChanged));
private static void OnPlotterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
FrameworkElement element = d as FrameworkElement;
Plotter prevPlotter = (Plotter)e.OldValue;
Plotter currPlotter = (Plotter)e.NewValue;
// raise Plotter[*] events, where * is Attached, Detaching, Changed.
if (element != null)
{
PlotterChangedEventArgs args = new PlotterChangedEventArgs(prevPlotter, currPlotter, PlotterDetachingEvent);
if (currPlotter == null && prevPlotter != null)
{
RaisePlotterEvent(element, args);
}
else if (currPlotter != null)
{
args.RoutedEvent = PlotterAttachedEvent;
RaisePlotterEvent(element, args);
}
args.RoutedEvent = PlotterChangedEvent;
RaisePlotterEvent(element, args);
}
}
private static void RaisePlotterEvent(FrameworkElement element, PlotterChangedEventArgs args)
{
element.RaiseEvent(args);
PlotterEvents.Notify(element, args);
}
#endregion
#region Plotter routed events
public static readonly RoutedEvent PlotterAttachedEvent = EventManager.RegisterRoutedEvent(
"PlotterAttached",
RoutingStrategy.Direct,
typeof(PlotterChangedEventHandler),
typeof(Plotter));
public static readonly RoutedEvent PlotterDetachingEvent = EventManager.RegisterRoutedEvent(
"PlotterDetaching",
RoutingStrategy.Direct,
typeof(PlotterChangedEventHandler),
typeof(Plotter));
public static readonly RoutedEvent PlotterChangedEvent = EventManager.RegisterRoutedEvent(
"PlotterChanged",
RoutingStrategy.Direct,
typeof(PlotterChangedEventHandler),
typeof(Plotter));
#endregion
}
public delegate void PlotterChangedEventHandler(object sender, PlotterChangedEventArgs e);
}

206
Common/Plotter.xaml.cs Normal file
View File

@@ -0,0 +1,206 @@
using System;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Microsoft.Research.DynamicDataDisplay.Common;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Windows.Markup;
using System.ComponentModel;
namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>Plotter is a base control for displaying various graphs. It provides
/// mainCanvas to draw graph itself and side space for axes, annotations, etc</summary>
[ContentProperty("Children")]
public partial class Plotter : ContentControl
{
static Plotter() {
//DefaultStyleKeyProperty.OverrideMetadata(typeof(Plotter), new FrameworkPropertyMetadata(typeof(Plotter)));
}
/// <summary>
/// Initializes a new instance of the <see cref="Plotter"/> class.
/// </summary>
public Plotter()
{
children.ChildAdded += OnChildAdded;
children.ChildRemoving += OnChildRemoving;
InitializeComponent();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
//headerPanel = GetTemplateChild("headerPanel") as StackPanel;
}
#region Children and add/removed events handling
private readonly ObservableChartsCollection children = new ObservableChartsCollection();
[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
public ObservableChartsCollection Children
{
get { return children; }
}
private void OnChildRemoving(object sender, PlotterCollectionChangedEventArgs e)
{
OnChildRemoving(e.Child);
}
protected virtual void OnChildRemoving(IPlotterElement uiElement)
{
IPlotterElement child = uiElement as IPlotterElement;
if (child != null)
{
child.OnPlotterDetaching(this);
}
}
private void OnChildAdded(object sender, PlotterCollectionChangedEventArgs e)
{
OnChildAdded(e.Child);
}
protected virtual void OnChildAdded(IPlotterElement uiElement)
{
IPlotterElement child = uiElement as IPlotterElement;
if (child != null)
{
child.OnPlotterAttached(this);
}
}
#endregion
#region Layout zones
public StackPanel HeaderPanel
{
get { return headerPanel; }
}
public StackPanel FooterPanel
{
get { return footerPanel; }
}
public StackPanel LeftPanel
{
get { return leftPanel; }
}
public StackPanel BottomPanel
{
get { return bottomPanel; }
}
public Canvas MainCanvas
{
get { return mainCanvas; }
}
public Grid CentralGrid
{
get { return centralGrid; }
}
#endregion
#region Screenshots & copy to clipboard
public BitmapSource CreateScreenshot()
{
Window window = Window.GetWindow(this);
Rect renderBounds = new Rect(RenderSize);
Point p1 = TranslatePoint(renderBounds.Location, window);
Point p2 = TranslatePoint(renderBounds.BottomRight, window);
Int32Rect rect = new Rect(p1, p2).ToInt32Rect();
rect.Y = 0;
return ScreenshotHelper.CreateScreenshot(this, rect);
}
/// <summary>Saves the screenshot.</summary>
/// <param name="filePath">The file path.</param>
public void SaveScreenshot(string filePath)
{
ScreenshotHelper.SaveBitmap(CreateScreenshot(), filePath);
}
/// <summary>Copies the screenshot to clipboard.</summary>
public void CopyScreenshotToClipboard()
{
Clipboard.Clear();
Clipboard.SetImage(CreateScreenshot());
}
#endregion
#region IsDefaultElement attached property
protected void SetAllChildrenAsDefault()
{
foreach (var child in Children.OfType<DependencyObject>())
{
child.SetValue(IsDefaultElementProperty, true);
}
}
/// <summary>Gets a value whether specified graphics object is default to this plotter or not</summary>
/// <param name="obj">Graphics object to check</param>
/// <returns>True if it is default or false otherwise</returns>
public static bool GetIsDefaultElement(DependencyObject obj)
{
return (bool)obj.GetValue(IsDefaultElementProperty);
}
public static void SetIsDefaultElement(DependencyObject obj, bool value)
{
obj.SetValue(IsDefaultElementProperty, value);
}
public static readonly DependencyProperty IsDefaultElementProperty = DependencyProperty.RegisterAttached(
"IsDefaultElement",
typeof(bool),
typeof(Plotter),
new UIPropertyMetadata(false));
/// <summary>Removes all user graphs from given UIElementCollection,
/// leaving only default graphs</summary>
protected static void RemoveUserElements(ObservableChartsCollection elements)
{
int index = 0;
while (index < elements.Count)
{
DependencyObject d = elements[index] as DependencyObject;
if (d != null && !GetIsDefaultElement(d))
{
elements.RemoveAt(index);
}
else
{
index++;
}
}
}
public void RemoveUserElements()
{
RemoveUserElements(Children);
}
#endregion
}
}

View File

@@ -0,0 +1,27 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Automation.Peers;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public class PlotterAutomationPeer : FrameworkElementAutomationPeer
{
public PlotterAutomationPeer(Plotter owner)
: base(owner)
{
}
protected override AutomationControlType GetAutomationControlTypeCore()
{
return AutomationControlType.Custom;
}
protected override string GetClassNameCore()
{
return "Plotter";
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Input;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public class PlotterChangedEventArgs : RoutedEventArgs
{
public PlotterChangedEventArgs(Plotter prevPlotter, Plotter currPlotter, RoutedEvent routedEvent) : base(routedEvent)
{
if (prevPlotter == null && currPlotter == null) {
throw new ArgumentException("Both Plotters cannot be null.");
}
this.prevPlotter = prevPlotter;
this.currPlotter = currPlotter;
}
private readonly Plotter prevPlotter;
public Plotter PreviousPlotter { get { return prevPlotter; } }
private readonly Plotter currPlotter;
public Plotter CurrentPlotter { get { return currPlotter; } }
}
}

View File

@@ -0,0 +1,209 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Windows.Markup;
using Microsoft.Research.DynamicDataDisplay.Charts;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Collections;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
/// <summary>
/// Contains all charts added to ChartPlotter.
/// </summary>
[ContentWrapper(typeof(ViewportUIContainer))]
public sealed class PlotterChildrenCollection : D3Collection<IPlotterElement>, IList
{
/// <summary>
/// Initializes a new instance of the <see cref="PlotterChildrenCollection"/> class.
/// </summary>
internal PlotterChildrenCollection(Plotter plotter)
{
if (plotter == null)
throw new ArgumentNullException("plotter");
this.plotter = plotter;
}
private readonly Plotter plotter;
public Plotter Plotter
{
get { return plotter; }
}
/// <summary>
/// Called before item added to collection. Enables to perform validation.
/// </summary>
/// <param name="item">The adding item.</param>
protected override void OnItemAdding(IPlotterElement item)
{
if (item == null)
throw new ArgumentNullException("item");
}
/// <summary>
/// This override enables notifying about removing each element, instead of
/// notifying about collection reset.
/// </summary>
protected override void ClearItems()
{
var items = new List<IPlotterElement>(base.Items);
foreach (var item in items)
{
Remove(item);
}
}
#region Foreign content
public void Add(FrameworkElement content)
{
if (content == null)
throw new ArgumentNullException("content");
IPlotterElement plotterElement = content as IPlotterElement;
if (plotterElement != null)
{
Add(plotterElement);
}
else
{
ViewportUIContainer container = new ViewportUIContainer(content);
Add(container);
}
}
#endregion // end of Foreign content
#region IList Members
int IList.Add(object value)
{
if (value == null)
throw new ArgumentNullException("value");
FrameworkElement content = value as FrameworkElement;
if (content != null)
{
Add(content);
return 0;
}
IPlotterElement element = value as IPlotterElement;
if (element != null)
{
Add(element);
return 0;
}
throw new ArgumentException(String.Format("Children of type '{0}' are not supported.", value.GetType()));
}
void IList.Clear()
{
Clear();
}
bool IList.Contains(object value)
{
IPlotterElement element = value as IPlotterElement;
return element != null && Contains(element);
}
int IList.IndexOf(object value)
{
IPlotterElement element = value as IPlotterElement;
if (element != null)
return IndexOf(element);
return -1;
}
void IList.Insert(int index, object value)
{
IPlotterElement element = value as IPlotterElement;
if (element != null)
{
Insert(index, element);
}
}
bool IList.IsFixedSize
{
get { return false; }
}
bool IList.IsReadOnly
{
get { return false; }
}
void IList.Remove(object value)
{
IPlotterElement element = value as IPlotterElement;
if (element != null)
Remove(element);
}
void IList.RemoveAt(int index)
{
RemoveAt(index);
}
object IList.this[int index]
{
get
{
return this[index];
}
set
{
IPlotterElement element = value as IPlotterElement;
if (element != null)
this[index] = element;
}
}
#endregion
#region ICollection Members
void ICollection.CopyTo(Array array, int index)
{
IPlotterElement[] elements = array as IPlotterElement[];
if (elements != null)
CopyTo(elements, index);
}
int ICollection.Count
{
get { return Count; }
}
bool ICollection.IsSynchronized
{
get { return false; }
}
object ICollection.SyncRoot
{
get { return null; }
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}

87
Common/PlotterElement.cs Normal file
View File

@@ -0,0 +1,87 @@
using System.Windows;
using System;
using System.ComponentModel;
namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>
/// Main interface of DynamicDataDisplay - each item that is going to be added to Plotter should implement it.
/// Contains methods that are called by parent plotter when item is added to it or removed from it.
/// </summary>
public interface IPlotterElement
{
/// <summary>
/// Called when parent plotter is attached.
/// Allows to, for example, add custom UI parts to ChartPlotter's visual tree or subscribe to ChartPlotter's events.
/// </summary>
/// <param name="plotter">The parent plotter.</param>
void OnPlotterAttached(Plotter plotter);
/// <summary>
/// Called when item is being detached from parent plotter.
/// Allows to remove added in OnPlotterAttached method UI parts or unsubscribe from events.
/// This should be done as each chart can be added only one Plotter at one moment of time.
/// </summary>
/// <param name="plotter">The plotter.</param>
void OnPlotterDetaching(Plotter plotter);
/// <summary>
/// Gets the parent plotter of chart.
/// Should be equal to null if item is not connected to any plotter.
/// </summary>
/// <value>The plotter.</value>
Plotter Plotter { get; }
}
/// <summary>
/// One of the simplest implementations of IPlotterElement interface.
/// Derives from FrameworkElement.
/// </summary>
public abstract class PlotterElement : FrameworkElement, IPlotterElement
{
/// <summary>
/// Initializes a new instance of the <see cref="PlotterElement"/> class.
/// </summary>
protected PlotterElement() { }
private Plotter plotter;
/// <summary>
/// Gets the parent plotter of chart.
/// Should be equal to null if item is not connected to any plotter.
/// </summary>
/// <value>The plotter.</value>
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public Plotter Plotter
{
get { return plotter; }
}
/// <summary>This method is invoked when element is attached to plotter. It is the place
/// to put additional controls to Plotter</summary>
/// <param name="plotter">Plotter for this element</param>
protected virtual void OnPlotterAttached(Plotter plotter)
{
this.plotter = plotter;
}
/// <summary>This method is invoked when element is being detached from plotter. If additional
/// controls were put on plotter in OnPlotterAttached method, they should be removed here</summary>
/// <remarks>This method is always called in pair with OnPlotterAttached</remarks>
protected virtual void OnPlotterDetaching(Plotter plotter)
{
this.plotter = null;
}
#region IPlotterElement Members
void IPlotterElement.OnPlotterAttached(Plotter plotter)
{
OnPlotterAttached(plotter);
}
void IPlotterElement.OnPlotterDetaching(Plotter plotter)
{
OnPlotterDetaching(plotter);
}
#endregion
}
}

View File

@@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public sealed class PlotterEventHelper
{
private RoutedEvent @event;
internal PlotterEventHelper(RoutedEvent @event)
{
this.@event = @event;
}
// todo use a weakReference here
private readonly Dictionary<DependencyObject, EventHandler<PlotterChangedEventArgs>> handlers = new Dictionary<DependencyObject, EventHandler<PlotterChangedEventArgs>>();
public void Subscribe(DependencyObject target, EventHandler<PlotterChangedEventArgs> handler)
{
if (target == null)
throw new ArgumentNullException("target");
if (handler == null)
throw new ArgumentNullException("handler");
handlers.Add(target, handler);
}
internal void Notify(FrameworkElement target, PlotterChangedEventArgs args)
{
if (args.RoutedEvent == @event && handlers.ContainsKey(target))
{
var handler = handlers[target];
handler(target, args);
}
}
}
}

36
Common/PlotterEvents.cs Normal file
View File

@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public static class PlotterEvents
{
internal static void Notify(FrameworkElement target, PlotterChangedEventArgs args)
{
plotterAttachedEvent.Notify(target, args);
plotterChangedEvent.Notify(target, args);
plotterDetachingEvent.Notify(target, args);
}
private static readonly PlotterEventHelper plotterAttachedEvent = new PlotterEventHelper(Plotter.PlotterAttachedEvent);
public static PlotterEventHelper PlotterAttachedEvent
{
get { return plotterAttachedEvent; }
}
private static readonly PlotterEventHelper plotterDetachingEvent = new PlotterEventHelper(Plotter.PlotterDetachingEvent);
public static PlotterEventHelper PlotterDetachingEvent
{
get { return plotterDetachingEvent; }
}
private static readonly PlotterEventHelper plotterChangedEvent = new PlotterEventHelper(Plotter.PlotterChangedEvent);
public static PlotterEventHelper PlotterChangedEvent
{
get { return plotterChangedEvent; }
}
}
}

12
Common/PlotterPanel.cs Normal file
View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public enum PlotterPanel
{
MainCanvas = 0
}
}

84
Common/PlotterStyle.xaml Normal file
View File

@@ -0,0 +1,84 @@
<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"
xmlns:common="clr-namespace:Microsoft.Research.DynamicDataDisplay.Common">
<Style TargetType="{x:Type local:Plotter}" x:Key="defaultPlotterStyle">
<Setter Property="Background" Value="White"/>
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="ClipToBounds" Value="True"/>
</Style>
<!--Send feedback icon-->
<Image x:Key="SendFeedbackIcon" Width="16" Height="16">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#FFFFD94D" Geometry="F1 M 5.125,10.0208L 26.7083,10.0208L 26.7083,21.6875L 5.125,21.6875L 5.125,10.0208 Z">
<GeometryDrawing.Pen>
<Pen LineJoin="Round" Brush="#FF000000"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFE278" Geometry="F1 M 5.52083,21.2708L 15.9167,14.2292L 26.2708,21.2708">
<GeometryDrawing.Pen>
<Pen LineJoin="Round" Brush="#FF000000"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFC800" Geometry="F1 M 5.45833,10.4583L 16,16.4375L 26.3958,10.4583">
<GeometryDrawing.Pen>
<Pen LineJoin="Round" Brush="#FF000000"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<!--Plotter template-->
<ControlTemplate TargetType="{x:Type local:Plotter}" x:Key="defaultPlotterTemplate">
<common:NotifyingGrid Name="PART_ContentsGrid" Background="{TemplateBinding Background}" DataContext="{TemplateBinding DataContext}">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<common:NotifyingStackPanel Name="PART_HeaderPanel" Orientation="Vertical" Grid.Row="0"/>
<common:NotifyingGrid Name="PART_MainGrid" Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<common:NotifyingGrid Name="PART_CentralGrid" Grid.Column="1" Grid.Row="1" ClipToBounds="True" Background="Transparent">
<common:NotifyingCanvas Name="PART_MainCanvas" Grid.Column="1" Grid.Row="1" ClipToBounds="True" Panel.ZIndex="1"/>
</common:NotifyingGrid>
<!-- Border of viewport -->
<Rectangle Name="Border" Grid.Column="1" Grid.Row="1" Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="{TemplateBinding BorderThickness}"/>
<common:NotifyingStackPanel Name="PART_LeftPanel" Grid.Column="0" Grid.Row="1" Orientation="Horizontal" Background="Transparent"/>
<common:NotifyingStackPanel Name="PART_RightPanel" Grid.Column="2" Grid.Row="1" Orientation="Horizontal" Background="Transparent"/>
<common:NotifyingStackPanel Name="PART_BottomPanel" Grid.Column="1" Grid.Row="2" Orientation="Vertical" Background="Transparent"/>
<common:NotifyingStackPanel Name="PART_TopPanel" Grid.Column="1" Grid.Row="0" Orientation="Vertical" Background="Transparent"/>
</common:NotifyingGrid>
<common:NotifyingCanvas Name="PART_ParallelCanvas" Grid.Column="1" Grid.Row="1"/>
<common:NotifyingStackPanel Name="PART_FooterPanel" Orientation="Vertical" Grid.Row="2"/>
</common:NotifyingGrid>
</ControlTemplate>
</ResourceDictionary>

150
Common/Range.cs Normal file
View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Diagnostics;
using System.ComponentModel;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay.Charts
{
/// <summary>
/// An ordered pair of values, representing a segment.
/// </summary>
/// <typeparam name="T">Type of each of two values of range.</typeparam>
[Serializable]
[DebuggerDisplay(@"{Min} — {Max}")]
[TypeConverter(typeof(RangeConverter))]
public struct Range<T> : IEquatable<Range<T>>
{
/// <summary>
/// Initializes a new instance of the <see cref="Range&lt;T&gt;"/> struct.
/// </summary>
/// <param name="min">The minimal value of segment.</param>
/// <param name="max">The maximal value of segment.</param>
public Range(T min, T max)
{
this.min = min;
this.max = max;
#if DEBUG
if (min is IComparable)
{
IComparable c1 = (IComparable)min;
IComparable c2 = (IComparable)max;
DebugVerify.Is(c1.CompareTo(c2) <= 0);
}
#endif
}
private readonly T min;
/// <summary>
/// Gets the minimal value of segment.
/// </summary>
/// <value>The min.</value>
public T Min
{
get { return min; }
}
private readonly T max;
/// <summary>
/// Gets the maximal value of segment.
/// </summary>
/// <value>The max.</value>
public T Max
{
get { return max; }
}
// static float floatEps = (float)1e-10;
// static double doubleEps = 1e-10;
public static bool operator ==(Range<T> first, Range<T> second)
{
return (first.min.Equals(second.min) && first.max.Equals(second.max) ||
first.IsEmpty && second.IsEmpty);
}
public static bool operator !=(Range<T> first, Range<T> second)
{
return !(first == second);
}
public static bool EqualEps(Range<double> first, Range<double> second,double eps)
{
double delta = Math.Min(first.GetLength(), second.GetLength());
return Math.Abs(first.Min - second.Min) < eps * delta &&
Math.Abs(first.Max - second.Max) < eps * delta;
}
/// <summary>
/// Indicates whether this instance and a specified object are equal.
/// </summary>
/// <param name="obj">Another object to compare to.</param>
/// <returns>
/// true if <paramref name="obj"/> and this instance are the same type and represent the same value; otherwise, false.
/// </returns>
public override bool Equals(object obj)
{
if (obj is Range<T>)
{
Range<T> other = (Range<T>)obj;
return (min.Equals(other.min) && max.Equals(other.max) || IsEmpty && other.IsEmpty);
}
else
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>
/// A 32-bit signed integer that is the hash code for this instance.
/// </returns>
public override int GetHashCode()
{
return min.GetHashCode() ^ max.GetHashCode();
}
/// <summary>
/// Returns the fully qualified type name of this instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.String"/> containing a fully qualified type name.
/// </returns>
public override string ToString()
{
return String.Format("{0} — {1}", min, max);
}
/// <summary>
/// Gets a value indicating whether this range is empty.
/// </summary>
/// <value><c>true</c> if this instance is empty; otherwise, <c>false</c>.</value>
public bool IsEmpty
{
get {
if (typeof(T) is IComparable)
return ((IComparable)min).CompareTo(max) >= 0;
else
return min.Equals(max);
}
}
/// <summary>
/// Indicates whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
/// </returns>
public bool Equals(Range<T> other)
{
return this == other;
}
}
}

74
Common/RangeConverter.cs Normal file
View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Globalization;
using Microsoft.Research.DynamicDataDisplay.Charts;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public sealed class RangeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return (sourceType == typeof(string)) || base.CanConvertFrom(context, sourceType);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
{
return (destinationType == typeof(string)) || base.CanConvertTo(context, destinationType);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
if (value == null)
{
throw base.GetConvertFromException(value);
}
string source = value as string;
if (source != null)
{
var parts = source.Split('-');
var minStr = parts[0];
var maxStr = parts[1];
int minInt32 = 0;
double minDouble = 0;
DateTime minDateTime = DateTime.Now;
if (Int32.TryParse(minStr, NumberStyles.Integer, culture, out minInt32))
{
int maxInt32 = Int32.Parse(maxStr, NumberStyles.Integer, culture);
return new Range<int>(minInt32, maxInt32);
}
else if (Double.TryParse(minStr, NumberStyles.Float, culture, out minDouble))
{
double maxDouble = Double.Parse(maxStr, NumberStyles.Float, culture);
return new Range<double>(minDouble, maxDouble);
}
else if (DateTime.TryParse(minStr, culture, DateTimeStyles.None, out minDateTime))
{
DateTime maxDateTime = DateTime.Parse(maxStr, culture);
return new Range<DateTime>(minDateTime, maxDateTime);
}
}
return base.ConvertFrom(context, culture, value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
{
if (destinationType != null && value is DataRect)
{
DataRect rect = (DataRect)value;
if (destinationType == typeof(string))
{
return rect.ConvertToString(null, culture);
}
}
return base.ConvertTo(context, culture, value, destinationType);
}
}
}

56
Common/RenderState.cs Normal file
View File

@@ -0,0 +1,56 @@
using System.Windows;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay
{
/// <summary>
/// Target of rendering
/// </summary>
public enum RenderTo
{
/// <summary>
/// Rendering directly to screen
/// </summary>
Screen,
/// <summary>
/// Rendering to bitmap, which will be drawn to screen later.
/// </summary>
Image
}
public sealed class RenderState
{
private readonly DataRect visible;
private readonly Rect output;
private readonly DataRect renderVisible;
private readonly RenderTo renderingType;
public DataRect RenderVisible
{
get { return renderVisible; }
}
public RenderTo RenderingType
{
get { return renderingType; }
}
public Rect Output
{
get { return output; }
}
public DataRect Visible
{
get { return visible; }
}
internal RenderState(DataRect renderVisible, DataRect visible, Rect output, RenderTo renderingType)
{
this.renderVisible = renderVisible;
this.visible = visible;
this.output = output;
this.renderingType = renderingType;
}
}
}

56
Common/ResourcePool.cs Normal file
View File

@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
[DebuggerDisplay("Count = {Count}")]
internal sealed class ResourcePool<T>
{
private readonly List<T> pool = new List<T>();
public T Get()
{
T item;
if (pool.Count < 1)
{
item = default(T);
}
else
{
int index = pool.Count - 1;
item = pool[index];
pool.RemoveAt(index);
}
return item;
}
public void Put(T item)
{
if (item == null)
throw new ArgumentNullException("item");
#if DEBUG
if (pool.IndexOf(item) != -1)
Debugger.Break();
#endif
pool.Add(item);
}
public int Count
{
get { return pool.Count; }
}
public void Clear()
{
pool.Clear();
}
}
}

140
Common/RingArray.cs Normal file
View File

@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Collections;
using System.Collections.Specialized;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
public class RingArray<T> : INotifyCollectionChanged, IList<T>
{
public RingArray(int capacity)
{
this.capacity = capacity;
array = new T[capacity];
}
public void Add(T item)
{
int index = (startIndex + count) % capacity;
if (startIndex + count >= capacity)
{
startIndex++;
}
else
{
count++;
}
array[index] = item;
CollectionChanged.Raise(this);
}
public T this[int index]
{
get { return array[(startIndex + index) % capacity]; }
set
{
array[(startIndex + index) % capacity] = value;
CollectionChanged.Raise(this);
}
}
public void Clear()
{
count = 0;
startIndex = 0;
array = new T[capacity];
}
public IEnumerator<T> GetEnumerator()
{
for (int i = 0; i < count; i++)
{
yield return this[i];
}
}
private int count;
public int Count
{
get { return count; }
}
private T[] array;
private int capacity;
public int Capacity
{
get { return capacity; }
}
private int startIndex = 0;
#region INotifyCollectionChanged Members
public event NotifyCollectionChangedEventHandler CollectionChanged;
#endregion
#region IList<T> Members
public int IndexOf(T item)
{
int index = Array.IndexOf(array, item);
if (index == -1)
return -1;
return (index - startIndex + count) % capacity;
}
public void Insert(int index, T item)
{
throw new NotImplementedException();
}
public void RemoveAt(int index)
{
throw new NotImplementedException();
}
#endregion
#region ICollection<T> Members
public bool Contains(T item)
{
return Array.IndexOf(array, item) > -1;
}
public void CopyTo(T[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public bool IsReadOnly
{
get { throw new NotImplementedException(); }
}
public bool Remove(T item)
{
throw new NotImplementedException();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
[Conditional("DEBUG")]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal sealed class SkipPropertyCheckAttribute : Attribute
{
}
}

24
Common/TokenizerHelper.cs Normal file
View File

@@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
internal static class TokenizerHelper
{
public static char GetNumericListSeparator(IFormatProvider provider)
{
char separator = ',';
NumberFormatInfo numberInfo = NumberFormatInfo.GetInstance(provider);
if (numberInfo.NumberDecimalSeparator.Length > 0 && separator == numberInfo.NumberDecimalSeparator[0])
{
separator = ';';
}
return separator;
}
}
}

View File

@@ -0,0 +1,211 @@
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.Collections;
using Microsoft.Research.DynamicDataDisplay.Charts;
namespace Microsoft.Research.DynamicDataDisplay.Common
{
/// <summary>
/// Collection of UI children of <see cref="IndependentArrangePanel"/>.
/// </summary>
internal sealed class UIChildrenCollection : UIElementCollection
{
IndividualArrangePanel hostPanel;
/// <summary>
/// Initializes a new instance of the <see cref="UIChildrenCollection"/> class.
/// </summary>
internal UIChildrenCollection(UIElement visualParent, FrameworkElement logicalParent)
: base(visualParent, logicalParent)
{
hostPanel = (IndividualArrangePanel)visualParent;
visualChildren = new VisualCollection(visualParent);
}
private readonly VisualCollection visualChildren;
private bool isAddingMany = false;
public bool IsAddingMany
{
get { return isAddingMany; }
set { isAddingMany = value; }
}
public override int Add(UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
SetLogicalParent(element);
var index = visualChildren.Add(element);
if (isAddingMany)
{
hostPanel.InvalidateMeasure();
}
else
{
hostPanel.OnChildAdded((FrameworkElement)element);
}
return index;
}
public override void Clear()
{
if (visualChildren.Count > 0)
{
Visual[] visualArray = new Visual[visualChildren.Count];
for (int i = 0; i < visualChildren.Count; i++)
{
visualArray[i] = visualChildren[i];
}
visualChildren.Clear();
for (int i = 0; i < visualArray.Length; i++)
{
UIElement element = visualArray[i] as UIElement;
if (element != null)
{
ClearLogicalParent(element);
}
}
}
}
public override bool Contains(UIElement element)
{
return visualChildren.Contains(element);
}
public override void CopyTo(Array array, int index)
{
visualChildren.CopyTo(array, index);
}
public override void CopyTo(UIElement[] array, int index)
{
visualChildren.CopyTo(array, index);
}
public override IEnumerator GetEnumerator()
{
return visualChildren.GetEnumerator();
}
public override int IndexOf(UIElement element)
{
return visualChildren.IndexOf(element);
}
public override void Insert(int index, UIElement element)
{
if (element == null)
throw new ArgumentNullException("element");
hostPanel.OnChildAdded((FrameworkElement)element);
SetLogicalParent(element);
visualChildren.Insert(index, element);
}
public override void Remove(UIElement element)
{
visualChildren.Remove(element);
ClearLogicalParent(element);
}
public override void RemoveAt(int index)
{
UIElement element = visualChildren[index] as UIElement;
visualChildren.RemoveAt(index);
if (element != null)
{
ClearLogicalParent(element);
}
}
public override void RemoveRange(int index, int count)
{
int actualCount = visualChildren.Count;
if (count > (actualCount - index))
{
count = actualCount - index;
}
if (count > 0)
{
Visual[] visualArray = new Visual[count];
int copyIndex = index;
for (int i = 0; i < count; i++)
{
visualArray[i] = visualChildren[copyIndex];
copyIndex++;
}
visualChildren.RemoveRange(index, count);
for (int i = 0; i < count; i++)
{
UIElement element = visualArray[i] as UIElement;
if (element != null)
{
ClearLogicalParent(element);
}
}
}
}
public override UIElement this[int index]
{
get
{
return visualChildren[index] as UIElement;
}
set
{
if (value == null)
throw new ArgumentNullException("value");
if (visualChildren[index] != value)
{
UIElement element = visualChildren[index] as UIElement;
if (element != null)
{
ClearLogicalParent(element);
}
hostPanel.OnChildAdded((FrameworkElement)value);
visualChildren[index] = value;
SetLogicalParent(value);
}
}
}
public override int Capacity
{
get { return visualChildren.Capacity; }
set { visualChildren.Capacity = value; }
}
public override int Count
{
get { return visualChildren.Count; }
}
public override bool IsSynchronized
{
get { return visualChildren.IsSynchronized; }
}
public override object SyncRoot
{
get { return visualChildren.SyncRoot; }
}
}
}

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Common.UndoSystem
{
[DebuggerDisplay("Count = {Count}")]
public sealed class ActionStack
{
private readonly Stack<UndoAction> stack = new Stack<UndoAction>();
public void Push(UndoAction action)
{
stack.Push(action);
if (stack.Count == 1)
RaiseIsEmptyChanged();
}
public UndoAction Pop()
{
var action = stack.Pop();
if (stack.Count == 0)
RaiseIsEmptyChanged();
return action;
}
public int Count { get { return stack.Count; } }
public void Clear()
{
stack.Clear();
RaiseIsEmptyChanged();
}
public bool IsEmpty
{
get { return stack.Count == 0; }
}
private void RaiseIsEmptyChanged()
{
IsEmptyChanged.Raise(this);
}
public event EventHandler IsEmptyChanged;
}
}

Some files were not shown because too many files have changed in this diff Show More