Initial Commit
This commit is contained in:
36
Common/Auxiliary/ArrayExtensions.cs
Normal file
36
Common/Auxiliary/ArrayExtensions.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
17
Common/Auxiliary/BindingHelper.cs
Normal file
17
Common/Auxiliary/BindingHelper.cs
Normal 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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
46
Common/Auxiliary/BoundsHelper.cs
Normal file
46
Common/Auxiliary/BoundsHelper.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
69
Common/Auxiliary/BrushHelper.cs
Normal file
69
Common/Auxiliary/BrushHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Common/Auxiliary/ColorExtensions.cs
Normal file
22
Common/Auxiliary/ColorExtensions.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Common/Auxiliary/ColorHelper.cs
Normal file
104
Common/Auxiliary/ColorHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
76
Common/Auxiliary/CoordinateUtils.cs
Normal file
76
Common/Auxiliary/CoordinateUtils.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
82
Common/Auxiliary/DataRectExtensions.cs
Normal file
82
Common/Auxiliary/DataRectExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Common/Auxiliary/DataSearch/GenericSearcher1d.cs
Normal file
95
Common/Auxiliary/DataSearch/GenericSearcher1d.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Common/Auxiliary/DataSearch/SearchResult1d.cs
Normal file
30
Common/Auxiliary/DataSearch/SearchResult1d.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Common/Auxiliary/DataSearch/SortedXSearcher1d.cs
Normal file
56
Common/Auxiliary/DataSearch/SortedXSearcher1d.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
59
Common/Auxiliary/DebugVerify.cs
Normal file
59
Common/Auxiliary/DebugVerify.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Common/Auxiliary/DependencyObjectExtensions.cs
Normal file
20
Common/Auxiliary/DependencyObjectExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Common/Auxiliary/DictionaryExtensions.cs
Normal file
27
Common/Auxiliary/DictionaryExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Common/Auxiliary/DispatcherExtensions.cs
Normal file
26
Common/Auxiliary/DispatcherExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Common/Auxiliary/DisposableTimer.cs
Normal file
43
Common/Auxiliary/DisposableTimer.cs
Normal 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
|
||||
}
|
||||
}
|
||||
16
Common/Auxiliary/DoubleCollectionHelper.cs
Normal file
16
Common/Auxiliary/DoubleCollectionHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Common/Auxiliary/EventExtensions.cs
Normal file
140
Common/Auxiliary/EventExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
301
Common/Auxiliary/HsbColor.cs
Normal file
301
Common/Auxiliary/HsbColor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
146
Common/Auxiliary/IDataSource2DExtensions.cs
Normal file
146
Common/Auxiliary/IDataSource2DExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Common/Auxiliary/IEnumerableExtensions.cs
Normal file
119
Common/Auxiliary/IEnumerableExtensions.cs
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
44
Common/Auxiliary/IListExtensions.cs
Normal file
44
Common/Auxiliary/IListExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Common/Auxiliary/IPlotterElementExtensions.cs
Normal file
32
Common/Auxiliary/IPlotterElementExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
21
Common/Auxiliary/IPointCollectionExtensions.cs
Normal file
21
Common/Auxiliary/IPointCollectionExtensions.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
34
Common/Auxiliary/ListExtensions.cs
Normal file
34
Common/Auxiliary/ListExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Common/Auxiliary/ListGenerator.cs
Normal file
26
Common/Auxiliary/ListGenerator.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
Common/Auxiliary/MarkupExtensions/ResourceExtension.cs
Normal file
53
Common/Auxiliary/MarkupExtensions/ResourceExtension.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Common/Auxiliary/MarkupExtensions/SelfBinding.cs
Normal file
23
Common/Auxiliary/MarkupExtensions/SelfBinding.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Common/Auxiliary/MarkupExtensions/TemplateBinding.cs
Normal file
22
Common/Auxiliary/MarkupExtensions/TemplateBinding.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Common/Auxiliary/MathHelper.cs
Normal file
91
Common/Auxiliary/MathHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Common/Auxiliary/MenuItemExtensions.cs
Normal file
16
Common/Auxiliary/MenuItemExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Common/Auxiliary/ObservableCollectionHelper.cs
Normal file
35
Common/Auxiliary/ObservableCollectionHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Common/Auxiliary/PlacementExtensions.cs
Normal file
16
Common/Auxiliary/PlacementExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Common/Auxiliary/PlotterChildrenCollectionExtensions.cs
Normal file
32
Common/Auxiliary/PlotterChildrenCollectionExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Common/Auxiliary/PlotterExtensions.cs
Normal file
15
Common/Auxiliary/PlotterExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
19
Common/Auxiliary/PointExtensions.cs
Normal file
19
Common/Auxiliary/PointExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
15
Common/Auxiliary/RandomExtensions.cs
Normal file
15
Common/Auxiliary/RandomExtensions.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
Common/Auxiliary/RangeExtensions.cs
Normal file
25
Common/Auxiliary/RangeExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
67
Common/Auxiliary/RectExtensions.cs
Normal file
67
Common/Auxiliary/RectExtensions.cs
Normal 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()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
22
Common/Auxiliary/ResourcePoolExtensions.cs
Normal file
22
Common/Auxiliary/ResourcePoolExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
205
Common/Auxiliary/ScreenshotHelper.cs
Normal file
205
Common/Auxiliary/ScreenshotHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Common/Auxiliary/SizeExtensions.cs
Normal file
20
Common/Auxiliary/SizeExtensions.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Common/Auxiliary/SizeHelper.cs
Normal file
16
Common/Auxiliary/SizeHelper.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Common/Auxiliary/StreamExtensions.cs
Normal file
23
Common/Auxiliary/StreamExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
Common/Auxiliary/StringExtensions.cs
Normal file
20
Common/Auxiliary/StringExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Common/Auxiliary/TaskExtensions.cs
Normal file
50
Common/Auxiliary/TaskExtensions.cs
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Common/Auxiliary/TriangleMath.cs
Normal file
73
Common/Auxiliary/TriangleMath.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Common/Auxiliary/ValueStore.cs
Normal file
106
Common/Auxiliary/ValueStore.cs
Normal 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
|
||||
}
|
||||
}
|
||||
30
Common/Auxiliary/ValueStoreConverter.cs
Normal file
30
Common/Auxiliary/ValueStoreConverter.cs
Normal 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
|
||||
}
|
||||
}
|
||||
16
Common/Auxiliary/VectorExtensions.cs
Normal file
16
Common/Auxiliary/VectorExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
64
Common/Auxiliary/Verify.cs
Normal file
64
Common/Auxiliary/Verify.cs
Normal 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)));
|
||||
}
|
||||
}
|
||||
}
|
||||
27
Common/Auxiliary/VisualTreeHelperHelper.cs
Normal file
27
Common/Auxiliary/VisualTreeHelperHelper.cs
Normal 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
120
Common/BezierBuilder.cs
Normal 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
132
Common/D3Collection.cs
Normal 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
687
Common/DataRect.cs
Normal 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
|
||||
}
|
||||
}
|
||||
51
Common/DataRectConverter.cs
Normal file
51
Common/DataRectConverter.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Common/DataRectSerializer.cs
Normal file
41
Common/DataRectSerializer.cs
Normal 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
42
Common/Footer.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay
|
||||
{
|
||||
/// <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
42
Common/Header.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
101
Common/HorizontalAxisTitle.cs
Normal file
101
Common/HorizontalAxisTitle.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
111
Common/IndividualArrangePanel.cs
Normal file
111
Common/IndividualArrangePanel.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
14
Common/NotNullAttribute.cs
Normal file
14
Common/NotNullAttribute.cs
Normal 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
|
||||
{
|
||||
}
|
||||
}
|
||||
13
Common/NotifyingPanels/INotifyingPanel.cs
Normal file
13
Common/NotifyingPanels/INotifyingPanel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
32
Common/NotifyingPanels/NotifyingCanvas.cs
Normal file
32
Common/NotifyingPanels/NotifyingCanvas.cs
Normal 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
|
||||
}
|
||||
}
|
||||
32
Common/NotifyingPanels/NotifyingGrid.cs
Normal file
32
Common/NotifyingPanels/NotifyingGrid.cs
Normal 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
|
||||
}
|
||||
}
|
||||
37
Common/NotifyingPanels/NotifyingStackPanel.cs
Normal file
37
Common/NotifyingPanels/NotifyingStackPanel.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
106
Common/NotifyingPanels/NotifyingUIElementCollection.cs
Normal file
106
Common/NotifyingPanels/NotifyingUIElementCollection.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
||||
175
Common/ObservableCollectionWrapper.cs
Normal file
175
Common/ObservableCollectionWrapper.cs
Normal 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
|
||||
}
|
||||
}
|
||||
24
Common/ObservableUIElementCollection.cs
Normal file
24
Common/ObservableUIElementCollection.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
Common/Palettes/DecoratorPaletteBase.cs
Normal file
66
Common/Palettes/DecoratorPaletteBase.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
Common/Palettes/DelegatePalette.cs
Normal file
26
Common/Palettes/DelegatePalette.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
61
Common/Palettes/HsbPalette.cs
Normal file
61
Common/Palettes/HsbPalette.cs
Normal 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
|
||||
}
|
||||
}
|
||||
26
Common/Palettes/IPalette.cs
Normal file
26
Common/Palettes/IPalette.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
99
Common/Palettes/LinearPalette.cs
Normal file
99
Common/Palettes/LinearPalette.cs
Normal 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
|
||||
}
|
||||
}
|
||||
30
Common/Palettes/LinearPalettes.cs
Normal file
30
Common/Palettes/LinearPalettes.cs
Normal 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; } }
|
||||
}
|
||||
}
|
||||
33
Common/Palettes/MinMaxLoggingPalette.cs
Normal file
33
Common/Palettes/MinMaxLoggingPalette.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
35
Common/Palettes/PaletteBase.cs
Normal file
35
Common/Palettes/PaletteBase.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Windows.Media;
|
||||
|
||||
namespace Microsoft.Research.DynamicDataDisplay.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
|
||||
}
|
||||
}
|
||||
24
Common/Palettes/TransparentLimitsPalette.cs
Normal file
24
Common/Palettes/TransparentLimitsPalette.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
133
Common/Palettes/UniformLinearPalette.cs
Normal file
133
Common/Palettes/UniformLinearPalette.cs
Normal 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
|
||||
}
|
||||
}
|
||||
45
Common/Palettes/UniformLinearPalettes.cs
Normal file
45
Common/Palettes/UniformLinearPalettes.cs
Normal 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
952
Common/Plotter.cs
Normal 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
206
Common/Plotter.xaml.cs
Normal 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
|
||||
}
|
||||
}
|
||||
27
Common/PlotterAutomationPeer.cs
Normal file
27
Common/PlotterAutomationPeer.cs
Normal 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
28
Common/PlotterChangedEventHandler.cs
Normal file
28
Common/PlotterChangedEventHandler.cs
Normal 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; } }
|
||||
}
|
||||
}
|
||||
209
Common/PlotterChildrenCollection.cs
Normal file
209
Common/PlotterChildrenCollection.cs
Normal 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
87
Common/PlotterElement.cs
Normal 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
|
||||
}
|
||||
}
|
||||
39
Common/PlotterEventHelper.cs
Normal file
39
Common/PlotterEventHelper.cs
Normal 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
36
Common/PlotterEvents.cs
Normal 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
12
Common/PlotterPanel.cs
Normal 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
84
Common/PlotterStyle.xaml
Normal 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
150
Common/Range.cs
Normal 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<T>"/> 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
74
Common/RangeConverter.cs
Normal 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
56
Common/RenderState.cs
Normal 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
56
Common/ResourcePool.cs
Normal 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
140
Common/RingArray.cs
Normal 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
|
||||
}
|
||||
}
|
||||
14
Common/SkipPropertyCheckAttribute.cs
Normal file
14
Common/SkipPropertyCheckAttribute.cs
Normal 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
24
Common/TokenizerHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
211
Common/UIChildrenCollection.cs
Normal file
211
Common/UIChildrenCollection.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
51
Common/UndoSystem/ActionStack.cs
Normal file
51
Common/UndoSystem/ActionStack.cs
Normal 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
Reference in New Issue
Block a user