PageRenderTime 51ms CodeModel.GetById 17ms app.highlight 27ms RepoModel.GetById 2ms app.codeStats 0ms

/Main/src/DynamicDataDisplay/Charts/Isolines/IsolineBuilder.cs

#
C# | 724 lines | 569 code | 95 blank | 60 comment | 123 complexity | 087de97e1b4e20f0e9f07763d3523af4 MD5 | raw file
  1using System;
  2using System.Linq;
  3using System.Diagnostics;
  4using System.Runtime.Serialization;
  5using System.Windows;
  6using System.Windows.Media;
  7using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;
  8using Microsoft.Research.DynamicDataDisplay.DataSources;
  9using System.Collections.Generic;
 10
 11namespace Microsoft.Research.DynamicDataDisplay.Charts.Isolines
 12{
 13	/// <summary>
 14	/// Generates geometric object for isolines of the input 2d scalar field.
 15	/// </summary>
 16	public sealed class IsolineBuilder
 17	{
 18		/// <summary>
 19		/// The density of isolines means the number of levels to draw.
 20		/// </summary>
 21		private int density = 12;
 22
 23		private bool[,] processed;
 24
 25		/// <summary>Number to be treated as missing value. NaN if no missing value is specified</summary>
 26		private double missingValue = Double.NaN;
 27
 28		static IsolineBuilder()
 29		{
 30			SetCellDictionaries();
 31		}
 32
 33		/// <summary>
 34		/// Initializes a new instance of the <see cref="IsolineBuilder"/> class.
 35		/// </summary>
 36		public IsolineBuilder() { }
 37
 38		/// <summary>
 39		/// Initializes a new instance of the <see cref="IsolineBuilder"/> class for specified 2d scalar data source.
 40		/// </summary>
 41		/// <param name="dataSource">The data source with 2d scalar data.</param>
 42		public IsolineBuilder(IDataSource2D<double> dataSource)
 43		{
 44			DataSource = dataSource;
 45		}
 46
 47		public double MissingValue
 48		{
 49			get
 50			{
 51				return missingValue;
 52			}
 53			set
 54			{
 55				missingValue = value;
 56			}
 57		}
 58
 59		#region Private methods
 60
 61		private static Dictionary<int, Dictionary<int, Edge>> dictChooser = new Dictionary<int, Dictionary<int, Edge>>();
 62		private static void SetCellDictionaries()
 63		{
 64			var bottomDict = new Dictionary<int, Edge>();
 65			bottomDict.Add((int)CellBitmask.RightBottom, Edge.Right);
 66			bottomDict.Add(Edge.Left,
 67				CellBitmask.LeftTop,
 68				CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop,
 69				CellBitmask.LeftTop | CellBitmask.RightBottom | CellBitmask.RightTop,
 70				CellBitmask.LeftBottom);
 71			bottomDict.Add(Edge.Right,
 72				CellBitmask.RightTop,
 73				CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.LeftTop,
 74				CellBitmask.LeftBottom | CellBitmask.LeftTop | CellBitmask.RightTop);
 75			bottomDict.Add(Edge.Top,
 76				CellBitmask.RightBottom | CellBitmask.RightTop,
 77				CellBitmask.LeftBottom | CellBitmask.LeftTop);
 78
 79			var leftDict = new Dictionary<int, Edge>();
 80			leftDict.Add(Edge.Top,
 81				CellBitmask.LeftTop,
 82				CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop);
 83			leftDict.Add(Edge.Right,
 84				CellBitmask.LeftTop | CellBitmask.RightTop,
 85				CellBitmask.LeftBottom | CellBitmask.RightBottom);
 86			leftDict.Add(Edge.Bottom,
 87				CellBitmask.RightBottom | CellBitmask.RightTop | CellBitmask.LeftTop,
 88				CellBitmask.LeftBottom);
 89
 90			var topDict = new Dictionary<int, Edge>();
 91			topDict.Add(Edge.Right,
 92				CellBitmask.RightTop,
 93				CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightBottom);
 94			topDict.Add(Edge.Right,
 95				CellBitmask.RightBottom,
 96				CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightTop);
 97			topDict.Add(Edge.Left,
 98				CellBitmask.RightBottom | CellBitmask.RightTop | CellBitmask.LeftTop,
 99				CellBitmask.LeftBottom,
100				CellBitmask.LeftTop,
101				CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.RightTop);
102			topDict.Add(Edge.Bottom,
103				CellBitmask.RightBottom | CellBitmask.RightTop,
104				CellBitmask.LeftTop | CellBitmask.LeftBottom);
105
106			var rightDict = new Dictionary<int, Edge>();
107			rightDict.Add(Edge.Top,
108				CellBitmask.RightTop,
109				CellBitmask.LeftBottom | CellBitmask.RightBottom | CellBitmask.LeftTop);
110			rightDict.Add(Edge.Left,
111				CellBitmask.LeftTop | CellBitmask.RightTop,
112				CellBitmask.LeftBottom | CellBitmask.RightBottom);
113			rightDict.Add(Edge.Bottom,
114				CellBitmask.RightBottom,
115				CellBitmask.LeftTop | CellBitmask.LeftBottom | CellBitmask.RightTop);
116
117			dictChooser.Add((int)Edge.Left, leftDict);
118			dictChooser.Add((int)Edge.Right, rightDict);
119			dictChooser.Add((int)Edge.Bottom, bottomDict);
120			dictChooser.Add((int)Edge.Top, topDict);
121		}
122
123		private Edge GetOutEdge(Edge inEdge, ValuesInCell cv, IrregularCell rect, double value)
124		{
125			// value smaller than all values in corners or 
126			// value greater than all values in corners
127			if (!cv.ValueBelongTo(value))
128			{
129				throw new IsolineGenerationException(Strings.Exceptions.IsolinesValueIsOutOfCell);
130			}
131
132			CellBitmask cellVal = cv.GetCellValue(value);
133			var dict = dictChooser[(int)inEdge];
134			if (dict.ContainsKey((int)cellVal))
135			{
136				Edge result = dict[(int)cellVal];
137				switch (result)
138				{
139					case Edge.Left:
140						if (cv.LeftTop.IsNaN() || cv.LeftBottom.IsNaN())
141							result = Edge.None;
142						break;
143					case Edge.Right:
144						if (cv.RightTop.IsNaN() || cv.RightBottom.IsNaN())
145							result = Edge.None;
146						break;
147					case Edge.Top:
148						if (cv.RightTop.IsNaN() || cv.LeftTop.IsNaN())
149							result = Edge.None;
150						break;
151					case Edge.Bottom:
152						if (cv.LeftBottom.IsNaN() || cv.RightBottom.IsNaN())
153							result = Edge.None;
154						break;
155				}
156				return result;
157			}
158			else if (cellVal.IsDiagonal())
159			{
160				return GetOutForOpposite(inEdge, cellVal, value, cv, rect);
161			}
162
163			const double near_zero = 0.0001;
164			const double near_one = 1 - near_zero;
165
166			double lt = cv.LeftTop;
167			double rt = cv.RightTop;
168			double rb = cv.RightBottom;
169			double lb = cv.LeftBottom;
170
171			switch (inEdge)
172			{
173				case Edge.Left:
174					if (value == lt)
175						value = near_one * lt + near_zero * lb;
176					else if (value == lb)
177						value = near_one * lb + near_zero * lt;
178					else
179						return Edge.None;
180					// Now this is possible because of missing value
181					//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
182					break;
183				case Edge.Top:
184					if (value == rt)
185						value = near_one * rt + near_zero * lt;
186					else if (value == lt)
187						value = near_one * lt + near_zero * rt;
188					else
189						return Edge.None;
190					// Now this is possibe because of missing value
191					//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
192					break;
193				case Edge.Right:
194					if (value == rb)
195						value = near_one * rb + near_zero * rt;
196					else if (value == rt)
197						value = near_one * rt + near_zero * rb;
198					else
199						return Edge.None;
200					// Now this is possibe because of missing value
201					//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
202					break;
203				case Edge.Bottom:
204					if (value == rb)
205						value = near_one * rb + near_zero * lb;
206					else if (value == lb)
207						value = near_one * lb + near_zero * rb;
208					else
209						return Edge.None;
210					// Now this is possibe because of missing value
211					//throw new IsolineGenerationException(Strings.Exceptions.IsolinesUnsupportedCase);
212					break;
213			}
214
215			// Recursion?
216			//return GetOutEdge(inEdge, cv, rect, value);
217
218			return Edge.None;
219		}
220
221		private Edge GetOutForOpposite(Edge inEdge, CellBitmask cellVal, double value, ValuesInCell cellValues, IrregularCell rect)
222		{
223			Edge outEdge;
224
225			SubCell subCell = GetSubCell(inEdge, value, cellValues);
226
227			int iters = 1000; // max number of iterations
228			do
229			{
230				ValuesInCell subValues = cellValues.GetSubCell(subCell);
231				IrregularCell subRect = rect.GetSubRect(subCell);
232				outEdge = GetOutEdge(inEdge, subValues, subRect, value);
233				if (outEdge == Edge.None)
234					return Edge.None;
235				bool isAppropriate = subCell.IsAppropriate(outEdge);
236				if (isAppropriate)
237				{
238					ValuesInCell sValues = subValues.GetSubCell(subCell);
239
240					Point point = GetPointXY(outEdge, value, subValues, subRect);
241					segments.AddPoint(point);
242					return outEdge;
243				}
244				else
245				{
246					subCell = GetAdjacentEdge(subCell, outEdge);
247				}
248
249				byte e = (byte)outEdge;
250				inEdge = (Edge)((e > 2) ? (e >> 2) : (e << 2));
251				iters--;
252			} while (iters >= 0);
253
254			throw new IsolineGenerationException(Strings.Exceptions.IsolinesDataIsUndetailized);
255		}
256
257		private static SubCell GetAdjacentEdge(SubCell sub, Edge edge)
258		{
259			SubCell res = SubCell.LeftBottom;
260
261			switch (sub)
262			{
263				case SubCell.LeftBottom:
264					res = edge == Edge.Top ? SubCell.LeftTop : SubCell.RightBottom;
265					break;
266				case SubCell.LeftTop:
267					res = edge == Edge.Bottom ? SubCell.LeftBottom : SubCell.RightTop;
268					break;
269				case SubCell.RightBottom:
270					res = edge == Edge.Top ? SubCell.RightTop : SubCell.LeftBottom;
271					break;
272				case SubCell.RightTop:
273				default:
274					res = edge == Edge.Bottom ? SubCell.RightBottom : SubCell.LeftTop;
275					break;
276			}
277
278			return res;
279		}
280
281		private static SubCell GetSubCell(Edge inEdge, double value, ValuesInCell vc)
282		{
283			double lb = vc.LeftBottom;
284			double rb = vc.RightBottom;
285			double rt = vc.RightTop;
286			double lt = vc.LeftTop;
287
288			SubCell res = SubCell.LeftBottom;
289			switch (inEdge)
290			{
291				case Edge.Left:
292					res = (Math.Abs(value - lb) < Math.Abs(value - lt)) ? SubCell.LeftBottom : SubCell.LeftTop;
293					break;
294				case Edge.Top:
295					res = (Math.Abs(value - lt) < Math.Abs(value - rt)) ? SubCell.LeftTop : SubCell.RightTop;
296					break;
297				case Edge.Right:
298					res = (Math.Abs(value - rb) < Math.Abs(value - rt)) ? SubCell.RightBottom : SubCell.RightTop;
299					break;
300				case Edge.Bottom:
301				default:
302					res = (Math.Abs(value - lb) < Math.Abs(value - rb)) ? SubCell.LeftBottom : SubCell.RightBottom;
303					break;
304			}
305
306			ValuesInCell subValues = vc.GetSubCell(res);
307			bool valueInside = subValues.ValueBelongTo(value);
308			if (!valueInside)
309			{
310				throw new IsolineGenerationException(Strings.Exceptions.IsolinesDataIsUndetailized);
311			}
312
313			return res;
314		}
315
316		private static Point GetPoint(double value, double a1, double a2, Vector v1, Vector v2)
317		{
318			double ratio = (value - a1) / (a2 - a1);
319
320			Verify.IsTrue(0 <= ratio && ratio <= 1);
321
322			Vector r = (1 - ratio) * v1 + ratio * v2;
323			return new Point(r.X, r.Y);
324		}
325
326		private Point GetPointXY(Edge edge, double value, ValuesInCell vc, IrregularCell rect)
327		{
328			double lt = vc.LeftTop;
329			double lb = vc.LeftBottom;
330			double rb = vc.RightBottom;
331			double rt = vc.RightTop;
332
333			switch (edge)
334			{
335				case Edge.Left:
336					return GetPoint(value, lb, lt, rect.LeftBottom, rect.LeftTop);
337				case Edge.Top:
338					return GetPoint(value, lt, rt, rect.LeftTop, rect.RightTop);
339				case Edge.Right:
340					return GetPoint(value, rb, rt, rect.RightBottom, rect.RightTop);
341				case Edge.Bottom:
342					return GetPoint(value, lb, rb, rect.LeftBottom, rect.RightBottom);
343				default:
344					throw new InvalidOperationException();
345			}
346		}
347
348		private bool BelongsToEdge(double value, double edgeValue1, double edgeValue2, bool onBoundary)
349		{
350			if (!Double.IsNaN(missingValue) && (edgeValue1 == missingValue || edgeValue2 == missingValue))
351				return false;
352
353			if (onBoundary)
354			{
355				return (edgeValue1 <= value && value < edgeValue2) ||
356				(edgeValue2 <= value && value < edgeValue1);
357			}
358			else
359			{
360				return (edgeValue1 < value && value < edgeValue2) ||
361					(edgeValue2 < value && value < edgeValue1);
362			}
363		}
364
365		private bool IsPassed(Edge edge, int i, int j, byte[,] edges)
366		{
367			switch (edge)
368			{
369				case Edge.Left:
370					return (i == 0) || (edges[i, j] & (byte)edge) != 0;
371				case Edge.Bottom:
372					return (j == 0) || (edges[i, j] & (byte)edge) != 0;
373				case Edge.Top:
374					return (j == edges.GetLength(1) - 2) || (edges[i, j + 1] & (byte)Edge.Bottom) != 0;
375				case Edge.Right:
376					return (i == edges.GetLength(0) - 2) || (edges[i + 1, j] & (byte)Edge.Left) != 0;
377				default:
378					throw new InvalidOperationException();
379			}
380		}
381
382		private void MakeEdgePassed(Edge edge, int i, int j)
383		{
384			switch (edge)
385			{
386				case Edge.Left:
387				case Edge.Bottom:
388					edges[i, j] |= (byte)edge;
389					break;
390				case Edge.Top:
391					edges[i, j + 1] |= (byte)Edge.Bottom;
392					break;
393				case Edge.Right:
394					edges[i + 1, j] |= (byte)Edge.Left;
395					break;
396				default:
397					throw new InvalidOperationException();
398			}
399		}
400
401		private Edge TrackLine(Edge inEdge, double value, ref int x, ref int y, out double newX, out double newY)
402		{
403			// Getting output edge
404			ValuesInCell vc = (missingValue.IsNaN()) ?
405				(new ValuesInCell(values[x, y],
406					values[x + 1, y],
407					values[x + 1, y + 1],
408					values[x, y + 1])) :
409				(new ValuesInCell(values[x, y],
410					values[x + 1, y],
411					values[x + 1, y + 1],
412					values[x, y + 1],
413					missingValue));
414
415			IrregularCell rect = new IrregularCell(
416				grid[x, y],
417				grid[x + 1, y],
418				grid[x + 1, y + 1],
419				grid[x, y + 1]);
420
421			Edge outEdge = GetOutEdge(inEdge, vc, rect, value);
422			if (outEdge == Edge.None)
423			{
424				newX = newY = -1; // Impossible cell indices
425				return Edge.None;
426			}
427
428			// Drawing new segment
429			Point point = GetPointXY(outEdge, value, vc, rect);
430			newX = point.X;
431			newY = point.Y;
432			segments.AddPoint(point);
433			processed[x, y] = true;
434
435			// Whether out-edge already was passed?
436			if (IsPassed(outEdge, x, y, edges)) // line is closed
437			{
438				//MakeEdgePassed(outEdge, x, y); // boundaries should be marked as passed too
439				return Edge.None;
440			}
441
442			// Make this edge passed
443			MakeEdgePassed(outEdge, x, y);
444
445			// Getting next cell's indices
446			switch (outEdge)
447			{
448				case Edge.Left:
449					x--;
450					return Edge.Right;
451				case Edge.Top:
452					y++;
453					return Edge.Bottom;
454				case Edge.Right:
455					x++;
456					return Edge.Left;
457				case Edge.Bottom:
458					y--;
459					return Edge.Top;
460				default:
461					throw new InvalidOperationException();
462			}
463		}
464
465		private void TrackLineNonRecursive(Edge inEdge, double value, int x, int y)
466		{
467			int s = x, t = y;
468
469			ValuesInCell vc = (missingValue.IsNaN()) ?
470				(new ValuesInCell(values[x, y],
471					values[x + 1, y],
472					values[x + 1, y + 1],
473					values[x, y + 1])) :
474				(new ValuesInCell(values[x, y],
475					values[x + 1, y],
476					values[x + 1, y + 1],
477					values[x, y + 1],
478					missingValue));
479
480			IrregularCell rect = new IrregularCell(
481				grid[x, y],
482				grid[x + 1, y],
483				grid[x + 1, y + 1],
484				grid[x, y + 1]);
485
486			Point point = GetPointXY(inEdge, value, vc, rect);
487
488			segments.StartLine(point, (value - minMax.Min) / (minMax.Max - minMax.Min), value);
489
490			MakeEdgePassed(inEdge, x, y);
491
492			//processed[x, y] = true;
493
494			double x2, y2;
495			do
496			{
497				inEdge = TrackLine(inEdge, value, ref s, ref t, out x2, out y2);
498			} while (inEdge != Edge.None);
499		}
500
501		#endregion
502
503		private bool HasIsoline(int x, int y)
504		{
505			return (edges[x, y] != 0 &&
506				((x < edges.GetLength(0) - 1 && edges[x + 1, y] != 0) ||
507				 (y < edges.GetLength(1) - 1 && edges[x, y + 1] != 0)));
508		}
509
510		/// <summary>Finds isoline for specified reference value</summary>
511		/// <param name="value">Reference value</param>
512		private void PrepareCells(double value)
513		{
514			double currentRatio = (value - minMax.Min) / (minMax.Max - minMax.Min);
515
516			if (currentRatio < 0 || currentRatio > 1)
517				return; // No contour lines for such value
518
519			int xSize = dataSource.Width;
520			int ySize = dataSource.Height;
521			int x, y;
522			for (x = 0; x < xSize; x++)
523				for (y = 0; y < ySize; y++)
524					edges[x, y] = 0;
525
526			processed = new bool[xSize, ySize];
527
528			// Looking in boundaries.
529			// left
530			for (y = 1; y < ySize; y++)
531			{
532				if (BelongsToEdge(value, values[0, y - 1], values[0, y], true) &&
533					(edges[0, y - 1] & (byte)Edge.Left) == 0)
534				{
535					TrackLineNonRecursive(Edge.Left, value, 0, y - 1);
536				}
537			}
538
539			// bottom
540			for (x = 0; x < xSize - 1; x++)
541			{
542				if (BelongsToEdge(value, values[x, 0], values[x + 1, 0], true)
543					&& (edges[x, 0] & (byte)Edge.Bottom) == 0)
544				{
545					TrackLineNonRecursive(Edge.Bottom, value, x, 0);
546				};
547			}
548
549			// right
550			x = xSize - 1;
551			for (y = 1; y < ySize; y++)
552			{
553				// Is this correct?
554				//if (BelongsToEdge(value, values[0, y - 1], values[0, y], true) &&
555				//    (edges[0, y - 1] & (byte)Edge.Left) == 0)
556				//{
557				//    TrackLineNonRecursive(Edge.Left, value, 0, y - 1);
558				//};
559
560				if (BelongsToEdge(value, values[x, y - 1], values[x, y], true) &&
561					(edges[x, y - 1] & (byte)Edge.Left) == 0)
562				{
563					TrackLineNonRecursive(Edge.Right, value, x - 1, y - 1);
564				};
565			}
566
567			// horizontals
568			for (x = 1; x < xSize - 1; x++)
569				for (y = 1; y < ySize - 1; y++)
570				{
571					if ((edges[x, y] & (byte)Edge.Bottom) == 0 &&
572						BelongsToEdge(value, values[x, y], values[x + 1, y], false) &&
573						!processed[x, y - 1])
574					{
575						TrackLineNonRecursive(Edge.Top, value, x, y - 1);
576					}
577					if ((edges[x, y] & (byte)Edge.Bottom) == 0 &&
578						BelongsToEdge(value, values[x, y], values[x + 1, y], false) &&
579						!processed[x, y])
580					{
581						TrackLineNonRecursive(Edge.Bottom, value, x, y);
582					}
583					if ((edges[x, y] & (byte)Edge.Left) == 0 &&
584						BelongsToEdge(value, values[x, y], values[x, y - 1], false) &&
585						!processed[x - 1, y - 1])
586					{
587						TrackLineNonRecursive(Edge.Right, value, x - 1, y - 1);
588					}
589					if ((edges[x, y] & (byte)Edge.Left) == 0 &&
590						BelongsToEdge(value, values[x, y], values[x, y - 1], false) &&
591						!processed[x, y - 1])
592					{
593						TrackLineNonRecursive(Edge.Left, value, x, y - 1);
594					}
595				}
596		}
597
598		/// <summary>
599		/// Builds isoline data for 2d scalar field contained in data source.
600		/// </summary>
601		/// <returns>Collection of data describing built isolines.</returns>
602		public IsolineCollection BuildIsoline()
603		{
604			VerifyDataSource();
605
606			segments = new IsolineCollection();
607
608			// Cannot draw isolines for fields with one dimension lesser than 2
609			if (dataSource.Width < 2 || dataSource.Height < 2)
610				return segments;
611
612			Init();
613
614			if (!minMax.IsEmpty)
615			{
616				values = dataSource.Data;
617				double[] levels = GetLevelsForIsolines();
618
619				foreach (double level in levels)
620				{
621					PrepareCells(level);
622				}
623
624				if (segments.Lines.Count > 0 && segments.Lines[segments.Lines.Count - 1].OtherPoints.Count == 0)
625					segments.Lines.RemoveAt(segments.Lines.Count - 1);
626			}
627			return segments;
628		}
629
630		private void Init()
631		{
632			if (dataSource.Range.HasValue)
633				minMax = dataSource.Range.Value;
634			else
635				minMax = (Double.IsNaN(missingValue) ? dataSource.GetMinMax() : dataSource.GetMinMax(missingValue));
636
637			if (dataSource.MissingValue.HasValue)
638				missingValue = dataSource.MissingValue.Value;
639
640			segments.Min = minMax.Min;
641			segments.Max = minMax.Max;
642		}
643
644		/// <summary>
645		/// Builds isoline data for the specified level in 2d scalar field.
646		/// </summary>
647		/// <param name="level">The level.</param>
648		/// <returns></returns>
649		public IsolineCollection BuildIsoline(double level)
650		{
651			VerifyDataSource();
652
653			segments = new IsolineCollection();
654
655			Init();
656			
657			if (!minMax.IsEmpty)
658			{
659				values = dataSource.Data;
660
661
662				PrepareCells(level);
663
664				if (segments.Lines.Count > 0 && segments.Lines[segments.Lines.Count - 1].OtherPoints.Count == 0)
665					segments.Lines.RemoveAt(segments.Lines.Count - 1);
666			}
667			return segments;
668		}
669
670		private void VerifyDataSource()
671		{
672			if (dataSource == null)
673				throw new InvalidOperationException(Strings.Exceptions.IsolinesDataSourceShouldBeSet);
674		}
675
676		IsolineCollection segments;
677
678		private double[,] values;
679		private byte[,] edges;
680		private Point[,] grid;
681
682		private Range<double> minMax;
683		private IDataSource2D<double> dataSource;
684		/// <summary>
685		/// Gets or sets the data source - 2d scalar field.
686		/// </summary>
687		/// <value>The data source.</value>
688		public IDataSource2D<double> DataSource
689		{
690			get { return dataSource; }
691			set
692			{
693				if (dataSource != value)
694				{
695					value.VerifyNotNull("value");
696
697					dataSource = value;
698					grid = dataSource.Grid;
699					edges = new byte[dataSource.Width, dataSource.Height];
700				}
701			}
702		}
703
704		private const double shiftPercent = 0.05;
705		private double[] GetLevelsForIsolines()
706		{
707			double[] levels;
708			double min = minMax.Min;
709			double max = minMax.Max;
710
711			double step = (max - min) / (density - 1);
712			double delta = (max - min);
713
714			levels = new double[density];
715			levels[0] = min + delta * shiftPercent;
716			levels[levels.Length - 1] = max - delta * shiftPercent;
717
718			for (int i = 1; i < levels.Length - 1; i++)
719				levels[i] = min + i * step;
720
721			return levels;
722		}
723	}
724}