Initial Commit

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

View File

@@ -0,0 +1,87 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows;
using System.Windows.Data;
using System.Diagnostics;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
public class AdditionalLinesRenderer : IsolineRenderer
{
protected override void CreateUIRepresentation()
{
InvalidateVisual();
}
protected override void OnPlotterAttached()
{
base.OnPlotterAttached();
FrameworkElement parent = (FrameworkElement)Parent;
var renderer = (FrameworkElement)parent.FindName("PART_IsolineRenderer");
Binding contentBoundsBinding = new Binding { Path = new PropertyPath("(0)", Viewport2D.ContentBoundsProperty), Source = renderer };
SetBinding(Viewport2D.ContentBoundsProperty, contentBoundsBinding);
SetBinding(ViewportPanel.ViewportBoundsProperty, contentBoundsBinding);
Plotter2D.Viewport.EndPanning += Viewport_EndPanning;
Plotter2D.Viewport.PropertyChanged += Viewport_PropertyChanged;
}
void Viewport_PropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
{
if (e.PropertyName == "Visible")
{
if (Plotter2D.Viewport.PanningState == Viewport2DPanningState.NotPanning)
InvalidateVisual();
}
}
protected override void OnPlotterDetaching()
{
Plotter2D.Viewport.EndPanning -= Viewport_EndPanning;
Plotter2D.Viewport.PropertyChanged -= Viewport_PropertyChanged;
base.OnPlotterDetaching();
}
private void Viewport_EndPanning(object sender, EventArgs e)
{
InvalidateVisual();
}
protected override void OnRender(DrawingContext drawingContext)
{
if (Plotter2D == null) return;
if (DataSource == null) return;
var collection = (IsolineCollection)Parent.GetValue(IsolineCollectionProperty);
if (collection == null) return;
var bounds = ViewportPanel.GetViewportBounds(this);
if (bounds.IsEmpty) return;
var dc = drawingContext;
var strokeThickness = StrokeThickness;
var transform = Plotter2D.Transform.WithRects(bounds, new Rect(RenderSize));
//dc.DrawRectangle(null, new Pen(Brushes.Green, 2), new Rect(RenderSize));
var additionalLevels = GetAdditionalLevels(collection);
IsolineBuilder.DataSource = DataSource;
var additionalIsolineCollections = additionalLevels.Select(level =>
{
return IsolineBuilder.BuildIsoline(level);
});
foreach (var additionalCollection in additionalIsolineCollections)
{
RenderIsolineCollection(dc, strokeThickness, additionalCollection, transform);
}
}
}
}

399
Charts/Isolines/CellInfo.cs Normal file
View File

@@ -0,0 +1,399 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
/// <summary>
/// Isoline's grid cell
/// </summary>
internal interface ICell
{
Vector LeftTop { get; }
Vector LeftBottom { get; }
Vector RightTop { get; }
Vector RightBottom { get; }
}
internal sealed class IrregularCell : ICell
{
public IrregularCell(Vector leftBottom, Vector rightBottom, Vector rightTop, Vector leftTop)
{
this.leftBottom = leftBottom;
this.rightBottom = rightBottom;
this.rightTop = rightTop;
this.leftTop = leftTop;
}
public IrregularCell(Point lb, Point rb, Point rt, Point lt)
{
leftTop = lt.ToVector();
leftBottom = lb.ToVector();
rightTop = rt.ToVector();
rightBottom = rb.ToVector();
}
#region ICell Members
private readonly Vector leftTop;
public Vector LeftTop
{
get { return leftTop; }
}
private readonly Vector leftBottom;
public Vector LeftBottom
{
get { return leftBottom; }
}
private readonly Vector rightTop;
public Vector RightTop
{
get { return rightTop; }
}
private readonly Vector rightBottom;
public Vector RightBottom
{
get { return rightBottom; }
}
#endregion
#region Sides
public Vector LeftSide
{
get { return (leftBottom + leftTop) / 2; }
}
public Vector RightSide
{
get { return (rightBottom + rightTop) / 2; }
}
public Vector TopSide
{
get { return (leftTop + rightTop) / 2; }
}
public Vector BottomSide
{
get { return (leftBottom + rightBottom) / 2; }
}
#endregion
public Point Center
{
get { return ((LeftSide + RightSide) / 2).ToPoint(); }
}
public IrregularCell GetSubRect(SubCell sub)
{
switch (sub)
{
case SubCell.LeftBottom:
return new IrregularCell(LeftBottom, BottomSide, Center.ToVector(), LeftSide);
case SubCell.LeftTop:
return new IrregularCell(LeftSide, Center.ToVector(), TopSide, LeftTop);
case SubCell.RightBottom:
return new IrregularCell(BottomSide, RightBottom, RightSide, Center.ToVector());
case SubCell.RightTop:
default:
return new IrregularCell(Center.ToVector(), RightSide, RightTop, TopSide);
}
}
}
internal enum SubCell
{
LeftBottom = 0,
LeftTop = 1,
RightBottom = 2,
RightTop = 3
}
internal class ValuesInCell
{
double min = Double.MaxValue, max = Double.MinValue;
/// <summary>Initializes values in four corners of cell</summary>
/// <param name="leftBottom"></param>
/// <param name="rightBottom"></param>
/// <param name="rightTop"></param>
/// <param name="leftTop"></param>
/// <remarks>Some or all values can be NaN. That means that value is not specified (misssing)</remarks>
public ValuesInCell(double leftBottom, double rightBottom, double rightTop, double leftTop)
{
this.leftTop = leftTop;
this.leftBottom = leftBottom;
this.rightTop = rightTop;
this.rightBottom = rightBottom;
// Find max and min values (with respect to possible NaN values)
if (!Double.IsNaN(leftTop))
{
if (min > leftTop)
min = leftTop;
if (max < leftTop)
max = leftTop;
}
if (!Double.IsNaN(leftBottom))
{
if (min > leftBottom)
min = leftBottom;
if (max < leftBottom)
max = leftBottom;
}
if (!Double.IsNaN(rightTop))
{
if (min > rightTop)
min = rightTop;
if (max < rightTop)
max = rightTop;
}
if (!Double.IsNaN(rightBottom))
{
if (min > rightBottom)
min = rightBottom;
if (max < rightBottom)
max = rightBottom;
}
left = (leftTop + leftBottom) / 2;
bottom = (leftBottom + rightBottom) / 2;
right = (rightTop + rightBottom) / 2;
top = (rightTop + leftTop) / 2;
}
public ValuesInCell(double leftBottom, double rightBottom, double rightTop, double leftTop, double missingValue)
{
DebugVerify.IsNotNaN(leftBottom);
DebugVerify.IsNotNaN(rightBottom);
DebugVerify.IsNotNaN(rightTop);
DebugVerify.IsNotNaN(leftTop);
// Copy values and find min and max with respect to possible missing values
if (leftTop != missingValue)
{
this.leftTop = leftTop;
if (min > leftTop)
min = leftTop;
if (max < leftTop)
max = leftTop;
}
else
this.leftTop = Double.NaN;
if (leftBottom != missingValue)
{
this.leftBottom = leftBottom;
if (min > leftBottom)
min = leftBottom;
if (max < leftBottom)
max = leftBottom;
}
else
this.leftBottom = Double.NaN;
if (rightTop != missingValue)
{
this.rightTop = rightTop;
if (min > rightTop)
min = rightTop;
if (max < rightTop)
max = rightTop;
}
else
this.rightTop = Double.NaN;
if (rightBottom != missingValue)
{
this.rightBottom = rightBottom;
if (min > rightBottom)
min = rightBottom;
if (max < rightBottom)
max = rightBottom;
}
else
this.rightBottom = Double.NaN;
left = (this.leftTop + this.leftBottom) / 2;
bottom = (this.leftBottom + this.rightBottom) / 2;
right = (this.rightTop + this.rightBottom) / 2;
top = (this.rightTop + this.leftTop) / 2;
/*
if (leftTop != missingValue && )
{
if (leftBottom != missingValue)
left = (leftTop + leftBottom) / 2;
else
left = Double.NaN;
if (rightTop != missingValue)
top = (leftTop + rightTop) / 2;
else
top = Double.NaN;
}
if (rightBottom != missingValue)
{
if (leftBottom != missingValue)
bottom = (leftBottom + rightBottom) / 2;
else
bottom = Double.NaN;
if (rightTop != missingValue)
right = (rightTop + rightBottom) / 2;
else
right = Double.NaN;
}*/
}
/*internal bool ValueBelongTo(double value)
{
IEnumerable<double> values = new double[] { leftTop, leftBottom, rightTop, rightBottom };
return !(values.All(v => v > value) || values.All(v => v < value));
}*/
internal bool ValueBelongTo(double value)
{
return (min <= value && value <= max);
}
#region Edges
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly double leftTop;
public double LeftTop { get { return leftTop; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly double leftBottom;
public double LeftBottom { get { return leftBottom; } }
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly double rightTop;
public double RightTop
{
get { return rightTop; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly double rightBottom;
public double RightBottom
{
get { return rightBottom; }
}
#endregion
#region Sides & center
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly double left;
public double Left
{
get { return left; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly double right;
public double Right
{
get { return right; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly double top;
public double Top
{
get { return top; }
}
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private readonly double bottom;
public double Bottom
{
get { return bottom; }
}
public double Center
{
get { return (Left + Right) * 0.5; }
}
#endregion
#region SubCells
public ValuesInCell LeftTopCell
{
get { return new ValuesInCell(Left, Center, Top, LeftTop); }
}
public ValuesInCell RightTopCell
{
get { return new ValuesInCell(Center, Right, RightTop, Top); }
}
public ValuesInCell RightBottomCell
{
get { return new ValuesInCell(Bottom, RightBottom, Right, Center); }
}
public ValuesInCell LeftBottomCell
{
get { return new ValuesInCell(LeftBottom, Bottom, Center, Left); }
}
public ValuesInCell GetSubCell(SubCell subCell)
{
switch (subCell)
{
case SubCell.LeftBottom:
return LeftBottomCell;
case SubCell.LeftTop:
return LeftTopCell;
case SubCell.RightBottom:
return RightBottomCell;
case SubCell.RightTop:
default:
return RightTopCell;
}
}
#endregion
/// <summary>
/// Returns bitmask of comparison of values at cell corners with reference value.
/// Corresponding bit is set to one if value at cell corner is greater than reference value.
/// a------b
/// | Cell |
/// d------c
/// </summary>
/// <param name="a">Value at corner (see figure)</param>
/// <param name="b">Value at corner (see figure)</param>
/// <param name="c">Value at corner (see figure)</param>
/// <param name="d">Value at corner (see figure)</param>
/// <param name="value">Reference value</param>
/// <returns>Bitmask</returns>
public CellBitmask GetCellValue(double value)
{
CellBitmask n = CellBitmask.None;
if (!Double.IsNaN(leftTop) && leftTop > value)
n |= CellBitmask.LeftTop;
if (!Double.IsNaN(leftBottom) && leftBottom > value)
n |= CellBitmask.LeftBottom;
if (!Double.IsNaN(rightBottom) && rightBottom > value)
n |= CellBitmask.RightBottom;
if (!Double.IsNaN(rightTop) && rightTop > value)
n |= CellBitmask.RightTop;
return n;
}
}
}

67
Charts/Isolines/Enums.cs Normal file
View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
/// <summary>
/// Edge identifier - indicates which side of cell isoline crosses.
/// </summary>
internal enum Edge
{
// todo check if everything is ok with None.
None = 0,
/// <summary>
/// Isoline crosses left boundary of cell (bit 0)
/// </summary>
Left = 1,
/// <summary>
/// Isoline crosses top boundary of cell (bit 1)
/// </summary>
Top = 2,
/// <summary>
/// Isoline crosses right boundary of cell (bit 2)
/// </summary>
Right = 4,
/// <summary>
/// Isoline crosses bottom boundary of cell (bit 3)
/// </summary>
Bottom = 8
}
[Flags]
internal enum CellBitmask
{
None = 0,
LeftTop = 1,
LeftBottom = 8,
RightBottom = 4,
RightTop = 2
}
internal static class IsolineExtensions
{
internal static bool IsDiagonal(this CellBitmask bitmask)
{
return bitmask == (CellBitmask.RightBottom | CellBitmask.LeftTop) ||
bitmask == (CellBitmask.LeftBottom | CellBitmask.RightTop);
}
internal static bool IsAppropriate(this SubCell sub, Edge edge)
{
switch (sub)
{
case SubCell.LeftBottom:
return edge == Edge.Left || edge == Edge.Bottom;
case SubCell.LeftTop:
return edge == Edge.Left || edge == Edge.Top;
case SubCell.RightBottom:
return edge == Edge.Right || edge == Edge.Bottom;
case SubCell.RightTop:
default:
return edge == Edge.Right || edge == Edge.Top;
}
}
}
}

View File

@@ -0,0 +1,19 @@
<isolines:IsolineGraphBase x:Class="Microsoft.Research.DynamicDataDisplay.Charts.Isolines.FastIsolineDisplay"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:isolines="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts.Isolines"
xmlns:local="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts"
xmlns:common="clr-namespace:Microsoft.Research.DynamicDataDisplay"
IsHitTestVisible="False"
>
<isolines:IsolineGraphBase.Template>
<ControlTemplate TargetType="{x:Type isolines:IsolineGraphBase}">
<Canvas IsHitTestVisible="False">
<local:ViewportHostPanel Name="PART_ViewportPanel">
<isolines:FastIsolineRenderer Name="PART_IsolineRenderer"/>
<isolines:AdditionalLinesRenderer Name="PART_AdditionalLinesRenderer"/>
</local:ViewportHostPanel>
</Canvas>
</ControlTemplate>
</isolines:IsolineGraphBase.Template>
</isolines:IsolineGraphBase>

View File

@@ -0,0 +1,59 @@
using System;
using System.Collections.Generic;
using System.Linq;
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 Microsoft.Research.DynamicDataDisplay.Charts.Shapes;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
public partial class FastIsolineDisplay : IsolineGraphBase
{
public FastIsolineDisplay()
{
InitializeComponent();
}
protected override Panel HostPanel
{
get
{
return Plotter2D.CentralGrid;
}
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
var isolineRenderer = (FastIsolineRenderer)Template.FindName("PART_IsolineRenderer", this);
//Binding contentBoundsBinding = new Binding { Path = new PropertyPath("(0)", Viewport2D.ContentBoundsProperty), Source = isolineRenderer };
//SetBinding(Viewport2D.ContentBoundsProperty, contentBoundsBinding);
if (isolineRenderer != null)
{
isolineRenderer.AddHandler(Viewport2D.ContentBoundsChangedEvent, new RoutedEventHandler(OnRendererContentBoundsChanged));
UpdateContentBounds(isolineRenderer);
}
}
private void OnRendererContentBoundsChanged(object sender, RoutedEventArgs e)
{
UpdateContentBounds((DependencyObject)sender);
}
private void UpdateContentBounds(DependencyObject source)
{
var contentBounds = Viewport2D.GetContentBounds(source);
Viewport2D.SetContentBounds(this, contentBounds);
}
}
}

View File

@@ -0,0 +1,200 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows;
using System.Diagnostics;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using Microsoft.Research.DynamicDataDisplay.Charts.Shapes;
using System.Windows.Threading;
using System.Globalization;
using Microsoft.Research.DynamicDataDisplay.Charts.NewLine;
using System.Windows.Data;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
public class FastIsolineRenderer : IsolineRenderer
{
private List<IsolineCollection> additionalLines = new List<IsolineCollection>();
private const int subDivisionNum = 10;
protected override void CreateUIRepresentation()
{
InvalidateVisual();
}
protected override void OnPlotterAttached()
{
base.OnPlotterAttached();
FrameworkElement parent = (FrameworkElement)Parent;
Binding collectionBinding = new Binding("IsolineCollection") { Source = this };
parent.SetBinding(IsolineCollectionProperty, collectionBinding);
}
protected override void OnRender(DrawingContext drawingContext)
{
if (Plotter2D == null) return;
if (Collection == null) return;
if (DataSource == null) return;
if (Collection.Lines.Count == 0)
{
IsolineBuilder.DataSource = DataSource;
IsolineBuilder.MissingValue = MissingValue;
Collection = IsolineBuilder.BuildIsoline();
}
IsolineCollection = Collection;
var dc = drawingContext;
var strokeThickness = StrokeThickness;
var collection = Collection;
var bounds = DataRect.Empty;
// determining content bounds
foreach (LevelLine line in collection)
{
foreach (Point point in line.AllPoints)
{
bounds.Union(point);
}
}
Viewport2D.SetContentBounds(this, bounds);
ViewportPanel.SetViewportBounds(this, bounds);
if (bounds.IsEmpty) return;
// custom transform with output set to renderSize of this control
var transform = Plotter2D.Transform.WithRects(bounds, new Rect(RenderSize));
// actual drawing of isolines
RenderIsolineCollection(dc, strokeThickness, collection, transform);
//var additionalLevels = GetAdditionalIsolines(collection);
//var additionalIsolineCollections = additionalLevels.Select(level => IsolineBuilder.BuildIsoline(level));
//foreach (var additionalCollection in additionalIsolineCollections)
//{
// RenderIsolineCollection(dc, strokeThickness, additionalCollection, transform);
//}
RenderLabels(dc, collection);
// foreach (var additionalCollection in additionalIsolineCollections)
// {
// RenderLabels(dc, additionalCollection);
// }
}
private IEnumerable<double> GetAdditionalIsolines(IsolineCollection collection)
{
var dataSource = DataSource;
var visibleMinMax = dataSource.GetMinMax(Plotter2D.Visible);
var visibleMinMaxRatio = (collection.Max - collection.Min) / visibleMinMax.GetLength();
var log = Math.Log10(visibleMinMaxRatio);
if (log > 0.9)
{
var upperLog = Math.Ceiling(log);
var divisionsNum = Math.Pow(10, upperLog);
var delta = (collection.Max - collection.Min) / divisionsNum;
var start = Math.Ceiling(visibleMinMax.Min / delta) * delta;
var x = start;
while (x < visibleMinMax.Max)
{
yield return x;
x += delta;
}
}
}
private void RenderLabels(DrawingContext dc, IsolineCollection collection)
{
if (Plotter2D == null) return;
if (collection == null) return;
if (!DrawLabels) return;
var viewportBounds = ViewportPanel.GetViewportBounds(this);
if (viewportBounds.IsEmpty)
return;
var strokeThickness = StrokeThickness;
var visible = Plotter2D.Visible;
var output = Plotter2D.Viewport.Output;
var transform = Plotter2D.Transform.WithRects(viewportBounds, new Rect(RenderSize));
var labelStringFormat = LabelStringFormat;
// drawing constants
var labelRectangleFill = Brushes.White;
var biggerViewport = viewportBounds.ZoomOutFromCenter(1.1);
// getting and filtering annotations to draw only visible ones
Annotater.WayBeforeText = Math.Sqrt(visible.Width * visible.Width + visible.Height * visible.Height) / 100 * WayBeforeTextMultiplier;
var annotations = Annotater.Annotate(collection, visible)
.Where(annotation =>
{
Point viewportPosition = annotation.Position.DataToViewport(transform);
return biggerViewport.Contains(viewportPosition);
});
// drawing annotations
foreach (var annotation in annotations)
{
FormattedText text = CreateFormattedText(annotation.Value.ToString(LabelStringFormat));
Point position = annotation.Position.DataToScreen(transform);
var labelTransform = CreateTransform(annotation, text, position);
// creating rectange stroke
double colorRatio = (annotation.Value - collection.Min) / (collection.Max - collection.Min);
colorRatio = MathHelper.Clamp(colorRatio);
Color rectangleStrokeColor = Palette.GetColor(colorRatio);
SolidColorBrush rectangleStroke = new SolidColorBrush(rectangleStrokeColor);
Pen labelRectangleStrokePen = new Pen(rectangleStroke, 2);
dc.PushTransform(labelTransform);
{
var bounds = RectExtensions.FromCenterSize(position, new Size(text.Width, text.Height));
bounds = bounds.ZoomOutFromCenter(1.3);
dc.DrawRoundedRectangle(labelRectangleFill, labelRectangleStrokePen, bounds, 8, 8);
DrawTextInPosition(dc, text, position);
}
dc.Pop();
}
}
private static void DrawTextInPosition(DrawingContext dc, FormattedText text, Point position)
{
var textPosition = position;
textPosition.Offset(-text.Width / 2, -text.Height / 2);
dc.DrawText(text, textPosition);
}
private static Transform CreateTransform(IsolineTextLabel isolineLabel, FormattedText text, Point position)
{
double angle = isolineLabel.Rotation;
if (angle < 0)
angle += 360;
if (90 < angle && angle < 270)
angle -= 180;
RotateTransform transform = new RotateTransform(angle, position.X, position.Y);
return transform;
}
private static FormattedText CreateFormattedText(string text)
{
FormattedText result = new FormattedText(text,
CultureInfo.CurrentCulture, System.Windows.FlowDirection.LeftToRight, new Typeface("Arial"), 12, Brushes.Black,1.0);
return result;
}
}
}

View File

@@ -0,0 +1,708 @@
using System;
using System.Linq;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Media;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using System.Collections.Generic;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
/// <summary>
/// Generates geometric object for isolines of the input 2d scalar field.
/// </summary>
public sealed class IsolineBuilder
{
/// <summary>
/// The density of isolines means the number of levels to draw.
/// </summary>
private int density = 12;
private bool[,] processed;
/// <summary>Number to be treated as missing value. NaN if no missing value is specified</summary>
private double missingValue = Double.NaN;
static IsolineBuilder()
{
SetCellDictionaries();
}
/// <summary>
/// Initializes a new instance of the <see cref="IsolineBuilder"/> class.
/// </summary>
public IsolineBuilder() { }
/// <summary>
/// Initializes a new instance of the <see cref="IsolineBuilder"/> class for specified 2d scalar data source.
/// </summary>
/// <param name="dataSource">The data source with 2d scalar data.</param>
public IsolineBuilder(IDataSource2D<double> dataSource)
{
DataSource = dataSource;
}
public double MissingValue
{
get
{
return missingValue;
}
set
{
missingValue = value;
}
}
#region Private methods
private static Dictionary<int, Dictionary<int, Edge>> dictChooser = new Dictionary<int, Dictionary<int, Edge>>();
private static void SetCellDictionaries()
{
var bottomDict = new Dictionary<int, Edge>();
bottomDict.Add((int)CellBitmask.RightBottom, Edge.Right);
bottomDict.Add(Edge.Left,
CellBitmask.LeftTop,
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop,
CellBitmask.LeftTop | CellBitmask.RightBottom | CellBitmask.RightTop,
CellBitmask.LeftBottom);
bottomDict.Add(Edge.Right,
CellBitmask.RightTop,
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.LeftTop,
CellBitmask.LeftBottom | CellBitmask.LeftTop | CellBitmask.RightTop);
bottomDict.Add(Edge.Top,
CellBitmask.RightBottom | CellBitmask.RightTop,
CellBitmask.LeftBottom | CellBitmask.LeftTop);
var leftDict = new Dictionary<int, Edge>();
leftDict.Add(Edge.Top,
CellBitmask.LeftTop,
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop);
leftDict.Add(Edge.Right,
CellBitmask.LeftTop | CellBitmask.RightTop,
CellBitmask.LeftBottom | CellBitmask.RightBottom);
leftDict.Add(Edge.Bottom,
CellBitmask.RightBottom | CellBitmask.RightTop | CellBitmask.LeftTop,
CellBitmask.LeftBottom);
var topDict = new Dictionary<int, Edge>();
topDict.Add(Edge.Right,
CellBitmask.RightTop,
CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightBottom);
topDict.Add(Edge.Right,
CellBitmask.RightBottom,
CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightTop);
topDict.Add(Edge.Left,
CellBitmask.RightBottom | CellBitmask.RightTop | CellBitmask.LeftTop,
CellBitmask.LeftBottom,
CellBitmask.LeftTop,
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop);
topDict.Add(Edge.Bottom,
CellBitmask.RightBottom | CellBitmask.RightTop,
CellBitmask.LeftTop | CellBitmask.LeftBottom);
var rightDict = new Dictionary<int, Edge>();
rightDict.Add(Edge.Top,
CellBitmask.RightTop,
CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.LeftTop);
rightDict.Add(Edge.Left,
CellBitmask.LeftTop | CellBitmask.RightTop,
CellBitmask.LeftBottom | CellBitmask.RightBottom);
rightDict.Add(Edge.Bottom,
CellBitmask.RightBottom,
CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightTop);
dictChooser.Add((int)Edge.Left, leftDict);
dictChooser.Add((int)Edge.Right, rightDict);
dictChooser.Add((int)Edge.Bottom, bottomDict);
dictChooser.Add((int)Edge.Top, topDict);
}
private Edge GetOutEdge(Edge inEdge, ValuesInCell cv, IrregularCell rect, double value)
{
// value smaller than all values in corners or
// value greater than all values in corners
if (!cv.ValueBelongTo(value))
{
throw new IsolineGenerationException(Strings.Exceptions.IsolinesValueIsOutOfCell);
}
CellBitmask cellVal = cv.GetCellValue(value);
var dict = dictChooser[(int)inEdge];
if (dict.ContainsKey((int)cellVal))
{
Edge result = dict[(int)cellVal];
switch (result)
{
case Edge.Left:
if (cv.LeftTop.IsNaN() || cv.LeftBottom.IsNaN())
result = Edge.None;
break;
case Edge.Right:
if (cv.RightTop.IsNaN() || cv.RightBottom.IsNaN())
result = Edge.None;
break;
case Edge.Top:
if (cv.RightTop.IsNaN() || cv.LeftTop.IsNaN())
result = Edge.None;
break;
case Edge.Bottom:
if (cv.LeftBottom.IsNaN() || cv.RightBottom.IsNaN())
result = Edge.None;
break;
}
return result;
}
else if (cellVal.IsDiagonal())
{
return GetOutForOpposite(inEdge, cellVal, value, cv, rect);
}
const double near_zero = 0.0001;
const double near_one = 1 - near_zero;
double lt = cv.LeftTop;
double rt = cv.RightTop;
double rb = cv.RightBottom;
double lb = cv.LeftBottom;
switch (inEdge)
{
case Edge.Left:
if (value == lt)
value = near_one * lt + near_zero * lb;
else if (value == lb)
value = near_one * lb + near_zero * lt;
else
return Edge.None;
// Now this is possible because of missing value
//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
break;
case Edge.Top:
if (value == rt)
value = near_one * rt + near_zero * lt;
else if (value == lt)
value = near_one * lt + near_zero * rt;
else
return Edge.None;
// Now this is possibe because of missing value
//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
break;
case Edge.Right:
if (value == rb)
value = near_one * rb + near_zero * rt;
else if (value == rt)
value = near_one * rt + near_zero * rb;
else
return Edge.None;
// Now this is possibe because of missing value
//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
break;
case Edge.Bottom:
if (value == rb)
value = near_one * rb + near_zero * lb;
else if (value == lb)
value = near_one * lb + near_zero * rb;
else
return Edge.None;
// Now this is possibe because of missing value
//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
break;
}
// Recursion?
//return GetOutEdge(inEdge, cv, rect, value);
return Edge.None;
}
private Edge GetOutForOpposite(Edge inEdge, CellBitmask cellVal, double value, ValuesInCell cellValues, IrregularCell rect)
{
Edge outEdge;
SubCell subCell = GetSubCell(inEdge, value, cellValues);
int iters = 1000; // max number of iterations
do
{
ValuesInCell subValues = cellValues.GetSubCell(subCell);
IrregularCell subRect = rect.GetSubRect(subCell);
outEdge = GetOutEdge(inEdge, subValues, subRect, value);
if (outEdge == Edge.None)
return Edge.None;
bool isAppropriate = subCell.IsAppropriate(outEdge);
if (isAppropriate)
{
ValuesInCell sValues = subValues.GetSubCell(subCell);
Point point = GetPointXY(outEdge, value, subValues, subRect);
segments.AddPoint(point);
return outEdge;
}
else
{
subCell = GetAdjacentEdge(subCell, outEdge);
}
byte e = (byte)outEdge;
inEdge = (Edge)((e > 2) ? (e >> 2) : (e << 2));
iters--;
} while (iters >= 0);
throw new IsolineGenerationException(Strings.Exceptions.IsolinesDataIsUndetailized);
}
private static SubCell GetAdjacentEdge(SubCell sub, Edge edge)
{
SubCell res = SubCell.LeftBottom;
switch (sub)
{
case SubCell.LeftBottom:
res = edge == Edge.Top ? SubCell.LeftTop : SubCell.RightBottom;
break;
case SubCell.LeftTop:
res = edge == Edge.Bottom ? SubCell.LeftBottom : SubCell.RightTop;
break;
case SubCell.RightBottom:
res = edge == Edge.Top ? SubCell.RightTop : SubCell.LeftBottom;
break;
case SubCell.RightTop:
default:
res = edge == Edge.Bottom ? SubCell.RightBottom : SubCell.LeftTop;
break;
}
return res;
}
private static SubCell GetSubCell(Edge inEdge, double value, ValuesInCell vc)
{
double lb = vc.LeftBottom;
double rb = vc.RightBottom;
double rt = vc.RightTop;
double lt = vc.LeftTop;
SubCell res = SubCell.LeftBottom;
switch (inEdge)
{
case Edge.Left:
res = (Math.Abs(value - lb) < Math.Abs(value - lt)) ? SubCell.LeftBottom : SubCell.LeftTop;
break;
case Edge.Top:
res = (Math.Abs(value - lt) < Math.Abs(value - rt)) ? SubCell.LeftTop : SubCell.RightTop;
break;
case Edge.Right:
res = (Math.Abs(value - rb) < Math.Abs(value - rt)) ? SubCell.RightBottom : SubCell.RightTop;
break;
case Edge.Bottom:
default:
res = (Math.Abs(value - lb) < Math.Abs(value - rb)) ? SubCell.LeftBottom : SubCell.RightBottom;
break;
}
ValuesInCell subValues = vc.GetSubCell(res);
bool valueInside = subValues.ValueBelongTo(value);
if (!valueInside)
{
throw new IsolineGenerationException(Strings.Exceptions.IsolinesDataIsUndetailized);
}
return res;
}
private static Point GetPoint(double value, double a1, double a2, Vector v1, Vector v2)
{
double ratio = (value - a1) / (a2 - a1);
Verify.IsTrue(0 <= ratio && ratio <= 1);
Vector r = (1 - ratio) * v1 + ratio * v2;
return new Point(r.X, r.Y);
}
private Point GetPointXY(Edge edge, double value, ValuesInCell vc, IrregularCell rect)
{
double lt = vc.LeftTop;
double lb = vc.LeftBottom;
double rb = vc.RightBottom;
double rt = vc.RightTop;
switch (edge)
{
case Edge.Left:
return GetPoint(value, lb, lt, rect.LeftBottom, rect.LeftTop);
case Edge.Top:
return GetPoint(value, lt, rt, rect.LeftTop, rect.RightTop);
case Edge.Right:
return GetPoint(value, rb, rt, rect.RightBottom, rect.RightTop);
case Edge.Bottom:
return GetPoint(value, lb, rb, rect.LeftBottom, rect.RightBottom);
default:
throw new InvalidOperationException();
}
}
private bool BelongsToEdge(double value, double edgeValue1, double edgeValue2, bool onBoundary)
{
if (!Double.IsNaN(missingValue) && (edgeValue1 == missingValue || edgeValue2 == missingValue))
return false;
if (onBoundary)
{
return (edgeValue1 <= value && value < edgeValue2) ||
(edgeValue2 <= value && value < edgeValue1);
}
else
{
return (edgeValue1 < value && value < edgeValue2) ||
(edgeValue2 < value && value < edgeValue1);
}
}
private bool IsPassed(Edge edge, int i, int j, byte[,] edges)
{
switch (edge)
{
case Edge.Left:
return (i == 0) || (edges[i, j] & (byte)edge) != 0;
case Edge.Bottom:
return (j == 0) || (edges[i, j] & (byte)edge) != 0;
case Edge.Top:
return (j == edges.GetLength(1) - 2) || (edges[i, j + 1] & (byte)Edge.Bottom) != 0;
case Edge.Right:
return (i == edges.GetLength(0) - 2) || (edges[i + 1, j] & (byte)Edge.Left) != 0;
default:
throw new InvalidOperationException();
}
}
private void MakeEdgePassed(Edge edge, int i, int j)
{
switch (edge)
{
case Edge.Left:
case Edge.Bottom:
edges[i, j] |= (byte)edge;
break;
case Edge.Top:
edges[i, j + 1] |= (byte)Edge.Bottom;
break;
case Edge.Right:
edges[i + 1, j] |= (byte)Edge.Left;
break;
default:
throw new InvalidOperationException();
}
}
private Edge TrackLine(Edge inEdge, double value, ref int x, ref int y, out double newX, out double newY)
{
// Getting output edge
ValuesInCell vc = (missingValue.IsNaN()) ?
(new ValuesInCell(values[x, y],
values[x + 1, y],
values[x + 1, y + 1],
values[x, y + 1])) :
(new ValuesInCell(values[x, y],
values[x + 1, y],
values[x + 1, y + 1],
values[x, y + 1],
missingValue));
IrregularCell rect = new IrregularCell(
grid[x, y],
grid[x + 1, y],
grid[x + 1, y + 1],
grid[x, y + 1]);
Edge outEdge = GetOutEdge(inEdge, vc, rect, value);
if (outEdge == Edge.None)
{
newX = newY = -1; // Impossible cell indices
return Edge.None;
}
// Drawing new segment
Point point = GetPointXY(outEdge, value, vc, rect);
newX = point.X;
newY = point.Y;
segments.AddPoint(point);
processed[x, y] = true;
// Whether out-edge already was passed?
if (IsPassed(outEdge, x, y, edges)) // line is closed
{
//MakeEdgePassed(outEdge, x, y); // boundaries should be marked as passed too
return Edge.None;
}
// Make this edge passed
MakeEdgePassed(outEdge, x, y);
// Getting next cell's indices
switch (outEdge)
{
case Edge.Left:
x--;
return Edge.Right;
case Edge.Top:
y++;
return Edge.Bottom;
case Edge.Right:
x++;
return Edge.Left;
case Edge.Bottom:
y--;
return Edge.Top;
default:
throw new InvalidOperationException();
}
}
private void TrackLineNonRecursive(Edge inEdge, double value, int x, int y)
{
int s = x, t = y;
ValuesInCell vc = (missingValue.IsNaN()) ?
(new ValuesInCell(values[x, y],
values[x + 1, y],
values[x + 1, y + 1],
values[x, y + 1])) :
(new ValuesInCell(values[x, y],
values[x + 1, y],
values[x + 1, y + 1],
values[x, y + 1],
missingValue));
IrregularCell rect = new IrregularCell(
grid[x, y],
grid[x + 1, y],
grid[x + 1, y + 1],
grid[x, y + 1]);
Point point = GetPointXY(inEdge, value, vc, rect);
segments.StartLine(point, (value - minMax.Min) / (minMax.Max - minMax.Min), value);
MakeEdgePassed(inEdge, x, y);
//processed[x, y] = true;
double x2, y2;
do
{
inEdge = TrackLine(inEdge, value, ref s, ref t, out x2, out y2);
} while (inEdge != Edge.None);
}
#endregion
private bool HasIsoline(int x, int y)
{
return (edges[x,y] != 0 &&
((x < edges.GetLength(0) - 1 && edges[x+1,y] != 0) ||
(y < edges.GetLength(1) - 1 && edges[x,y+1] != 0)));
}
/// <summary>Finds isoline for specified reference value</summary>
/// <param name="value">Reference value</param>
private void PrepareCells(double value)
{
double currentRatio = (value - minMax.Min) / (minMax.Max - minMax.Min);
if (currentRatio < 0 || currentRatio > 1)
return; // No contour lines for such value
int xSize = dataSource.Width;
int ySize = dataSource.Height;
int x, y;
for (x = 0; x < xSize; x++)
for (y = 0; y < ySize; y++)
edges[x, y] = 0;
processed = new bool[xSize, ySize];
// Looking in boundaries.
// left
for (y = 1; y < ySize; y++)
{
if (BelongsToEdge(value, values[0, y - 1], values[0, y], true) &&
(edges[0, y - 1] & (byte)Edge.Left) == 0)
{
TrackLineNonRecursive(Edge.Left, value, 0, y - 1);
}
}
// bottom
for (x = 0; x < xSize - 1; x++)
{
if (BelongsToEdge(value, values[x, 0], values[x + 1, 0], true)
&& (edges[x, 0] & (byte)Edge.Bottom) == 0)
{
TrackLineNonRecursive(Edge.Bottom, value, x, 0);
};
}
// right
x = xSize - 1;
for (y = 1; y < ySize; y++)
{
// Is this correct?
//if (BelongsToEdge(value, values[0, y - 1], values[0, y], true) &&
// (edges[0, y - 1] & (byte)Edge.Left) == 0)
//{
// TrackLineNonRecursive(Edge.Left, value, 0, y - 1);
//};
if (BelongsToEdge(value, values[x, y - 1], values[x, y], true) &&
(edges[x, y - 1] & (byte)Edge.Left) == 0)
{
TrackLineNonRecursive(Edge.Right, value, x - 1, y - 1);
};
}
// horizontals
for (x = 1; x < xSize - 1; x++)
for (y = 1; y < ySize - 1; y++)
{
if ((edges[x, y] & (byte)Edge.Bottom) == 0 &&
BelongsToEdge(value, values[x, y], values[x + 1, y], false) &&
!processed[x,y-1])
{
TrackLineNonRecursive(Edge.Top, value, x, y-1);
}
if ((edges[x, y] & (byte)Edge.Bottom) == 0 &&
BelongsToEdge(value, values[x, y], values[x + 1, y], false) &&
!processed[x,y])
{
TrackLineNonRecursive(Edge.Bottom, value, x, y);
}
if ((edges[x, y] & (byte)Edge.Left) == 0 &&
BelongsToEdge(value, values[x, y], values[x, y - 1], false) &&
!processed[x-1,y-1])
{
TrackLineNonRecursive(Edge.Right, value, x - 1, y - 1);
}
if ((edges[x, y] & (byte)Edge.Left) == 0 &&
BelongsToEdge(value, values[x, y], values[x, y - 1], false) &&
!processed[x,y-1])
{
TrackLineNonRecursive(Edge.Left, value, x, y - 1);
}
}
}
/// <summary>
/// Builds isoline data for 2d scalar field contained in data source.
/// </summary>
/// <returns>Collection of data describing built isolines.</returns>
public IsolineCollection BuildIsoline()
{
VerifyDataSource();
segments = new IsolineCollection();
minMax = (Double.IsNaN(missingValue) ? dataSource.GetMinMax() : dataSource.GetMinMax(missingValue));
segments.Min = minMax.Min;
segments.Max = minMax.Max;
if (!minMax.IsEmpty)
{
values = dataSource.Data;
double[] levels = GetLevelsForIsolines();
foreach (double level in levels)
{
PrepareCells(level);
}
if (segments.Lines.Count > 0 && segments.Lines[segments.Lines.Count - 1].OtherPoints.Count == 0)
segments.Lines.RemoveAt(segments.Lines.Count - 1);
}
return segments;
}
/// <summary>
/// Builds isoline data for the specified level in 2d scalar field.
/// </summary>
/// <param name="level">The level.</param>
/// <returns></returns>
public IsolineCollection BuildIsoline(double level)
{
VerifyDataSource();
segments = new IsolineCollection();
minMax = (Double.IsNaN(missingValue) ? dataSource.GetMinMax() : dataSource.GetMinMax(missingValue));
if (!minMax.IsEmpty)
{
values = dataSource.Data;
PrepareCells(level);
if (segments.Lines.Count > 0 && segments.Lines[segments.Lines.Count - 1].OtherPoints.Count == 0)
segments.Lines.RemoveAt(segments.Lines.Count - 1);
}
return segments;
}
private void VerifyDataSource()
{
if (dataSource == null)
throw new InvalidOperationException(Strings.Exceptions.IsolinesDataSourceShouldBeSet);
}
IsolineCollection segments;
private double[,] values;
private byte[,] edges;
private Point[,] grid;
private Range<double> minMax;
private IDataSource2D<double> dataSource;
/// <summary>
/// Gets or sets the data source - 2d scalar field.
/// </summary>
/// <value>The data source.</value>
public IDataSource2D<double> DataSource
{
get { return dataSource; }
set
{
if (dataSource != value)
{
value.VerifyNotNull("value");
dataSource = value;
grid = dataSource.Grid;
edges = new byte[dataSource.Width, dataSource.Height];
}
}
}
private const double shiftPercent = 0.05;
private double[] GetLevelsForIsolines()
{
double[] levels;
double min = minMax.Min;
double max = minMax.Max;
double step = (max - min) / (density - 1);
double delta = (max - min);
levels = new double[density];
levels[0] = min + delta * shiftPercent;
levels[levels.Length - 1] = max - delta * shiftPercent;
for (int i = 1; i < levels.Length - 1; i++)
levels[i] = min + i * step;
return levels;
}
}
}

View File

@@ -0,0 +1,159 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Collections;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
/// <summary>
/// LevelLine contains all data for one isoline line - its start point, other points and value in field.
/// </summary>
public sealed class LevelLine
{
/// <summary>
/// Gets or sets the value of line in limits of [0..1].
/// </summary>
/// <value>The value01.</value>
public double Value01 { get; set; }
/// <summary>
/// Gets or sets the real value of line - without scaling to [0..1] segment.
/// </summary>
/// <value>The real value.</value>
public double RealValue { get; set; }
/// <summary>
/// Gets or sets the start point of line.
/// </summary>
/// <value>The start point.</value>
public Point StartPoint { get; set; }
private readonly List<Point> otherPoints = new List<Point>();
/// <summary>
/// Gets other points of line, except first point.
/// </summary>
/// <value>The other points.</value>
public List<Point> OtherPoints
{
get { return otherPoints; }
}
/// <summary>
/// Gets all points of line, including start point.
/// </summary>
/// <value>All points.</value>
public IEnumerable<Point> AllPoints
{
get
{
yield return StartPoint;
for (int i = 0; i < otherPoints.Count; i++)
{
yield return otherPoints[i];
}
}
}
/// <summary>
/// Gets all the segments of lines.
/// </summary>
/// <returns></returns>
public IEnumerable<Range<Point>> GetSegments()
{
if (otherPoints.Count < 1)
yield break;
yield return new Range<Point>(StartPoint, otherPoints[0]);
for (int i = 1; i < otherPoints.Count; i++)
{
yield return new Range<Point>(otherPoints[i - 1], otherPoints[i]);
}
}
}
/// <summary>
/// IsolineTextLabel contains information about one label in isoline - its text, position and rotation.
/// </summary>
public sealed class IsolineTextLabel
{
/// <summary>
/// Gets or sets the rotation of isoline text label.
/// </summary>
/// <value>The rotation.</value>
public double Rotation { get; internal set; }
/// <summary>
/// Gets or sets the text of isoline label.
/// </summary>
/// <value>The text.</value>
public double Value { get; internal set; }
/// <summary>
/// Gets or sets the position of isoline text label.
/// </summary>
/// <value>The position.</value>
public Point Position { get; internal set; }
}
/// <summary>
/// Collection which contains all data generated by <seealso cref="IsolineBuilder"/>.
/// </summary>
public sealed class IsolineCollection : IEnumerable<LevelLine>
{
private double min;
public double Min
{
get { return min; }
set { min = value; }
}
private double max;
public double Max
{
get { return max; }
set { max = value; }
}
private readonly List<LevelLine> lines = new List<LevelLine>();
/// <summary>
/// Gets the list of isoline lines.
/// </summary>
/// <value>The lines.</value>
public List<LevelLine> Lines
{
get { return lines; }
}
internal void StartLine(Point p, double value01, double realValue)
{
LevelLine segment = new LevelLine { StartPoint = p, Value01 = value01, RealValue = realValue };
if (lines.Count == 0 || lines[lines.Count - 1].OtherPoints.Count > 0)
lines.Add(segment);
else
lines[lines.Count - 1] = segment;
}
internal void AddPoint(Point p)
{
lines[lines.Count - 1].OtherPoints.Add(p);
}
#region IEnumerable<LevelLine> Members
public IEnumerator<LevelLine> GetEnumerator()
{
return lines.GetEnumerator();
}
#endregion
#region IEnumerable Members
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
#endregion
}
}

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.Serialization;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
/// <summary>
/// Exception that is thrown when error occurs while building isolines.
/// </summary>
[Serializable]
public sealed class IsolineGenerationException : Exception
{
internal IsolineGenerationException() { }
internal IsolineGenerationException(string message) : base(message) { }
internal IsolineGenerationException(string message, Exception inner) : base(message, inner) { }
internal IsolineGenerationException(SerializationInfo info, StreamingContext context) : base(info, context) { }
}
}

View File

@@ -0,0 +1,258 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using Microsoft.Research.DynamicDataDisplay.Charts.Isolines;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay.Charts
{
/// <summary>
/// Draws isolines on given two-dimensional scalar data.
/// </summary>
public sealed class IsolineGraph : IsolineRenderer
{
private static Brush labelBackground = new SolidColorBrush(Color.FromArgb(130, 255, 255, 255));
/// <summary>
/// Initializes a new instance of the <see cref="IsolineGraph"/> class.
/// </summary>
public IsolineGraph()
{
Content = content;
Viewport2D.SetIsContentBoundsHost(this, true);
}
protected override void OnPlotterAttached()
{
CreateUIRepresentation();
UpdateUIRepresentation();
}
private readonly Canvas content = new Canvas();
protected override void UpdateDataSource()
{
base.UpdateDataSource();
CreateUIRepresentation();
rebuildText = true;
UpdateUIRepresentation();
}
protected override void OnLineThicknessChanged()
{
foreach (var path in linePaths)
{
path.StrokeThickness = StrokeThickness;
}
}
private List<FrameworkElement> textBlocks = new List<FrameworkElement>();
private List<Path> linePaths = new List<Path>();
protected override void CreateUIRepresentation()
{
if (Plotter2D == null)
return;
content.Children.Clear();
linePaths.Clear();
if (Collection != null)
{
DataRect bounds = DataRect.Empty;
foreach (var line in Collection.Lines)
{
foreach (var point in line.AllPoints)
{
bounds.Union(point);
}
Path path = new Path
{
Stroke = new SolidColorBrush(Palette.GetColor(line.Value01)),
StrokeThickness = StrokeThickness,
Data = CreateGeometry(line),
Tag = line
};
content.Children.Add(path);
linePaths.Add(path);
}
Viewport2D.SetContentBounds(this, bounds);
if (DrawLabels)
{
var transform = Plotter2D.Viewport.Transform;
double wayBeforeText = new Rect(new Size(2000, 2000)).ScreenToData(transform).Width;
Annotater.WayBeforeText = wayBeforeText;
var textLabels = Annotater.Annotate(Collection, Plotter2D.Viewport.Visible);
foreach (var textLabel in textLabels)
{
var text = CreateTextLabel(textLabel);
content.Children.Add(text);
textBlocks.Add(text);
}
}
}
}
private FrameworkElement CreateTextLabel(IsolineTextLabel textLabel)
{
var transform = Plotter2D.Viewport.Transform;
Point screenPos = textLabel.Position.DataToScreen(transform);
double angle = textLabel.Rotation;
if (angle < 0)
angle += 360;
if (135 < angle && angle < 225)
angle -= 180;
string tooltip = textLabel.Value.ToString("F"); //String.Format("{0} at ({1}, {2})", textLabel.Text, textLabel.Position.X, textLabel.Position.Y);
Grid grid = new Grid
{
RenderTransform = new RotateTransform(angle),
Tag = textLabel,
RenderTransformOrigin = new Point(0.5, 0.5),
ToolTip = tooltip
};
TextBlock res = new TextBlock
{
Text = textLabel.Value.ToString("F"),
Margin = new Thickness(3,1,3,1)
};
//res.Measure(SizeHelper.CreateInfiniteSize());
Rectangle rect = new Rectangle
{
Stroke = Brushes.Gray,
Fill = labelBackground,
RadiusX = 8,
RadiusY = 8
};
grid.Children.Add(rect);
grid.Children.Add(res);
grid.Measure(SizeHelper.CreateInfiniteSize());
Size textSize = grid.DesiredSize;
Point position = new Point(screenPos.X - textSize.Width / 2, screenPos.Y - textSize.Height / 2);
Canvas.SetLeft(grid, position.X);
Canvas.SetTop(grid, position.Y);
return grid;
}
private Geometry CreateGeometry(LevelLine lineData)
{
var transform = Plotter2D.Viewport.Transform;
StreamGeometry geometry = new StreamGeometry();
using (var context = geometry.Open())
{
context.BeginFigure(lineData.StartPoint.DataToScreen(transform), false, false);
context.PolyLineTo(lineData.OtherPoints.DataToScreenAsList(transform), true, true);
}
geometry.Freeze();
return geometry;
}
private bool rebuildText = true;
protected override void OnViewportPropertyChanged(ExtendedPropertyChangedEventArgs e)
{
if (e.PropertyName == "Visible" || e.PropertyName == "Output")
{
bool isVisibleChanged = e.PropertyName == "Visible";
DataRect prevRect = isVisibleChanged ? (DataRect)e.OldValue : new DataRect((Rect)e.OldValue);
DataRect currRect = isVisibleChanged ? (DataRect)e.NewValue : new DataRect((Rect)e.NewValue);
// completely rebuild text only if width and height have changed many
const double smallChangePercent = 0.05;
bool widthChangedLittle = Math.Abs(currRect.Width - prevRect.Width) / currRect.Width < smallChangePercent;
bool heightChangedLittle = Math.Abs(currRect.Height - prevRect.Height) / currRect.Height < smallChangePercent;
rebuildText = !(widthChangedLittle && heightChangedLittle);
}
UpdateUIRepresentation();
}
private void UpdateUIRepresentation()
{
if (Plotter2D == null) return;
foreach (var path in linePaths)
{
LevelLine line = (LevelLine)path.Tag;
path.Data = CreateGeometry(line);
}
var transform = Plotter2D.Viewport.Transform;
Rect output = Plotter2D.Viewport.Output;
DataRect visible = Plotter2D.Viewport.Visible;
if (rebuildText && DrawLabels)
{
rebuildText = false;
foreach (var text in textBlocks)
{
if (text.Visibility == Visibility.Visible)
content.Children.Remove(text);
}
textBlocks.Clear();
double wayBeforeText = new Rect(new Size(100, 100)).ScreenToData(transform).Width;
Annotater.WayBeforeText = wayBeforeText;
var textLabels = Annotater.Annotate(Collection, Plotter2D.Viewport.Visible);
foreach (var textLabel in textLabels)
{
var text = CreateTextLabel(textLabel);
textBlocks.Add(text);
if (visible.Contains(textLabel.Position))
{
content.Children.Add(text);
}
else
{
text.Visibility = Visibility.Hidden;
}
}
}
else
{
foreach (var text in textBlocks)
{
IsolineTextLabel label = (IsolineTextLabel)text.Tag;
Point screenPos = label.Position.DataToScreen(transform);
Size textSize = text.DesiredSize;
Point position = new Point(screenPos.X - textSize.Width / 2, screenPos.Y - textSize.Height / 2);
if (output.Contains(position))
{
Canvas.SetLeft(text, position.X);
Canvas.SetTop(text, position.Y);
if (text.Visibility == Visibility.Hidden)
{
text.Visibility = Visibility.Visible;
content.Children.Add(text);
}
}
else if (text.Visibility == Visibility.Visible)
{
text.Visibility = Visibility.Hidden;
content.Children.Remove(text);
}
}
}
}
}
}

View File

@@ -0,0 +1,282 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Research.DynamicDataDisplay.Common.Palettes;
using Microsoft.Research.DynamicDataDisplay.DataSources;
using DataSource = Microsoft.Research.DynamicDataDisplay.DataSources.IDataSource2D<double>;
using System.Windows;
using Microsoft.Research.DynamicDataDisplay.Charts.Shapes;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
public abstract class IsolineGraphBase : ContentGraph
{
protected IsolineGraphBase() { }
private IsolineCollection collection = new IsolineCollection();
protected IsolineCollection Collection
{
get { return collection; }
set { collection = value; }
}
private readonly IsolineBuilder isolineBuilder = new IsolineBuilder();
protected IsolineBuilder IsolineBuilder
{
get { return isolineBuilder; }
}
private readonly IsolineTextAnnotater annotater = new IsolineTextAnnotater();
protected IsolineTextAnnotater Annotater
{
get { return annotater; }
}
#region Properties
#region IsolineCollection property
public IsolineCollection IsolineCollection
{
get { return (IsolineCollection)GetValue(IsolineCollectionProperty); }
set { SetValue(IsolineCollectionProperty, value); }
}
public static readonly DependencyProperty IsolineCollectionProperty = DependencyProperty.Register(
"IsolineCollection",
typeof(IsolineCollection),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits));
#endregion // end of IsolineCollection property
#region WayBeforeTextMultiplier
public double WayBeforeTextMultiplier
{
get { return (double)GetValue(WayBeforeTextMultiplierProperty); }
set { SetValue(WayBeforeTextMultiplierProperty, value); }
}
public static readonly DependencyProperty WayBeforeTextMultiplierProperty = DependencyProperty.Register(
"WayBeforeTextCoeff",
typeof(double),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.Inherits, OnIsolinePropertyChanged));
#endregion // end of WayBeforeTextCoeff
private static void OnIsolinePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// todo do smth here
}
#region Palette property
public IPalette Palette
{
get { return (IPalette)GetValue(PaletteProperty); }
set { SetValue(PaletteProperty, value); }
}
public static readonly DependencyProperty PaletteProperty = DependencyProperty.Register(
"Palette",
typeof(IPalette),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata(new HSBPalette(), FrameworkPropertyMetadataOptions.Inherits, OnIsolinePropertyChanged), ValidatePalette);
private static bool ValidatePalette(object value)
{
return value != null;
}
#endregion // end of Palette property
#region DataSource property
public DataSource DataSource
{
get { return (DataSource)GetValue(DataSourceProperty); }
set { SetValue(DataSourceProperty, value); }
}
public static readonly DependencyProperty DataSourceProperty = DependencyProperty.Register(
"DataSource",
typeof(DataSource),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnDataSourceChanged));
private static void OnDataSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IsolineGraphBase owner = (IsolineGraphBase)d;
owner.OnDataSourceChanged((DataSource)e.OldValue, (DataSource)e.NewValue);
}
protected virtual void OnDataSourceChanged(IDataSource2D<double> prevDataSource, IDataSource2D<double> currDataSource)
{
if (prevDataSource != null)
prevDataSource.Changed -= OnDataSourceChanged;
if (currDataSource != null)
currDataSource.Changed += OnDataSourceChanged;
UpdateDataSource();
CreateUIRepresentation();
RaiseEvent(new RoutedEventArgs(BackgroundRenderer.UpdateRequested));
}
#endregion // end of DataSource property
#region DrawLabels property
public bool DrawLabels
{
get { return (bool)GetValue(DrawLabelsProperty); }
set { SetValue(DrawLabelsProperty, value); }
}
public static readonly DependencyProperty DrawLabelsProperty = DependencyProperty.Register(
"DrawLabels",
typeof(bool),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits, OnIsolinePropertyChanged));
#endregion // end of DrawLabels property
#region LabelStringFormat
public string LabelStringFormat
{
get { return (string)GetValue(LabelStringFormatProperty); }
set { SetValue(LabelStringFormatProperty, value); }
}
public static readonly DependencyProperty LabelStringFormatProperty = DependencyProperty.Register(
"LabelStringFormat",
typeof(string),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata("F", FrameworkPropertyMetadataOptions.Inherits, OnIsolinePropertyChanged));
#endregion // end of LabelStringFormat
#region UseBezierCurves
public bool UseBezierCurves
{
get { return (bool)GetValue(UseBezierCurvesProperty); }
set { SetValue(UseBezierCurvesProperty, value); }
}
public static readonly DependencyProperty UseBezierCurvesProperty = DependencyProperty.Register(
"UseBezierCurves",
typeof(bool),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.Inherits));
#endregion // end of UseBezierCurves
#endregion // end of Properties
#region DataSource
//private DataSource dataSource = null;
///// <summary>
///// Gets or sets the data source.
///// </summary>
///// <value>The data source.</value>
//public DataSource DataSource
//{
// get { return dataSource; }
// set
// {
// if (dataSource != value)
// {
// DetachDataSource(dataSource);
// dataSource = value;
// AttachDataSource(dataSource);
// UpdateDataSource();
// }
// }
//}
#region MissineValue property
public double MissingValue
{
get { return (double)GetValue(MissingValueProperty); }
set { SetValue(MissingValueProperty, value); }
}
public static readonly DependencyProperty MissingValueProperty = DependencyProperty.Register(
"MissingValue",
typeof(double),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata(Double.NaN, FrameworkPropertyMetadataOptions.Inherits, OnMissingValueChanged));
private static void OnMissingValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IsolineGraphBase owner = (IsolineGraphBase)d;
owner.UpdateDataSource();
}
#endregion // end of MissineValue property
public void SetDataSource(DataSource dataSource, double missingValue)
{
DataSource = dataSource;
MissingValue = missingValue;
UpdateDataSource();
}
/// <summary>
/// This method is called when data source changes.
/// </summary>
protected virtual void UpdateDataSource()
{
}
protected virtual void CreateUIRepresentation() { }
protected virtual void OnDataSourceChanged(object sender, EventArgs e)
{
UpdateDataSource();
}
#endregion
#region StrokeThickness
/// <summary>
/// Gets or sets thickness of isoline lines.
/// </summary>
/// <value>The stroke thickness.</value>
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
/// <summary>
/// Identifies the StrokeThickness dependency property.
/// </summary>
public static readonly DependencyProperty StrokeThicknessProperty =
DependencyProperty.Register(
"StrokeThickness",
typeof(double),
typeof(IsolineGraphBase),
new FrameworkPropertyMetadata(2.0, OnLineThicknessChanged));
private static void OnLineThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
IsolineGraphBase graph = (IsolineGraphBase)d;
graph.OnLineThicknessChanged();
}
protected virtual void OnLineThicknessChanged() { }
#endregion
}
}

View File

@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Media;
using System.Windows;
using System.Diagnostics;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using Microsoft.Research.DynamicDataDisplay.Charts.Shapes;
using System.Windows.Threading;
using System.Globalization;
using Microsoft.Research.DynamicDataDisplay.Charts.NewLine;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
public abstract class IsolineRenderer : IsolineGraphBase
{
protected override void UpdateDataSource()
{
if (DataSource != null)
{
IsolineBuilder.DataSource = DataSource;
IsolineBuilder.MissingValue = MissingValue;
Collection = IsolineBuilder.BuildIsoline();
}
else
{
Collection = null;
}
}
protected IEnumerable<double> GetAdditionalLevels(IsolineCollection collection)
{
var dataSource = DataSource;
var visibleMinMax = dataSource.GetMinMax(Plotter2D.Visible);
double totalDelta = collection.Max - collection.Min;
double visibleMinMaxRatio = totalDelta / visibleMinMax.GetLength();
double defaultDelta = totalDelta / 12;
if (true || 2 * defaultDelta < visibleMinMaxRatio)
{
double number = Math.Ceiling(visibleMinMaxRatio * 4);
number = Math.Pow(2, Math.Ceiling(Math.Log(number) / Math.Log(2)));
double delta = totalDelta / number;
double x = collection.Min + Math.Ceiling((visibleMinMax.Min - collection.Min) / delta) * delta;
List<double> result = new List<double>();
while (x < visibleMinMax.Max)
{
result.Add(x);
x += delta;
}
return result;
}
return Enumerable.Empty<double>();
}
protected void RenderIsolineCollection(DrawingContext dc, double strokeThickness, IsolineCollection collection, CoordinateTransform transform)
{
foreach (LevelLine line in collection)
{
StreamGeometry lineGeometry = new StreamGeometry();
using (var context = lineGeometry.Open())
{
context.BeginFigure(line.StartPoint.ViewportToScreen(transform), false, false);
if (!UseBezierCurves)
{
context.PolyLineTo(line.OtherPoints.ViewportToScreen(transform).ToArray(), true, true);
}
else
{
context.PolyBezierTo(BezierBuilder.GetBezierPoints(line.AllPoints.ViewportToScreen(transform).ToArray()).Skip(1).ToArray(), true, true);
}
}
lineGeometry.Freeze();
Pen pen = new Pen(new SolidColorBrush(Palette.GetColor(line.Value01)), strokeThickness);
dc.DrawGeometry(null, pen, lineGeometry);
}
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using System.Windows;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
/// <summary>
/// IsolineTextAnnotater defines methods to annotate isolines - create a list of labels with its position.
/// </summary>
public sealed class IsolineTextAnnotater
{
private double wayBeforeText = 10;
/// <summary>
/// Gets or sets the distance between text labels.
/// </summary>
public double WayBeforeText
{
get { return wayBeforeText; }
set { wayBeforeText = value; }
}
/// <summary>
/// Annotates the specified isoline collection.
/// </summary>
/// <param name="collection">The collection.</param>
/// <param name="visible">The visible rectangle.</param>
/// <returns></returns>
public Collection<IsolineTextLabel> Annotate(IsolineCollection collection, DataRect visible)
{
Collection<IsolineTextLabel> res = new Collection<IsolineTextLabel>();
foreach (var line in collection.Lines)
{
double way = 0;
var forwardSegments = line.GetSegments();
var forwardEnumerator = forwardSegments.GetEnumerator();
forwardEnumerator.MoveNext();
foreach (var segment in line.GetSegments())
{
bool hasForwardSegment = forwardEnumerator.MoveNext();
double length = segment.GetLength();
way += length;
if (way > wayBeforeText)
{
way = 0;
var rotation = (segment.Max - segment.Min).ToAngle();
if (hasForwardSegment)
{
var forwardSegment = forwardEnumerator.Current;
rotation = (rotation + (forwardSegment.Max - forwardSegment.Min).ToAngle()) / 2;
}
res.Add(new IsolineTextLabel
{
Value = line.RealValue,
Position = segment.Max,
Rotation = rotation
});
}
}
}
return res;
}
}
}

View File

@@ -0,0 +1,19 @@
<d3:IsolineGraphBase x:Class="Microsoft.Research.DynamicDataDisplay.Charts.IsolineTrackingGraph"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d3="clr-namespace:Microsoft.Research.DynamicDataDisplay.Charts.Isolines"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<d3:IsolineGraphBase.Style>
<Style TargetType="{x:Type d3:IsolineGraphBase}">
<Setter Property="Cursor" Value="Cross"/>
<Setter Property="StrokeThickness" Value="3"/>
</Style>
</d3:IsolineGraphBase.Style>
<Canvas Name="content" Background="Transparent">
<Grid Name="labelGrid" Visibility="Hidden" Panel.ZIndex="1">
<Rectangle RadiusX="10" RadiusY="10" Stroke="DarkGray"
StrokeThickness="1" Fill="#AAFFFFFF"/>
<TextBlock Name="textBlock" Margin="10,5,10,5"/>
</Grid>
</Canvas>
</d3:IsolineGraphBase>

View File

@@ -0,0 +1,255 @@
using System;
using System.Collections.Generic;
using System.Linq;
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 Microsoft.Research.DynamicDataDisplay.Charts.Isolines;
using Microsoft.Research.DynamicDataDisplay;
using Microsoft.Research.DynamicDataDisplay.Common;
namespace Microsoft.Research.DynamicDataDisplay.Charts
{
/// <summary>
/// Draws one isoline line through mouse position.
/// </summary>
public partial class IsolineTrackingGraph : IsolineGraphBase
{
/// <summary>
/// Initializes a new instance of the <see cref="IsolineTrackingGraph"/> class.
/// </summary>
public IsolineTrackingGraph()
{
InitializeComponent();
}
private Style pathStyle = null;
/// <summary>
/// Gets or sets style, applied to line path.
/// </summary>
/// <value>The path style.</value>
public Style PathStyle
{
get { return pathStyle; }
set
{
pathStyle = value;
foreach (var path in addedPaths)
{
path.Style = pathStyle;
}
}
}
Point prevMousePos;
protected override void OnPlotterAttached()
{
UIElement parent = (UIElement)Parent;
parent.MouseMove += parent_MouseMove;
UpdateUIRepresentation();
}
protected override void OnPlotterDetaching()
{
UIElement parent = (UIElement)Parent;
parent.MouseMove -= parent_MouseMove;
}
private void parent_MouseMove(object sender, MouseEventArgs e)
{
Point mousePos = e.GetPosition(this);
if (mousePos != prevMousePos)
{
prevMousePos = mousePos;
UpdateUIRepresentation();
}
}
protected override void UpdateDataSource()
{
IsolineBuilder.DataSource = DataSource;
UpdateUIRepresentation();
}
protected override void OnViewportPropertyChanged(ExtendedPropertyChangedEventArgs e)
{
UpdateUIRepresentation();
}
private readonly List<Path> addedPaths = new List<Path>();
private Vector labelShift = new Vector(3, 3);
private void UpdateUIRepresentation()
{
if (Plotter2D == null)
return;
foreach (var path in addedPaths)
{
content.Children.Remove(path);
}
addedPaths.Clear();
if (DataSource == null)
{
labelGrid.Visibility = Visibility.Hidden;
return;
}
Rect output = Plotter2D.Viewport.Output;
Point mousePos = Mouse.GetPosition(this);
if (!output.Contains(mousePos)) return;
var transform = Plotter2D.Viewport.Transform;
Point visiblePoint = mousePos.ScreenToData(transform);
DataRect visible = Plotter2D.Viewport.Visible;
double isolineLevel;
if (Search(visiblePoint, out isolineLevel))
{
var collection = IsolineBuilder.BuildIsoline(isolineLevel);
string format = "G3";
if (Math.Abs(isolineLevel) < 1000)
format = "F";
textBlock.Text = isolineLevel.ToString(format);
double x = mousePos.X + labelShift.X;
if (x + labelGrid.ActualWidth > output.Right)
x = mousePos.X - labelShift.X - labelGrid.ActualWidth;
double y = mousePos.Y - labelShift.Y - labelGrid.ActualHeight;
if (y < output.Top)
y = mousePos.Y + labelShift.Y;
Canvas.SetLeft(labelGrid, x);
Canvas.SetTop(labelGrid, y);
foreach (LevelLine segment in collection.Lines)
{
StreamGeometry streamGeom = new StreamGeometry();
using (StreamGeometryContext context = streamGeom.Open())
{
Point startPoint = segment.StartPoint.DataToScreen(transform);
var otherPoints = segment.OtherPoints.DataToScreenAsList(transform);
context.BeginFigure(startPoint, false, false);
context.PolyLineTo(otherPoints, true, true);
}
Path path = new Path
{
Stroke = new SolidColorBrush(Palette.GetColor(segment.Value01)),
Data = streamGeom,
Style = pathStyle
};
content.Children.Add(path);
addedPaths.Add(path);
labelGrid.Visibility = Visibility.Visible;
Binding pathBinding = new Binding { Path = new PropertyPath("StrokeThickness"), Source = this };
path.SetBinding(Path.StrokeThicknessProperty, pathBinding);
}
}
else
{
labelGrid.Visibility = Visibility.Hidden;
}
}
int foundI = 0;
int foundJ = 0;
Quad foundQuad = null;
private bool Search(Point pt, out double foundVal)
{
var grid = DataSource.Grid;
foundVal = 0;
int width = DataSource.Width;
int height = DataSource.Height;
bool found = false;
int i = 0, j = 0;
for (i = 0; i < width - 1; i++)
{
for (j = 0; j < height - 1; j++)
{
Quad quad = new Quad(
grid[i, j],
grid[i, j + 1],
grid[i + 1, j + 1],
grid[i + 1, j]);
if (quad.Contains(pt))
{
found = true;
foundQuad = quad;
foundI = i;
foundJ = j;
break;
}
}
if (found) break;
}
if (!found)
{
foundQuad = null;
return false;
}
var data = DataSource.Data;
double x = pt.X;
double y = pt.Y;
Vector A = grid[i, j + 1].ToVector(); // @TODO: in common case add a sorting of points:
Vector B = grid[i + 1, j + 1].ToVector(); // maxA ___K___ B
Vector C = grid[i + 1, j].ToVector(); // | |
Vector D = grid[i, j].ToVector(); // M P N
double a = data[i, j + 1]; // | |
double b = data[i + 1, j + 1]; // В ___L____Сmin
double c = data[i + 1, j];
double d = data[i, j];
Vector K, L;
double k, l;
if (x >= A.X)
k = Interpolate(A, B, a, b, K = new Vector(x, GetY(A, B, x)));
else
k = Interpolate(D, A, d, a, K = new Vector(x, GetY(D, A, x)));
if (x >= C.X)
l = Interpolate(C, B, c, b, L = new Vector(x, GetY(C, B, x)));
else
l = Interpolate(D, C, d, c, L = new Vector(x, GetY(D, C, x)));
foundVal = Interpolate(L, K, l, k, new Vector(x, y));
return !Double.IsNaN(foundVal);
}
private double Interpolate(Vector v0, Vector v1, double u0, double u1, Vector a)
{
Vector l1 = a - v0;
Vector l = v1 - v0;
double res = (u1 - u0) / l.Length * l1.Length + u0;
return res;
}
private double GetY(Vector v0, Vector v1, double x)
{
double res = v0.Y + (v1.Y - v0.Y) / (v1.X - v0.X) * (x - v0.X);
return res;
}
}
}

63
Charts/Isolines/Quad.cs Normal file
View File

@@ -0,0 +1,63 @@
using System;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
using System.Windows;
namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
{
/// <summary>
/// Represents quadrangle; its points are arranged by round in one direction.
/// </summary>
internal sealed class Quad
{
private readonly Point v00;
public Point V00
{
get { return v00; }
}
private readonly Point v01;
public Point V01
{
get { return v01; }
}
private readonly Point v10;
public Point V10
{
get { return v10; }
}
private readonly Point v11;
public Point V11
{
get { return v11; }
}
public Quad(Point v00, Point v01, Point v11, Point v10)
{
DebugVerify.IsNotNaN(v00);
DebugVerify.IsNotNaN(v01);
DebugVerify.IsNotNaN(v11);
DebugVerify.IsNotNaN(v10);
this.v00 = v00;
this.v01 = v01;
this.v10 = v10;
this.v11 = v11;
}
/// <summary>
/// Determines whether this quad contains the specified point.
/// </summary>
/// <param name="v">The point</param>
/// <returns>
/// <c>true</c> if quad contains the specified point; otherwise, <c>false</c>.
/// </returns>
public bool Contains(Point pt)
{
// breaking quad into 2 triangles,
// points contains in quad, if it contains in at least one half-triangle of it.
return TriangleMath.TriangleContains(v00, v01, v11, pt) || TriangleMath.TriangleContains(v00, v10, v11, pt);
}
}
}