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 { /// /// Describes a rectangle in viewport or data coordinates. /// [Serializable] [ValueSerializer(typeof(DataRectSerializer))] [TypeConverter(typeof(DataRectConverter))] public struct DataRect : IEquatable, IFormattable { #region Ctors /// /// Initializes a new instance of the struct. /// /// Source rect. public DataRect(Rect rect) { xMin = rect.X; yMin = rect.Y; width = rect.Width; height = rect.Height; } /// /// Initializes a new instance of the struct. /// /// The size. public DataRect(Size size) { if (size.IsEmpty) { this = emptyRect; } else { xMin = yMin = 0.0; width = size.Width; height = size.Height; } } /// /// Initializes a new instance of the struct. /// /// The location. /// The size. public DataRect(Point location, Size size) { if (size.IsEmpty) { this = emptyRect; } else { xMin = location.X; yMin = location.Y; width = size.Width; height = size.Height; } } /// /// Initializes a new instance of the struct. /// /// The point1. /// The point2. 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); } /// /// Initializes a new instance of the struct. /// /// The point. /// The vector. public DataRect(Point point, Vector vector) : this(point, point + vector) { } /// /// Initializes a new instance of the struct. /// /// The minimal x. /// The minimal y. /// The width. /// The height. 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 /// /// Creates the DataRect from minimal and maximal 'x' and 'y' coordinates. /// /// The x min. /// The y min. /// The x max. /// The y max. /// 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; /// /// Gets a value indicating whether this instance is empty. /// /// true if this instance is empty; otherwise, false. public bool IsEmpty { get { return width < 0; } } /// /// Gets the bottom. /// /// The bottom. public double YMin { get { return yMin; } set { if (this.IsEmpty) throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect); yMin = value; } } /// /// Gets the maximal y value. /// /// The top. public double YMax { get { if (IsEmpty) return Double.PositiveInfinity; return yMin + height; } } /// /// Gets the minimal x value. /// /// The left. public double XMin { get { return xMin; } set { if (this.IsEmpty) throw new InvalidOperationException(Strings.Exceptions.CannotModifyEmptyDataRect); xMin = value; } } /// /// Gets the maximal x value. /// /// The right. public double XMax { get { if (IsEmpty) return Double.PositiveInfinity; return xMin + width; } } /// /// Gets or sets the location. /// /// The location. 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); } } /// /// Gets or sets the size. /// /// The size. 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 /// /// Indicates whether this instance and a specified object are equal. /// /// Another object to compare to. /// /// true if and this instance are the same type and represent the same value; otherwise, false. /// public override bool Equals(object obj) { if (obj == null) return false; if (!(obj is DataRect)) return false; DataRect other = (DataRect)obj; return Equals(other); } /// /// Returns the hash code for this instance. /// /// /// A 32-bit signed integer that is the hash code for this instance. /// public override int GetHashCode() { if (IsEmpty) return 0; return xMin.GetHashCode() ^ width.GetHashCode() ^ yMin.GetHashCode() ^ height.GetHashCode(); } /// /// Returns the fully qualified type name of this instance. /// /// /// A containing a fully qualified type name. /// public override string ToString() { if (IsEmpty) return "Empty"; return String.Format("({0};{1}) -> {2}*{3}", xMin, yMin, width, height); } /// /// Implements the operator ==. /// /// The rect1. /// The rect2. /// The result of the operator. public static bool operator ==(DataRect rect1, DataRect rect2) { return rect1.Equals(rect2); } /// /// Implements the operator !=. /// /// The rect1. /// The rect2. /// The result of the operator. 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 Members /// /// Indicates whether the current object is equal to another object of the same type. /// /// An object to compare with this object. /// /// true if the current object is equal to the parameter; otherwise, false. /// 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 /// /// Determines whether this DataRect contains point with specified coordinates. /// /// The x coordinate of point. /// The y coordinate of point. /// /// true if contains point with specified coordinates; otherwise, false. /// 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); } /// /// Parses the specified string as a DataRect. /// /// /// 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". /// /// The source. /// DataRect, parsed from the given input string. 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 } }