1 //-------------------------------------------------------------
2 // <copyright company=�Microsoft Corporation�>
3 //   Copyright � Microsoft Corporation. All Rights Reserved.
4 // </copyright>
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 //  File:		AxisScaleSegments.cs
9 //
10 //  Namespace:	System.Web.UI.WebControls[Windows.Forms].Charting
11 //
12 //	Classes:	AxisScaleSegment, AxisScaleSegmentCollection
13 //
14 //  Purpose:
15 //
16 //	Reviewed:
17 //
18 //===================================================================
19 
20 #region Used namespaces
21 
22 using System;
23 using System.Collections;
24 using System.Collections.Specialized;
25 using System.ComponentModel;
26 using System.ComponentModel.Design;
27 using System.Data;
28 using System.Drawing;
29 using System.Drawing.Design;
30 using System.Drawing.Drawing2D;
31 using System.Globalization;
32 #if Microsoft_CONTROL
33 
34 	using System.Windows.Forms.DataVisualization.Charting.Data;
35 	using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
36 	using System.Windows.Forms.DataVisualization.Charting.Utilities;
37 	using System.Windows.Forms.DataVisualization.Charting.Borders3D;
38 	using System.Windows.Forms.DataVisualization.Charting;
39 
40 #else
41 	using System.Web;
42 	using System.Web.UI;
43 	using System.Web.UI.DataVisualization.Charting;
44 	using System.Web.UI.DataVisualization.Charting.Data;
45 	using System.Web.UI.DataVisualization.Charting.ChartTypes;
46 	using System.Web.UI.DataVisualization.Charting.Utilities;
47 #endif
48 
49 #endregion
50 
51 #if Microsoft_CONTROL
52 	namespace System.Windows.Forms.DataVisualization.Charting
53 #else
54 namespace System.Web.UI.DataVisualization.Charting
55 
56 #endif
57 {
58 	/// <summary>
59 	/// <b>AxisScaleSegment</b> class represents a single segment of the axis with
60 	/// it's own scale and intervals.
61 	/// </summary>
62 	[
63 	SRDescription("DescriptionAttributeAxisScaleSegment_AxisScaleSegment"),
64 	]
65      internal class AxisScaleSegment
66 	{
67 		#region Fields
68 
69 		// Associated axis
70 		internal Axis axis = null;
71 
72 		// Axis segment position in percent of the axis size
73 		private double _position = 0.0;
74 
75 		// Axis segment size in percent of the axis size
76 		private double _size = 0.0;
77 
78 		// Axis segment spacing in percent of the axis size
79 		private double _spacing = 0.0;
80 
81 		// Axis segment scale minimum value
82 		private double _scaleMinimum = 0.0;
83 
84 		// Axis segment scale maximum value
85 		private double _scaleMaximum = 0.0;
86 
87 		// Axis segment interval offset.
88 		private double _intervalOffset = 0;
89 
90 		// Axis segment interval.
91 		private double _interval = 0;
92 
93 		// Axis segment interval units type.
94 		private DateTimeIntervalType _intervalType = DateTimeIntervalType.Auto;
95 
96 		// Axis segment interval offset units type.
97 		private DateTimeIntervalType _intervalOffsetType = DateTimeIntervalType.Auto;
98 
99 		// Object associated with the segment
100 		private object _tag = null;
101 
102 		// Stack used to save/load axis settings
103 		private Stack	_oldAxisSettings = new Stack();
104 
105 		#endregion // Fields
106 
107 		#region Constructor
108 
109 		/// <summary>
110 		/// Default object constructor.
111 		/// </summary>
AxisScaleSegment()112 		public AxisScaleSegment()
113 		{
114 		}
115 
116 		#endregion // Constructor
117 
118 		#region Properties
119 
120 		/// <summary>
121 		/// Axis segment position in axis size percentage.
122 		/// </summary>
123 		[
124 		SRCategory("CategoryAttributeMisc"),
125 		DefaultValue(0.0),
126 		SRDescription("DescriptionAttributeAxisScaleSegment_Position"),
127 		]
128 		public double Position
129 		{
130 			get
131 			{
132 				return this._position;
133 			}
134 			set
135 			{
136 				if(value < 0.0 || value > 100.0)
137 				{
138                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsPositionInvalid));
139 				}
140 				this._position = value;
141 			}
142 		}
143 
144 		/// <summary>
145 		/// Axis segment size in axis size percentage.
146 		/// </summary>
147 		[
148 		SRCategory("CategoryAttributeMisc"),
149 		DefaultValue(0.0),
150 		SRDescription("DescriptionAttributeAxisScaleSegment_Size"),
151 		]
152 		public double Size
153 		{
154 			get
155 			{
156 				return this._size;
157 			}
158 			set
159 			{
160 				if(value < 0.0 || value > 100.0)
161 				{
162                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsSizeInvalid));
163 					}
164 				this._size = value;
165 			}
166 		}
167 
168 		/// <summary>
169 		/// Axis segment spacing in axis size percentage.
170 		/// </summary>
171 		[
172 		SRCategory("CategoryAttributeMisc"),
173 		DefaultValue(0.0),
174 		SRDescription("DescriptionAttributeAxisScaleSegment_Spacing"),
175 		]
176 		public double Spacing
177 		{
178 			get
179 			{
180 				return this._spacing;
181 			}
182 			set
183 			{
184 				if(value < 0.0 || value > 100.0)
185 				{
186                     throw (new ArgumentOutOfRangeException("value", SR.ExceptionAxisScaleSegmentsSpacingInvalid));
187 				}
188 				this._spacing = value;
189 			}
190 		}
191 
192 		/// <summary>
193 		/// Axis segment scale maximum value.
194 		/// </summary>
195 		[
196 		SRCategory("CategoryAttributeMisc"),
197 		DefaultValue(0.0),
198 		SRDescription("DescriptionAttributeAxisScaleSegment_ScaleMaximum"),
199 		]
200 		public double ScaleMaximum
201 		{
202 			get
203 			{
204 				return this._scaleMaximum;
205 			}
206 			set
207 			{
208 				this._scaleMaximum = value;
209 			}
210 		}
211 
212 		/// <summary>
213 		/// Axis segment scale minimum value.
214 		/// </summary>
215 		[
216 		SRCategory("CategoryAttributeMisc"),
217 		DefaultValue(0.0),
218 		SRDescription("DescriptionAttributeAxisScaleSegment_ScaleMinimum"),
219 		]
220 		public double ScaleMinimum
221 		{
222 			get
223 			{
224 				return this._scaleMinimum;
225 			}
226 			set
227 			{
228 				this._scaleMinimum = value;
229 			}
230 		}
231 
232 
233 		/// <summary>
234 		/// Axis segment interval size.
235 		/// </summary>
236 		[
237 		SRCategory("CategoryAttributeInterval"),
238 		DefaultValue(0.0),
239 		SRDescription("DescriptionAttributeAxisScaleSegment_Interval"),
240 		TypeConverter(typeof(AxisIntervalValueConverter)),
241 		]
242 		public double Interval
243 		{
244 			get
245 			{
246 				return this._interval;
247 			}
248 			set
249 			{
250 				// Axis interval properties must be set
251 				if(double.IsNaN(value))
252 				{
253 					this._interval = 0;
254 				}
255 				else
256 				{
257 					this._interval = value;
258 				}
259 			}
260 		}
261 
262 		/// <summary>
263 		/// Axis segment interval offset.
264 		/// </summary>
265 		[
266 		SRCategory("CategoryAttributeInterval"),
267 		DefaultValue(0.0),
268 		SRDescription("DescriptionAttributeAxisScaleSegment_IntervalOffset"),
269 		TypeConverter(typeof(AxisIntervalValueConverter))
270 		]
271 		public double IntervalOffset
272 		{
273 			get
274 			{
275 				return _intervalOffset;
276 			}
277 		}
278 
279 		/// <summary>
280 		/// Axis segment interval type.
281 		/// </summary>
282 		[
283 		SRCategory("CategoryAttributeInterval"),
284 		DefaultValue(DateTimeIntervalType.Auto),
285 		SRDescription("DescriptionAttributeAxisScaleSegment_IntervalType"),
286 		]
287 		public DateTimeIntervalType IntervalType
288 		{
289 			get
290 			{
291 				return this._intervalType;
292 			}
293 			set
294 			{
295 				// Axis interval properties must be set
296 				if(value == DateTimeIntervalType.NotSet)
297 				{
298 					this._intervalType = DateTimeIntervalType.Auto;
299 				}
300 				else
301 				{
302 					_intervalType = value;
303 				}
304 			}
305 		}
306 
307 		/// <summary>
308 		/// Axis segment interval offset type.
309 		/// </summary>
310 		[
311 		SRCategory("CategoryAttributeInterval"),
312 		DefaultValue(DateTimeIntervalType.Auto),
313 		SRDescription("DescriptionAttributeAxisScaleSegment_IntervalOffsetType"),
314 		]
315 		public DateTimeIntervalType IntervalOffsetType
316 		{
317 			get
318 			{
319 				return this._intervalOffsetType;
320 			}
321 		}
322 
323 		/// <summary>
324 		/// Object associated with axis scale segment.
325 		/// </summary>
326 		[
327 		SRCategory("CategoryAttributeMisc"),
328 		Browsable(false),
329 		DefaultValue(null),
330 		SRDescription("DescriptionAttributeAxisScaleSegment_Tag"),
331 		DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden),
332 		SerializationVisibility(SerializationVisibility.Hidden),
333 		]
334 		public object Tag
335 		{
336 			get
337 			{
338 				return this._tag;
339 			}
340 			set
341 			{
342 				this._tag = value;
343 			}
344 		}
345 
346 		#endregion // Properties
347 
348 		#region Break Line Painting Methods
349 
350         /// <summary>
351         /// Paints the axis break line.
352         /// </summary>
353         /// <param name="graph">Chart graphics to use.</param>
354         /// <param name="nextSegment">Axis scale segment next to current.</param>
PaintBreakLine(ChartGraphics graph, AxisScaleSegment nextSegment)355 		internal void PaintBreakLine(ChartGraphics graph, AxisScaleSegment nextSegment)
356 		{
357 			// Get break line position
358 			RectangleF breakPosition = this.GetBreakLinePosition(graph, nextSegment);
359 
360 			// Get top line graphics path
361 			GraphicsPath breakLinePathTop = this.GetBreakLinePath(breakPosition, true);
362 			GraphicsPath breakLinePathBottom = null;
363 
364 			// Clear break line space using chart color behind the area
365 			if(breakPosition.Width > 0f && breakPosition.Height > 0f)
366 			{
367 				// Get bottom line graphics path
368 				breakLinePathBottom = this.GetBreakLinePath(breakPosition, false);
369 
370 				// Clear plotting area background
371 				using(GraphicsPath fillPath = new GraphicsPath())
372 				{
373 					// Create fill path out of top and bottom break lines
374 					fillPath.AddPath(breakLinePathTop, true);
375 					fillPath.Reverse();
376 					fillPath.AddPath(breakLinePathBottom, true);
377 					fillPath.CloseAllFigures();
378 
379 					// Use chart back color to fill the area
380 					using(Brush fillBrush = this.GetChartFillBrush(graph))
381 					{
382 						graph.FillPath(fillBrush, fillPath);
383 
384 						// Check if shadow exsits in chart area
385 						if( this.axis.ChartArea.ShadowOffset != 0 && !this.axis.ChartArea.ShadowColor.IsEmpty)
386 						{
387 							// Clear shadow
388 							RectangleF shadowPartRect = breakPosition;
389 							if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
390 							{
391 								shadowPartRect.Y += this.axis.ChartArea.ShadowOffset;
392 								shadowPartRect.Height -= this.axis.ChartArea.ShadowOffset;
393 								shadowPartRect.X = shadowPartRect.Right - 1;
394 								shadowPartRect.Width = this.axis.ChartArea.ShadowOffset + 2;
395 							}
396 							else
397 							{
398 								shadowPartRect.X += this.axis.ChartArea.ShadowOffset;
399 								shadowPartRect.Width -= this.axis.ChartArea.ShadowOffset;
400 								shadowPartRect.Y = shadowPartRect.Bottom - 1;
401 								shadowPartRect.Height = this.axis.ChartArea.ShadowOffset + 2;
402 							}
403 							graph.FillRectangle(fillBrush, shadowPartRect);
404 
405 							// Draw new shadow
406 							using(GraphicsPath shadowPath = new GraphicsPath())
407 							{
408 								shadowPath.AddPath(breakLinePathTop, false);
409 
410 								// Define maximum size
411 								float size = this.axis.ChartArea.ShadowOffset;
412 								if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
413 								{
414 									size = Math.Min(size, breakPosition.Height);
415 								}
416 								else
417 								{
418 									size = Math.Min(size, breakPosition.Width);
419 								}
420 
421 								// Define step to increase transperancy
422 								int transparencyStep = (int)(this.axis.ChartArea.ShadowColor.A / size);
423 
424 								// Set clip region to achieve spacing of the shadow
425 								// Start with the plotting rectangle position
426 								RectangleF clipRegion = graph.GetAbsoluteRectangle(this.axis.PlotAreaPosition.ToRectangleF());
427 								if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
428 								{
429 									clipRegion.X += this.axis.ChartArea.ShadowOffset;
430 									clipRegion.Width += this.axis.ChartArea.ShadowOffset;
431 								}
432 								else
433 								{
434 									clipRegion.Y += this.axis.ChartArea.ShadowOffset;
435 									clipRegion.Height += this.axis.ChartArea.ShadowOffset;
436 								}
437 								graph.SetClip(graph.GetRelativeRectangle(clipRegion));
438 
439 								// Draw several lines to form shadow
440 								for(int index = 0; index < size; index ++)
441 								{
442 									using(Matrix newMatrix = new Matrix())
443 									{
444 										// Shift top break line by 1 pixel
445 										if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
446 										{
447 											newMatrix.Translate(0f, 1f);
448 										}
449 										else
450 										{
451 											newMatrix.Translate(1f, 0f);
452 										}
453 										shadowPath.Transform(newMatrix);
454 									}
455 
456 									// Get line color
457 									Color color = Color.FromArgb(
458 										this.axis.ChartArea.ShadowColor.A - transparencyStep * index,
459 										this.axis.ChartArea.ShadowColor);
460 
461 									using(Pen shadowPen = new Pen(color, 1))
462 									{
463 										// Draw shadow
464 										graph.DrawPath(shadowPen, shadowPath);
465 									}
466 								}
467 
468 								graph.ResetClip();
469 							}
470 						}
471 					}
472 				}
473 			}
474 
475 			// Draw Separator Line(s)
476 			if(this.axis.ScaleBreakStyle.BreakLineStyle != BreakLineStyle.None)
477 			{
478 				using(Pen pen = new Pen(this.axis.ScaleBreakStyle.LineColor, this.axis.ScaleBreakStyle.LineWidth))
479 				{
480 					// Set line style
481 					pen.DashStyle = graph.GetPenStyle(this.axis.ScaleBreakStyle.LineDashStyle);
482 
483 					// Draw break lines
484 					graph.DrawPath(pen, breakLinePathTop);
485 					if(breakPosition.Width > 0f && breakPosition.Height > 0f)
486 					{
487 						graph.DrawPath(pen, breakLinePathBottom);
488 					}
489 				}
490 			}
491 
492 			// Dispose break line paths
493 			breakLinePathTop.Dispose();
494 			breakLinePathTop = null;
495 			if(breakLinePathBottom != null)
496 			{
497 				breakLinePathBottom.Dispose();
498 				breakLinePathBottom = null;
499 			}
500 
501 		}
502 
503 		/// <summary>
504 		/// Get fill brush used to fill axis break lines spacing.
505 		/// </summary>
506 		/// <param name="graph">chart graphics.</param>
507 		/// <returns>Fill brush.</returns>
GetChartFillBrush(ChartGraphics graph)508 		private Brush GetChartFillBrush(ChartGraphics graph)
509 		{
510 			Chart chart = this.axis.ChartArea.Common.Chart;
511 			Brush brush = null;
512 
513 			if( chart.BackGradientStyle == GradientStyle.None )
514 			{
515 				brush = new SolidBrush(chart.BackColor);
516 			}
517 			else
518 			{
519 				// If a gradient type is set create a brush with gradient
520                 brush = graph.GetGradientBrush(new RectangleF(0, 0, chart.chartPicture.Width - 1, chart.chartPicture.Height - 1), chart.BackColor, chart.BackSecondaryColor, chart.BackGradientStyle);
521 			}
522 
523 			if( chart.BackHatchStyle != ChartHatchStyle.None )
524 			{
525 				brush = graph.GetHatchBrush( chart.BackHatchStyle, chart.BackColor, chart.BackSecondaryColor );
526 			}
527 
528 			if( chart.BackImage.Length > 0 &&
529 				chart.BackImageWrapMode != ChartImageWrapMode.Unscaled &&
530 				chart.BackImageWrapMode != ChartImageWrapMode.Scaled)
531 			{
532 				brush = graph.GetTextureBrush(chart.BackImage, chart.BackImageTransparentColor, chart.BackImageWrapMode, chart.BackColor );
533 			}
534 
535 			return brush;
536 		}
537 
538 		/// <summary>
539 		/// Gets a path of the break line for specified position.
540 		/// </summary>
541 		/// <param name="breakLinePosition">Break line position.</param>
542 		/// <param name="top">Indicates if top or bottom break line path should be retrieved.</param>
543 		/// <returns>Graphics path with break line path.</returns>
GetBreakLinePath(RectangleF breakLinePosition, bool top)544 		private GraphicsPath GetBreakLinePath(RectangleF breakLinePosition, bool top)
545 		{
546 			GraphicsPath path = new GraphicsPath();
547 
548 			if(this.axis.ScaleBreakStyle.BreakLineStyle == BreakLineStyle.Wave)
549 			{
550 				PointF[] points = null;
551 				int pointNumber = 0;
552 				if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
553 				{
554 					float startX = breakLinePosition.X;
555 					float endX = breakLinePosition.Right;
556 					float y = (top) ? breakLinePosition.Y : breakLinePosition.Bottom;
557 					pointNumber = ((int)(endX - startX) / 40) * 2 ;
558 					if(pointNumber < 2)
559 					{
560 						pointNumber = 2;
561 					}
562 					float step = (endX - startX) / pointNumber;
563 					points = new PointF[pointNumber + 1];
564 					for(int pointIndex = 1; pointIndex < pointNumber + 1; pointIndex++)
565 					{
566 						points[pointIndex] = new PointF(
567 							startX + pointIndex * step,
568 							y + ((pointIndex%2 == 0) ? -2f : 2f) );
569 					}
570 
571 					points[0] = new PointF(startX, y);
572 					points[points.Length - 1] = new PointF(endX, y);
573 				}
574 				else
575 				{
576 					float startY = breakLinePosition.Y;
577 					float endY = breakLinePosition.Bottom;
578 					float x = (top) ? breakLinePosition.X : breakLinePosition.Right;
579 					pointNumber = ((int)(endY - startY) / 40) * 2 ;
580 					if(pointNumber < 2)
581 					{
582 						pointNumber = 2;
583 					}
584 					float step = (endY - startY) / pointNumber;
585 					points = new PointF[pointNumber + 1];
586 					for(int pointIndex = 1; pointIndex < pointNumber + 1; pointIndex++)
587 					{
588 						points[pointIndex] = new PointF(
589 							x + ((pointIndex%2 == 0) ? -2f : 2f),
590 							startY + pointIndex * step);
591 					}
592 
593 					points[0] = new PointF(x, startY);
594 					points[points.Length - 1] = new PointF(x, endY);
595 				}
596 
597 				path.AddCurve(points, 0, pointNumber, 0.8f);
598 			}
599 			else if(this.axis.ScaleBreakStyle.BreakLineStyle == BreakLineStyle.Ragged)
600 			{
601 				PointF[] points = null;
602 				Random rand = new Random(435657);
603 				if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
604 				{
605 					float startX = breakLinePosition.X;
606 					float endX = breakLinePosition.Right;
607 					float y = (top) ? breakLinePosition.Y : breakLinePosition.Bottom;
608 					float step = 10f;
609 					int pointNumber = (int)((endX - startX) / step);
610 					if(pointNumber < 2)
611 					{
612 						pointNumber = 2;
613 					}
614 					points = new PointF[pointNumber];
615 
616 					for(int pointIndex = 1; pointIndex < pointNumber - 1; pointIndex++)
617 					{
618 						points[pointIndex] = new PointF(
619 							startX + pointIndex * step,
620 							y + rand.Next(-3, 3) );
621 					}
622 
623 					points[0] = new PointF(startX, y);
624 					points[points.Length - 1] = new PointF(endX, y);
625 				}
626 				else
627 				{
628 					float startY = breakLinePosition.Y;
629 					float endY = breakLinePosition.Bottom;
630 					float x = (top) ? breakLinePosition.X : breakLinePosition.Right;
631 					float step = 10f;
632 					int pointNumber = (int)((endY - startY) / step);
633 					if(pointNumber < 2)
634 					{
635 						pointNumber = 2;
636 					}
637 					points = new PointF[pointNumber];
638 
639 					for(int pointIndex = 1; pointIndex < pointNumber - 1; pointIndex++)
640 					{
641 						points[pointIndex] = new PointF(
642 							x + rand.Next(-3, 3),
643 							startY + pointIndex * step );
644 					}
645 
646 					points[0] = new PointF(x, startY);
647 					points[points.Length - 1] = new PointF(x, endY);
648 				}
649 
650 				path.AddLines(points);
651 			}
652 			else
653 			{
654 				if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
655 				{
656 					if(top)
657 					{
658 						path.AddLine(breakLinePosition.X, breakLinePosition.Y, breakLinePosition.Right, breakLinePosition.Y);
659 					}
660 					else
661 					{
662 						path.AddLine(breakLinePosition.X, breakLinePosition.Bottom, breakLinePosition.Right, breakLinePosition.Bottom);
663 					}
664 				}
665 				else
666 				{
667 					if(top)
668 					{
669 						path.AddLine(breakLinePosition.X, breakLinePosition.Y, breakLinePosition.X, breakLinePosition.Bottom);
670 					}
671 					else
672 					{
673 						path.AddLine(breakLinePosition.Right, breakLinePosition.Y, breakLinePosition.Right, breakLinePosition.Bottom);
674 					}
675 				}
676 			}
677 			return path;
678 		}
679 
680 		/// <summary>
681 		/// Gets position of the axis break line. Break line may be shown as a single
682 		/// line or two lines separated with a spacing.
683 		/// </summary>
684 		/// <param name="graph">Chart graphics.</param>
685 		/// <param name="nextSegment">Next segment reference.</param>
686 		/// <returns>Position of the axis break line in pixel coordinates.</returns>
GetBreakLinePosition(ChartGraphics graph, AxisScaleSegment nextSegment)687 		internal RectangleF GetBreakLinePosition(ChartGraphics graph, AxisScaleSegment nextSegment)
688 		{
689 			// Start with the plotting rectangle position
690 			RectangleF breakPosition = this.axis.PlotAreaPosition.ToRectangleF();
691 
692 			// Find maximum scale value of the current segment and minimuj of the next
693 			double from = this.axis.GetLinearPosition(nextSegment.ScaleMinimum);
694 			double to = this.axis.GetLinearPosition(this.ScaleMaximum);
695 			if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
696 			{
697 				breakPosition.Y = (float)Math.Min(from, to);
698 				breakPosition.Height = (float)Math.Max(from, to);
699 			}
700 			else
701 			{
702 				breakPosition.X = (float)Math.Min(from, to);
703 				breakPosition.Width = (float)Math.Max(from, to);;
704 			}
705 
706 			// Convert to pixels
707 			breakPosition = Rectangle.Round(graph.GetAbsoluteRectangle(breakPosition));
708 
709 			// Add border width
710 			if( this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left )
711 			{
712 				breakPosition.Height = (float)Math.Abs(breakPosition.Y - breakPosition.Height);
713 				breakPosition.X -= this.axis.ChartArea.BorderWidth;
714 				breakPosition.Width += 2 * this.axis.ChartArea.BorderWidth;
715 			}
716 			else
717 			{
718 				breakPosition.Width = (float)Math.Abs(breakPosition.X - breakPosition.Width);
719 				breakPosition.Y -= this.axis.ChartArea.BorderWidth;
720 				breakPosition.Height += 2 * this.axis.ChartArea.BorderWidth;
721 			}
722 
723 			return breakPosition;
724 		}
725 
726 		#endregion // Break Line Painting Methods
727 
728 		#region Helper Methods
729 
730 		/// <summary>
731 		/// Gets segment scale position and size in relative coordinates.
732 		/// Method takes in consideration segment spacing and space required fro separatorType.
733 		/// </summary>
734 		/// <param name="plotAreaSize">Plotting area size in relative coordinates.</param>
735 		/// <param name="scalePosition">Returns scale position.</param>
736 		/// <param name="scaleSize">Returns scale size.</param>
GetScalePositionAndSize(double plotAreaSize, out double scalePosition, out double scaleSize)737 		internal void GetScalePositionAndSize(double plotAreaSize, out double scalePosition, out double scaleSize)
738 		{
739 			scaleSize = (this.Size - this.Spacing) * (plotAreaSize / 100.0);
740 			scalePosition = this.Position * (plotAreaSize / 100.0);
741 		}
742 
743 		/// <summary>
744 		/// Saves axis settings and set them from the current segment.
745 		/// </summary>
SetTempAxisScaleAndInterval()746 		internal void SetTempAxisScaleAndInterval()
747 		{
748 			// Save current axis settings
749 			if(_oldAxisSettings.Count == 0)
750 			{
751 				_oldAxisSettings.Push(this.axis.maximum);
752 				_oldAxisSettings.Push(this.axis.minimum);
753 
754 				_oldAxisSettings.Push(this.axis.majorGrid.interval);
755 				_oldAxisSettings.Push(this.axis.majorGrid.intervalType);
756 				_oldAxisSettings.Push(this.axis.majorGrid.intervalOffset);
757 				_oldAxisSettings.Push(this.axis.majorGrid.intervalOffsetType);
758 
759 				_oldAxisSettings.Push(this.axis.majorTickMark.interval);
760 				_oldAxisSettings.Push(this.axis.majorTickMark.intervalType);
761 				_oldAxisSettings.Push(this.axis.majorTickMark.intervalOffset);
762 				_oldAxisSettings.Push(this.axis.majorTickMark.intervalOffsetType);
763 
764 				_oldAxisSettings.Push(this.axis.LabelStyle.interval);
765 				_oldAxisSettings.Push(this.axis.LabelStyle.intervalType);
766 				_oldAxisSettings.Push(this.axis.LabelStyle.intervalOffset);
767 				_oldAxisSettings.Push(this.axis.LabelStyle.intervalOffsetType);
768 			}
769 
770 			// Copy settings from the segment into the axis
771 			this.axis.maximum = this.ScaleMaximum;
772 			this.axis.minimum = this.ScaleMinimum;
773 
774 			this.axis.majorGrid.interval = this.Interval;
775 			this.axis.majorGrid.intervalType = this.IntervalType;
776 			this.axis.majorGrid.intervalOffset = this.IntervalOffset;
777 			this.axis.majorGrid.intervalOffsetType = this.IntervalOffsetType;
778 
779 			this.axis.majorTickMark.interval = this.Interval;
780 			this.axis.majorTickMark.intervalType = this.IntervalType;
781 			this.axis.majorTickMark.intervalOffset = this.IntervalOffset;
782 			this.axis.majorTickMark.intervalOffsetType = this.IntervalOffsetType;
783 
784 			this.axis.LabelStyle.interval = this.Interval;
785 			this.axis.LabelStyle.intervalType = this.IntervalType;
786 			this.axis.LabelStyle.intervalOffset = this.IntervalOffset;
787 			this.axis.LabelStyle.intervalOffsetType = this.IntervalOffsetType;
788 		}
789 
790 		/// <summary>
791 		/// Restore previously saved axis settings.
792 		/// </summary>
RestoreAxisScaleAndInterval()793 		internal void RestoreAxisScaleAndInterval()
794 		{
795 			if(_oldAxisSettings.Count > 0)
796 			{
797 				this.axis.LabelStyle.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop();
798 				this.axis.LabelStyle.intervalOffset = (double)_oldAxisSettings.Pop();
799 				this.axis.LabelStyle.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop();
800 				this.axis.LabelStyle.interval = (double)_oldAxisSettings.Pop();
801 
802 				this.axis.majorTickMark.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop();
803 				this.axis.majorTickMark.intervalOffset = (double)_oldAxisSettings.Pop();
804 				this.axis.majorTickMark.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop();
805 				this.axis.majorTickMark.interval = (double)_oldAxisSettings.Pop();
806 
807 				this.axis.majorGrid.intervalOffsetType = (DateTimeIntervalType)_oldAxisSettings.Pop();
808 				this.axis.majorGrid.intervalOffset = (double)_oldAxisSettings.Pop();
809 				this.axis.majorGrid.intervalType = (DateTimeIntervalType)_oldAxisSettings.Pop();
810 				this.axis.majorGrid.interval = (double)_oldAxisSettings.Pop();
811 
812 				this.axis.minimum = (double)_oldAxisSettings.Pop();
813 				this.axis.maximum = (double)_oldAxisSettings.Pop();
814 			}
815 		}
816 
817 		#endregion // Helper Methods
818 
819 	}
820 
821 	/// <summary>
822 	/// <b>AxisScaleSegmentCollection</b> is a class that stores collection of axis segments.
823 	/// </summary>
824 	[
825 	SRDescription("DescriptionAttributeAxisScaleSegmentCollection_AxisScaleSegmentCollection"),
826 	]
827 	internal class AxisScaleSegmentCollection : CollectionBase
828 	{
829 		#region Fields
830 
831 		// Axis this segment collection belongs to.
832 		private Axis _axis = null;
833 
834 		// Segment which is always used to convert scale values.
835 		// This value is set tmporarly when only one segment has
836 		// to handle all the values.
837 		private AxisScaleSegment _enforcedSegment = null;
838 
839 		// Indicates that values allowed to be outside of the scale segment.
840 		// Otherwise they will be rounded to Min and Max values.
841 		internal bool AllowOutOfScaleValues = false;
842 
843 		#endregion // Fields
844 
845 		#region Construction and Initialization
846 
847 		/// <summary>
848 		/// Default public constructor.
849 		/// </summary>
850 		/// <remarks>
851 		/// This constructor is for internal use and should not be part of documentation.
852 		/// </remarks>
AxisScaleSegmentCollection()853 		public AxisScaleSegmentCollection()
854 		{
855 		}
856 
857 		/// <summary>
858 		/// Default public constructor.
859 		/// </summary>
860 		/// <remarks>
861 		/// This constructor is for internal use and should not be part of documentation.
862 		/// </remarks>
863 		/// <param name="axis">
864 		/// Chart axis this collection belongs to
865 		/// </param>
AxisScaleSegmentCollection(Axis axis)866 		internal AxisScaleSegmentCollection(Axis axis)
867 		{
868 			this._axis = axis;
869 		}
870 
871 		#endregion // Construction and Initialization
872 
873 		#region Indexer
874 
875 		/// <summary>
876 		/// Axis scale segment collection indexer.
877 		/// </summary>
878 		/// <remarks>
879 		/// The <b>AxisScaleSegment</b> object index can be provided as a parameter. Returns the <see cref="AxisScaleSegment"/> object.
880 		/// </remarks>
881 		[
882 		SRDescription("DescriptionAttributeAxisScaleSegmentCollection_Item"),
883 		]
884 		public AxisScaleSegment this[int index]
885 		{
886 			get
887 			{
888 				return (AxisScaleSegment)this.List[(int)index];
889 			}
890 		}
891 
892 		#endregion // Indexer
893 
894 		#region Collection Add and Insert methods
895 
896 		/// <summary>
897 		/// Adds a segment to the end of the collection.
898 		/// </summary>
899 		/// <param name="segment">
900 		/// <see cref="AxisScaleSegment"/> object to add.
901 		/// </param>
902 		/// <returns>
903 		/// Index of the newly added object.
904 		/// </returns>
Add(AxisScaleSegment segment)905 		public int Add(AxisScaleSegment segment)
906 		{
907 			return this.List.Add(segment);
908 		}
909 
910 
911 		#endregion // Collection Add and Insert methods
912 
913 		#region Items Inserting and Removing Notification methods
914 
915 		/// <summary>
916 		/// After new item inserted.
917 		/// </summary>
918 		/// <param name="index">Item index.</param>
919 		/// <param name="value">Item object.</param>
920 		/// <remarks>
921 		/// This is an internal method and should not be part of the documentation.
922 		/// </remarks>
OnInsertComplete(int index, object value)923 		protected override void OnInsertComplete(int index, object value)
924 		{
925 			((AxisScaleSegment)value).axis  = this._axis;
926 		}
927 
928 		/// <summary>
929 		/// After items is set.
930 		/// </summary>
931 		/// <param name="index">The zero-based index at which oldValue can be found.</param>
932 		/// <param name="oldValue">The value to replace with newValue.</param>
933 		/// <param name="newValue">The new value of the element at index.</param>
934 		/// <remarks>
935 		/// This is an internal method and should not be part of the documentation.
936 		/// </remarks>
OnSetComplete(int index, object oldValue, object newValue)937 		protected override void OnSetComplete(int index, object oldValue, object newValue)
938 		{
939 			((AxisScaleSegment)newValue).axis  = this._axis;
940 		}
941 
942 		#endregion
943 
944 		#region Helper Methods
945 
946         /// <summary>
947         /// Ensures that specified axis scale segment is used for all coordinate transformations.
948         /// Set tot NULL to reset.
949         /// </summary>
950         /// <param name="segment"></param>
EnforceSegment(AxisScaleSegment segment)951 		internal void EnforceSegment(AxisScaleSegment segment)
952 		{
953 			this._enforcedSegment = segment;
954 		}
955 
956 		/// <summary>
957 		/// Find axis scale segment that should be used to translate axis value to relative coordinates.
958 		/// </summary>
959 		/// <param name="axisValue">Axis value to convert.</param>
960 		/// <returns>Scale segment to use for the convertion.</returns>
FindScaleSegmentForAxisValue(double axisValue)961 		public AxisScaleSegment FindScaleSegmentForAxisValue(double axisValue)
962 		{
963 			// Check if no segments defined
964 			if(this.List.Count == 0)
965 			{
966 				return null;
967 			}
968 
969 			// Check if segment enforcment is enabled
970 			if(_enforcedSegment != null)
971 			{
972 				return _enforcedSegment;
973 			}
974 
975 			// Iterate through all segments
976 			for(int index = 0; index < this.Count; index++)
977 			{
978 				if(axisValue < this[index].ScaleMinimum)
979 				{
980 					if(index == 0)
981 					{
982 						return this[index];
983 					}
984 					else
985 					{
986 						// Find the segment which is "closer" to the value
987 						if( Math.Abs(this[index].ScaleMinimum - axisValue) < Math.Abs(axisValue - this[index - 1].ScaleMaximum))
988 						{
989 							return this[index];
990 						}
991 						else
992 						{
993 							return this[index - 1];
994 						}
995 					}
996 				}
997 
998 				if(axisValue <= this[index].ScaleMaximum)
999 				{
1000 					return this[index];
1001 				}
1002 				else if(index == this.Count - 1)
1003 				{
1004 					return this[index];
1005 				}
1006 			}
1007 
1008 			return null;
1009 		}
1010 
1011 		#endregion // Helper Methods
1012 	}
1013 }
1014 
1015