1 //-------------------------------------------------------------
2 // <copyright company=�Microsoft Corporation�>
3 //   Copyright � Microsoft Corporation. All Rights Reserved.
4 // </copyright>
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 //  File:		FunnelChart.cs
9 //
10 //  Namespace:	DataVisualization.Charting.ChartTypes
11 //
12 //	Classes:	FunnelChart, PyramidChart, FunnelSegmentInfo,
13 //				FunnelPointLabelInfo
14 //
15 //  Purpose:    Provides 2D/3D drawing and hit testing functionality
16 //              for the Funnel and Pyramid charts.
17 //
18 //				Funnel and Pyramid Chart types display data that
19 //				equals 100% when totalled. This type of chart is a
20 //				single series chart representing the data as portions
21 //				of 100%, and this chart does not use any axes.
22 //
23 //	Reviewed:	AG - Microsoft 6, 2007
24 //
25 //===================================================================
26 
27 #region Used namespaces
28 
29 using System;
30 using System.Collections;
31 using System.Drawing;
32 using System.Drawing.Drawing2D;
33 using System.Diagnostics.CodeAnalysis;
34 using System.Globalization;
35 
36 #if Microsoft_CONTROL
37 	using System.Windows.Forms.DataVisualization.Charting.Utilities;
38 #else
39 	using System.Web.UI.DataVisualization.Charting.Utilities;
40 #endif
41 
42 #endregion // Used namespaces
43 
44 #if Microsoft_CONTROL
45 	namespace System.Windows.Forms.DataVisualization.Charting.ChartTypes
46 #else // Microsoft_CONTROL
47 	namespace System.Web.UI.DataVisualization.Charting.ChartTypes
48 #endif // Microsoft_CONTROL
49 {
50 	#region Enumerations
51 
52 	/// <summary>
53 	/// Value type of the pyramid chart.
54 	/// </summary>
55 	internal enum PyramidValueType
56 	{
57 		/// <summary>
58 		/// Each point value defines linear height of each segment.
59 		/// </summary>
60 		Linear,
61 
62 		/// <summary>
63 		/// Each point value defines surface of each segment.
64 		/// </summary>
65 		Surface
66 	}
67 
68 	/// <summary>
69 	/// Funnel chart drawing style.
70 	/// </summary>
71     internal enum FunnelStyle
72 	{
73 		/// <summary>
74 		/// Shape of the funnel is fixed and point Y value controls the height of the segments.
75 		/// </summary>
76         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "YIs")]
77 		YIsHeight,
78 
79 		/// <summary>
80 		/// Height of each segment is the same and point Y value controls the diameter of the segment.
81 		/// </summary>
82         [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "YIs")]
83         YIsWidth
84 	}
85 
86 	/// <summary>
87 	/// Outside labels placement.
88 	/// </summary>
89     internal enum FunnelLabelPlacement
90 	{
91 		/// <summary>
92 		/// Labels are placed on the right side of the funnel.
93 		/// </summary>
94 		Right,
95 
96 		/// <summary>
97 		/// Labels are placed on the left side of the funnel.
98 		/// </summary>
99 		Left
100 	}
101 
102 	/// <summary>
103 	/// Vertical alignment of the data point labels
104 	/// </summary>
105     internal enum FunnelLabelVerticalAlignment
106 	{
107 		/// <summary>
108 		/// Label placed in the middle.
109 		/// </summary>
110 		Center,
111 
112 		/// <summary>
113 		/// Label placed on top.
114 		/// </summary>
115 		Top,
116 
117 		/// <summary>
118 		/// Label placed on the bottom.
119 		/// </summary>
120 		Bottom
121 	}
122 
123 	/// <summary>
124 	/// Funnel chart 3D drawing style.
125 	/// </summary>
126     internal enum Funnel3DDrawingStyle
127 	{
128 		/// <summary>
129 		/// Circle will be used as a shape of the base.
130 		/// </summary>
131 		CircularBase,
132 
133 		/// <summary>
134 		/// Square will be used as a shape of the base.
135 		/// </summary>
136 		SquareBase
137 	}
138 
139 
140 	/// <summary>
141 	/// Funnel chart labels style enumeration.
142 	/// </summary>
143     internal enum FunnelLabelStyle
144 	{
145 		/// <summary>
146 		/// Data point labels are located inside of the funnel.
147 		/// </summary>
148 		Inside,
149 
150 		/// <summary>
151 		/// Data point labels are located outside of the funnel.
152 		/// </summary>
153 		Outside,
154 
155 		/// <summary>
156 		/// Data point labels are located outside of the funnel in a column.
157 		/// </summary>
158 		OutsideInColumn,
159 
160 		/// <summary>
161 		/// Data point labels are disabled.
162 		/// </summary>
163 		Disabled
164 	}
165 
166 	#endregion // Enumerations
167 
168 	/// <summary>
169     /// FunnelChart class provides 2D/3D drawing and hit testing functionality
170     /// for the Funnel and Pyramid charts.
171 	/// </summary>
172 	internal class FunnelChart : IChartType
173 	{
174 		#region Fields and Constructor
175 
176 		// Array list of funnel segments
177         internal ArrayList segmentList = null;
178 
179 		// List of data point labels information
180         internal ArrayList labelInfoList = null;
181 
182 		// Chart graphics object.
183         internal ChartGraphics Graph { get; set; }
184 
185 		// Chart area the chart type belongs to.
186         internal ChartArea Area { get; set; }
187 
188 		// Common chart elements.
189         internal CommonElements Common { get; set; }
190 
191 		// Spacing between each side of the funnel and chart area.
192         internal RectangleF plotAreaSpacing = new RectangleF(3f, 3f, 3f, 3f);
193 
194 		// Current chart type series
195 		private Series			_chartTypeSeries = null;
196 
197 		// Sum of all Y values in the data series
198         internal double yValueTotal = 0.0;
199 
200 		// Maximum Y value in the data series
201 		private double			_yValueMax = 0.0;
202 
203 		// Sum of all X values in the data series
204 		private double			_xValueTotal = 0.0;
205 
206 		// Number of points in the series
207         internal int pointNumber;
208 
209 		// Calculted plotting area of the chart
210         private RectangleF _plotAreaPosition = RectangleF.Empty;
211 
212 		// Funnel chart drawing style
213 		private	FunnelStyle		_funnelStyle = FunnelStyle.YIsHeight;
214 
215 		// Define the shape of the funnel neck
216 		private	SizeF			_funnelNeckSize = new SizeF(50f, 30f);
217 
218 		// Gap between funnel segments
219         internal float funnelSegmentGap = 0f;
220 
221 		// 3D funnel rotation angle
222 		private int				_rotation3D = 5;
223 
224 		// Indicates that rounded shape is used to draw 3D chart type instead of square
225         internal bool round3DShape = true;
226 
227 		// Indicates that Pyramid chart is rendered.
228         internal bool isPyramid = false;
229 
230 		// Minimum data point height
231 		private	float			_funnelMinPointHeight = 0f;
232 
233 		// Name of the attribute that controls the height of the gap between the points
234         internal string funnelPointGapAttributeName = CustomPropertyName.FunnelPointGap;
235 
236 		// Name of the attribute that controls the 3D funnel rotation angle
237         internal string funnelRotationAngleAttributeName = CustomPropertyName.Funnel3DRotationAngle;
238 
239 		// Name of the attribute that controls the minimum height of the point
240 		protected	string		funnelPointMinHeight = CustomPropertyName.FunnelMinPointHeight;
241 
242 		// Name of the attribute that controls the minimum height of the point
243         internal string funnel3DDrawingStyleAttributeName = CustomPropertyName.Funnel3DDrawingStyle;
244 
245 		// Name of the attribute that controls inside labels vertical alignment
246         internal string funnelInsideLabelAlignmentAttributeName = CustomPropertyName.FunnelInsideLabelAlignment;
247 
248 		// Name of the attribute that controls outside labels placement (Left vs. Right)
249 		protected	string		funnelOutsideLabelPlacementAttributeName = CustomPropertyName.FunnelOutsideLabelPlacement;
250 
251 		// Name of the attribute that controls labels style
252         internal string funnelLabelStyleAttributeName = CustomPropertyName.FunnelLabelStyle;
253 
254 		// Array of data point value adjusments in percentage
255 		private		double[]	_valuePercentages = null;
256 
257 		/// <summary>
258 		/// Default constructor
259 		/// </summary>
FunnelChart()260 		public FunnelChart()
261 		{
262 		}
263 
264 		#endregion
265 
266         #region Properties
267 
268         /// <summary>
269         /// Gets or sets the calculted plotting area of the chart
270         /// </summary>
271         internal RectangleF PlotAreaPosition
272         {
273             get { return _plotAreaPosition; }
274             set { _plotAreaPosition = value; }
275         }
276 
277         #endregion // Properties
278 
279         #region IChartType interface implementation
280 
281         /// <summary>
282 		/// Chart type name
283 		/// </summary>
284 		virtual public string Name			{ get{ return ChartTypeNames.Funnel;}}
285 
286 		/// <summary>
287 		/// True if chart type is stacked
288 		/// </summary>
289 		virtual public bool Stacked		{ get{ return false;}}
290 
291 
292 		/// <summary>
293 		/// True if stacked chart type supports groups
294 		/// </summary>
295 		virtual public bool SupportStackedGroups	{ get { return false; } }
296 
297 
298 		/// <summary>
299 		/// True if stacked chart type should draw separately positive and
300 		/// negative data points ( Bar and column Stacked types ).
301 		/// </summary>
302 		public bool StackSign		{ get{ return false;}}
303 
304 		/// <summary>
305 		/// True if chart type supports axeses
306 		/// </summary>
307 		virtual public bool RequireAxes	{ get{ return false;} }
308 
309 		/// <summary>
310 		/// Chart type with two y values used for scale ( bubble chart type )
311 		/// </summary>
312 		virtual public bool SecondYScale{ get{ return false;} }
313 
314 		/// <summary>
315 		/// True if chart type requires circular chart area.
316 		/// </summary>
317 		public bool CircularChartArea	{ get{ return false;} }
318 
319 		/// <summary>
320 		/// True if chart type supports logarithmic axes
321 		/// </summary>
322 		virtual public bool SupportLogarithmicAxes	{ get{ return true;} }
323 
324 		/// <summary>
325 		/// True if chart type requires to switch the value (Y) axes position
326 		/// </summary>
327 		virtual public bool SwitchValueAxes	{ get{ return false;} }
328 
329 		/// <summary>
330 		/// True if chart series can be placed side-by-side.
331 		/// </summary>
332 		virtual public bool SideBySideSeries { get{ return false;} }
333 
334 		/// <summary>
335 		/// True if each data point of a chart must be represented in the legend
336 		/// </summary>
337 		virtual public bool DataPointsInLegend	{ get{ return true;} }
338 
339 		/// <summary>
340 		/// If the crossing value is auto Crossing value should be
341 		/// automatically set to zero for some chart
342 		/// types (Bar, column, area etc.)
343 		/// </summary>
344 		virtual public bool ZeroCrossing { get{ return false;} }
345 
346 		/// <summary>
347 		/// True if palette colors should be applied for each data paoint.
348 		/// Otherwise the color is applied to the series.
349 		/// </summary>
350 		virtual public bool ApplyPaletteColorsToPoints	{ get { return true; } }
351 
352 		/// <summary>
353 		/// Indicates that extra Y values are connected to the scale of the Y axis
354 		/// </summary>
355 		virtual public bool ExtraYValuesConnectedToYAxis{ get { return false; } }
356 
357 		/// <summary>
358 		/// Indicates that it's a hundredred percent chart.
359 		/// Axis scale from 0 to 100 percent should be used.
360 		/// </summary>
361 		virtual public bool HundredPercent{ get{return false;} }
362 
363 		/// <summary>
364 		/// Indicates that it's a hundredred percent chart.
365 		/// Axis scale from 0 to 100 percent should be used.
366 		/// </summary>
367 		virtual public bool HundredPercentSupportNegative{ get{return false;} }
368 
369 		/// <summary>
370 		/// How to draw series/points in legend:
371 		/// Filled rectangle, Line or Marker
372 		/// </summary>
373 		/// <param name="series">Legend item series.</param>
374 		/// <returns>Legend item style.</returns>
GetLegendImageStyle(Series series)375 		virtual public LegendImageStyle GetLegendImageStyle(Series series)
376 		{
377 			return LegendImageStyle.Rectangle;
378 		}
379 
380 		/// <summary>
381 		/// Number of supported Y value(s) per point
382 		/// </summary>
383 		virtual public int YValuesPerPoint	{ get { return 1; } }
384 
385 		/// <summary>
386 		/// Gets chart type image.
387 		/// </summary>
388 		/// <param name="registry">Chart types registry object.</param>
389 		/// <returns>Chart type image.</returns>
GetImage(ChartTypeRegistry registry)390 		virtual public System.Drawing.Image GetImage(ChartTypeRegistry registry)
391 		{
392 			return (System.Drawing.Image)registry.ResourceManager.GetObject(this.Name + "ChartType");
393 		}
394 
395 	#endregion
396 
397 		#region Painting
398 
399 		/// <summary>
400 		/// Paint Funnel Chart.
401 		/// </summary>
402 		/// <param name="graph">The Chart Graphics object.</param>
403 		/// <param name="common">The Common elements object.</param>
404 		/// <param name="area">Chart area for this chart.</param>
405 		/// <param name="seriesToDraw">Chart series to draw.</param>
Paint( ChartGraphics graph, CommonElements common, ChartArea area, Series seriesToDraw )406 		virtual public void Paint(
407 			ChartGraphics graph,
408 			CommonElements common,
409 			ChartArea area,
410 			Series seriesToDraw )
411 		{
412 			// Reset fields
413 			this._chartTypeSeries = null;
414 			this._funnelMinPointHeight = 0f;
415 
416 			// Save reference to the input parameters
417 			this.Graph = graph;
418 			this.Common = common;
419 			this.Area = area;
420 
421 			// Funnel chart like a Pie chart shows each data point as part of the whole (100%).
422 			// Calculate the sum of all Y and X values, which will be used to calculate point percentage.
423 			GetDataPointValuesStatistic();
424 
425 			// Check if there are non-zero points
426 			if(this.yValueTotal == 0.0 || this.pointNumber == 0)
427 			{
428 				return;
429 			}
430 
431 			// When Y value is funnel width at least 2 points required
432 			this._funnelStyle = GetFunnelStyle( this.GetDataSeries() );
433 			if(this._funnelStyle == FunnelStyle.YIsWidth &&
434 				this.pointNumber == 1)
435 			{
436 				// At least 2 points required
437 				return;
438 			}
439 
440 			// Get minimum point height
441 			GetFunnelMinPointHeight( this.GetDataSeries() );
442 
443 			// Fill list of data point labels information
444 			this.labelInfoList = CreateLabelsInfoList();
445 
446 			// Calculate the spacing required for the labels.
447 			GetPlotAreaSpacing();
448 
449 			// Draw funnel
450 			ProcessChartType();
451 
452 			// Draw data point labels
453 			DrawLabels();
454 		}
455 
456 		/// <summary>
457 		/// Process chart type drawing.
458 		/// </summary>
ProcessChartType()459 		private void ProcessChartType()
460 		{
461 			// Reversed drawing order in 3D with positive rotation angle
462 			if(this.Area.Area3DStyle.Enable3D &&
463 				( (this._rotation3D > 0 && !this.isPyramid) || (this._rotation3D < 0 && this.isPyramid) ) )
464 			{
465 				this.segmentList.Reverse();
466 			}
467 
468 			// Check if series shadow should be drawn separatly
469 			bool	drawShadowSeparatly = true;
470 			bool	drawSegmentShadow = (this.Area.Area3DStyle.Enable3D) ? false : true;
471 
472 			// Process all funnel segments shadows
473 			Series series = this.GetDataSeries();
474 			if(drawSegmentShadow &&
475 				drawShadowSeparatly &&
476 				series != null &&
477 				series.ShadowOffset != 0)
478 			{
479 				foreach(FunnelSegmentInfo segmentInfo in this.segmentList)
480 				{
481 					// Draw funnel segment
482 					this.DrawFunnelCircularSegment(
483 						segmentInfo.Point,
484 						segmentInfo.PointIndex,
485 						segmentInfo.StartWidth,
486 						segmentInfo.EndWidth,
487 						segmentInfo.Location,
488 						segmentInfo.Height,
489 						segmentInfo.NothingOnTop,
490 						segmentInfo.NothingOnBottom,
491 						false,
492 						true);
493 				}
494 
495 				drawSegmentShadow = false;
496 			}
497 
498 			// Process all funnel segments
499 			foreach(FunnelSegmentInfo segmentInfo in this.segmentList)
500 			{
501 				// Draw funnel segment
502 				this.DrawFunnelCircularSegment(
503 					segmentInfo.Point,
504 					segmentInfo.PointIndex,
505 					segmentInfo.StartWidth,
506 					segmentInfo.EndWidth,
507 					segmentInfo.Location,
508 					segmentInfo.Height,
509 					segmentInfo.NothingOnTop,
510 					segmentInfo.NothingOnBottom,
511 					true,
512 					drawSegmentShadow);
513 			}
514 		}
515 
516 		/// <summary>
517 		/// Gets funnel data point segment height and width.
518 		/// </summary>
519 		/// <param name="series">Chart type series.</param>
520 		/// <param name="pointIndex">Data point index in the series.</param>
521 		/// <param name="location">Segment top location. Bottom location if reversed drawing order.</param>
522 		/// <param name="height">Returns the height of the segment.</param>
523 		/// <param name="startWidth">Returns top width of the segment.</param>
524 		/// <param name="endWidth">Returns botom width of the segment.</param>
GetPointWidthAndHeight( Series series, int pointIndex, float location, out float height, out float startWidth, out float endWidth)525 		protected virtual void GetPointWidthAndHeight(
526 			Series series,
527 			int pointIndex,
528 			float location,
529 			out float height,
530 			out float startWidth,
531 			out float endWidth)
532 		{
533 			PointF	pointPositionAbs = PointF.Empty;
534 
535 			// Get plotting area position in pixels
536 			RectangleF plotAreaPositionAbs = this.Graph.GetAbsoluteRectangle(this.PlotAreaPosition);
537 
538 			// Calculate total height of plotting area minus reserved space for the gaps
539 			float plotAreaHeightAbs = plotAreaPositionAbs.Height -
540 				this.funnelSegmentGap * (this.pointNumber - ((ShouldDrawFirstPoint()) ? 1 : 2) );
541 			if(plotAreaHeightAbs < 0f)
542 			{
543 				plotAreaHeightAbs = 0f;
544 			}
545 
546 			if( this._funnelStyle == FunnelStyle.YIsWidth )
547 			{
548 				// Check if X values are provided
549 				if(this._xValueTotal == 0.0)
550 				{
551 					// Calculate segment height in pixels by deviding
552 					// plotting area height by number of points.
553 					height = plotAreaHeightAbs / (this.pointNumber - 1);
554 				}
555 				else
556 				{
557 					// Calculate segment height as a part of total Y values in series
558 					height = (float)(plotAreaHeightAbs * (GetXValue(series.Points[pointIndex]) / this._xValueTotal));
559 				}
560 
561 				// Check for minimum segment height
562 				height = CheckMinHeight(height);
563 
564 				// Calculate start and end width of the segment based on Y value
565 				// of previous and current data point.
566 				startWidth = (float)(plotAreaPositionAbs.Width * (GetYValue(series.Points[pointIndex-1], pointIndex-1) / this._yValueMax));
567 				endWidth = (float)(plotAreaPositionAbs.Width * (GetYValue(series.Points[pointIndex], pointIndex) / this._yValueMax));
568 
569 				// Set point position for annotation anchoring
570 				pointPositionAbs  = new PointF(
571 					plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f,
572 					location + height);
573 			}
574 			else if( this._funnelStyle == FunnelStyle.YIsHeight )
575 			{
576 				// Calculate segment height as a part of total Y values in series
577 				height = (float)(plotAreaHeightAbs * (GetYValue(series.Points[pointIndex], pointIndex) / this.yValueTotal));
578 
579 				// Check for minimum segment height
580 				height = CheckMinHeight(height);
581 
582 				// Get intersection point of the horizontal line at the start of the segment
583 				// with the left pre-defined wall of the funnel.
584 				PointF startIntersection = ChartGraphics.GetLinesIntersection(
585 					plotAreaPositionAbs.X, location,
586 					plotAreaPositionAbs.Right, location,
587 					plotAreaPositionAbs.X, plotAreaPositionAbs.Y,
588 					plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f - this._funnelNeckSize.Width / 2f,
589 					plotAreaPositionAbs.Bottom - this._funnelNeckSize.Height );
590 
591 				// Get intersection point of the horizontal line at the end of the segment
592 				// with the left pre-defined wall of the funnel.
593 				PointF endIntersection = ChartGraphics.GetLinesIntersection(
594 					plotAreaPositionAbs.X, location + height,
595 					plotAreaPositionAbs.Right, location + height,
596 					plotAreaPositionAbs.X, plotAreaPositionAbs.Y,
597 					plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f - this._funnelNeckSize.Width / 2f,
598 					plotAreaPositionAbs.Bottom - this._funnelNeckSize.Height );
599 
600 				// Get segment start and end width
601 				startWidth = (float)( plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f -
602 					startIntersection.X) * 2f;
603 				endWidth = (float)( plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f -
604 					endIntersection.X) * 2f;
605 
606 				// Set point position for annotation anchoring
607 				pointPositionAbs  = new PointF(
608 					plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f,
609 					location + height / 2f);
610 			}
611 			else
612 			{
613                 throw (new InvalidOperationException(SR.ExceptionFunnelStyleUnknown(this._funnelStyle.ToString())));
614 			}
615 
616 			// Set pre-calculated point position
617 			series.Points[pointIndex].positionRel = Graph.GetRelativePoint(pointPositionAbs);
618 		}
619 
620 		/// <summary>
621 		/// Checks if first point in the series should be drawn.
622 		/// When point Y value is used to define the diameter of the funnel
623 		/// segment 2 points are required to draw 1 segment. In this case first
624 		/// data point is not drawn.
625 		/// </summary>
626 		/// <returns>True if first point in the series should be drawn.</returns>
ShouldDrawFirstPoint()627 		protected virtual bool ShouldDrawFirstPoint()
628 		{
629 			return ( this._funnelStyle == FunnelStyle.YIsHeight || this.isPyramid);
630 		}
631 
632 		/// <summary>
633 		/// Draws funnel 3D square segment.
634 		/// </summary>
635 		/// <param name="point">Data point</param>
636 		/// <param name="pointIndex">Data point index.</param>
637 		/// <param name="startWidth">Segment top width.</param>
638 		/// <param name="endWidth">Segment bottom width.</param>
639 		/// <param name="location">Segment top location.</param>
640 		/// <param name="height">Segment height.</param>
641 		/// <param name="nothingOnTop">True if nothing is on the top of that segment.</param>
642 		/// <param name="nothingOnBottom">True if nothing is on the bottom of that segment.</param>
643 		/// <param name="drawSegment">True if segment shadow should be drawn.</param>
644 		/// <param name="drawSegmentShadow">True if segment shadow should be drawn.</param>
DrawFunnel3DSquareSegment( DataPoint point, int pointIndex, float startWidth, float endWidth, float location, float height, bool nothingOnTop, bool nothingOnBottom, bool drawSegment, bool drawSegmentShadow)645 		private void DrawFunnel3DSquareSegment(
646 			DataPoint point,
647 			int pointIndex,
648 			float startWidth,
649 			float endWidth,
650 			float location,
651 			float height,
652 			bool nothingOnTop,
653 			bool nothingOnBottom,
654 			bool drawSegment,
655 			bool drawSegmentShadow)
656 		{
657 			// Increase the height of the segment to make sure there is no gaps between segments
658 			if(!nothingOnBottom)
659 			{
660 				height += 0.3f;
661 			}
662 
663 			// Get lighter and darker back colors
664 			Color	lightColor = ChartGraphics.GetGradientColor( point.Color, Color.White, 0.3 );
665 			Color	darkColor = ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.3 );
666 
667 			// Segment width can't be smaller than funnel neck width
668 			if( this._funnelStyle == FunnelStyle.YIsHeight && !this.isPyramid )
669 			{
670 				if(startWidth < this._funnelNeckSize.Width)
671 				{
672 					startWidth = this._funnelNeckSize.Width;
673 				}
674 				if(endWidth < this._funnelNeckSize.Width)
675 				{
676 					endWidth = this._funnelNeckSize.Width;
677 				}
678 			}
679 
680 			// Get 3D rotation angle
681 			float	topRotationHeight = (float)( (startWidth / 2f) * Math.Sin(this._rotation3D / 180F * Math.PI) );
682 			float	bottomRotationHeight = (float)( (endWidth / 2f) * Math.Sin(this._rotation3D / 180F * Math.PI) );
683 
684 			// Get plotting area position in pixels
685 			RectangleF plotAreaPositionAbs = this.Graph.GetAbsoluteRectangle(this.PlotAreaPosition);
686 
687 			// Get the horizontal center point in pixels
688 			float	xCenterPointAbs = plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f;
689 
690 			// Start Svg Selection mode
691 			this.Graph.StartHotRegion( point );
692 
693 			// Create segment path
694 			GraphicsPath segmentPath = new GraphicsPath();
695 
696 			// Draw left part of the pyramid segment
697 			// Add top line
698 			if(startWidth > 0f)
699 			{
700 				segmentPath.AddLine(
701 					xCenterPointAbs - startWidth / 2f, location,
702 					xCenterPointAbs, location + topRotationHeight);
703 			}
704 
705 			// Add middle line
706 			segmentPath.AddLine(
707 				xCenterPointAbs, location + topRotationHeight,
708 				xCenterPointAbs, location + height + bottomRotationHeight);
709 
710 			// Add bottom line
711 			if(endWidth > 0f)
712 			{
713 				segmentPath.AddLine(
714 					xCenterPointAbs, location + height + bottomRotationHeight,
715 					xCenterPointAbs - endWidth / 2f, location + height);
716 			}
717 
718 			// Add left line
719 			segmentPath.AddLine(
720 				xCenterPointAbs - endWidth / 2f, location + height,
721 				xCenterPointAbs - startWidth / 2f, location);
722 
723 			if( this.Common.ProcessModePaint )
724 			{
725 				// Fill graphics path
726 				this.Graph.DrawPathAbs(
727 					segmentPath,
728 					(drawSegment) ? lightColor : Color.Transparent,
729 					point.BackHatchStyle,
730 					point.BackImage,
731 					point.BackImageWrapMode,
732 					point.BackImageTransparentColor,
733 					point.BackImageAlignment,
734 					point.BackGradientStyle,
735 					(drawSegment) ? point.BackSecondaryColor : Color.Transparent,
736 					(drawSegment) ? point.BorderColor : Color.Transparent,
737 					point.BorderWidth,
738 					point.BorderDashStyle,
739 					PenAlignment.Center,
740 					(drawSegmentShadow) ? point.series.ShadowOffset : 0,
741 					point.series.ShadowColor);
742 			}
743 
744 			if( this.Common.ProcessModeRegions )
745 			{
746 				// Add hot region
747 				this.Common.HotRegionsList.AddHotRegion(
748 					segmentPath,
749 					false,
750 					this.Graph,
751 					point,
752 					point.series.Name,
753 					pointIndex);
754 			}
755 			segmentPath.Dispose();
756 
757 
758 
759 			// Draw right part of the pyramid segment
760 			// Add top line
761 			segmentPath = new GraphicsPath();
762 			if(startWidth > 0f)
763 			{
764 				segmentPath.AddLine(
765 					xCenterPointAbs + startWidth / 2f, location,
766 					xCenterPointAbs, location + topRotationHeight);
767 			}
768 
769 			// Add middle line
770 			segmentPath.AddLine(
771 				xCenterPointAbs, location + topRotationHeight,
772 				xCenterPointAbs, location + height + bottomRotationHeight);
773 
774 			// Add bottom line
775 			if(endWidth > 0f)
776 			{
777 				segmentPath.AddLine(
778 					xCenterPointAbs, location + height + bottomRotationHeight,
779 					xCenterPointAbs + endWidth / 2f, location + height);
780 			}
781 
782 			// Add right line
783 			segmentPath.AddLine(
784 				xCenterPointAbs + endWidth / 2f, location + height,
785 				xCenterPointAbs + startWidth / 2f, location);
786 
787 			if( this.Common.ProcessModePaint )
788 			{
789 				// Fill graphics path
790 				this.Graph.DrawPathAbs(
791 					segmentPath,
792 					(drawSegment) ? darkColor : Color.Transparent,
793 					point.BackHatchStyle,
794 					point.BackImage,
795 					point.BackImageWrapMode,
796 					point.BackImageTransparentColor,
797 					point.BackImageAlignment,
798 					point.BackGradientStyle,
799 					(drawSegment) ? point.BackSecondaryColor : Color.Transparent,
800 					(drawSegment) ? point.BorderColor : Color.Transparent,
801 					point.BorderWidth,
802 					point.BorderDashStyle,
803 					PenAlignment.Center,
804 					(drawSegmentShadow) ? point.series.ShadowOffset : 0,
805 					point.series.ShadowColor);
806 			}
807 
808 			if( this.Common.ProcessModeRegions )
809 			{
810 				// Add hot region
811 				this.Common.HotRegionsList.AddHotRegion(
812 					segmentPath,
813 					false,
814 					this.Graph,
815 					point,
816 					point.series.Name,
817 					pointIndex);
818 			}
819 			segmentPath.Dispose();
820 
821 
822 			// Add top 3D surface
823 			if(this._rotation3D > 0f && startWidth > 0f && nothingOnTop)
824 			{
825 				if(this.Area.Area3DStyle.Enable3D)
826 				{
827 					PointF[] sidePoints = new PointF[4];
828 					sidePoints[0] = new PointF(xCenterPointAbs + startWidth / 2f, location);
829 					sidePoints[1] = new PointF(xCenterPointAbs, location + topRotationHeight);
830 					sidePoints[2] = new PointF(xCenterPointAbs - startWidth / 2f, location);
831 					sidePoints[3] = new PointF(xCenterPointAbs, location - topRotationHeight);
832 					GraphicsPath topCurve = new GraphicsPath();
833 					topCurve.AddLines(sidePoints);
834 					topCurve.CloseAllFigures();
835 
836 					if( this.Common.ProcessModePaint )
837 					{
838 						// Fill graphics path
839 						this.Graph.DrawPathAbs(
840 							topCurve,
841 							(drawSegment) ? ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.4 ) : Color.Transparent,
842 							point.BackHatchStyle,
843 							point.BackImage,
844 							point.BackImageWrapMode,
845 							point.BackImageTransparentColor,
846 							point.BackImageAlignment,
847 							point.BackGradientStyle,
848 							(drawSegment) ? point.BackSecondaryColor : Color.Transparent,
849 							(drawSegment) ? point.BorderColor : Color.Transparent,
850 							point.BorderWidth,
851 							point.BorderDashStyle,
852 							PenAlignment.Center,
853 							(drawSegmentShadow) ? point.series.ShadowOffset : 0,
854 							point.series.ShadowColor);
855 					}
856 
857 					if( this.Common.ProcessModeRegions )
858 					{
859 						// Add hot region
860 						this.Common.HotRegionsList.AddHotRegion(
861 							topCurve,
862 							false,
863 							this.Graph,
864 							point,
865 							point.series.Name,
866 							pointIndex);
867 					}
868 					topCurve.Dispose();
869 				}
870 			}
871 
872 			// Add bottom 3D surface
873 			if(this._rotation3D < 0f && startWidth > 0f && nothingOnBottom)
874 			{
875 				if(this.Area.Area3DStyle.Enable3D)
876 				{
877 					PointF[] sidePoints = new PointF[4];
878 					sidePoints[0] = new PointF(xCenterPointAbs + endWidth / 2f, location + height);
879 					sidePoints[1] = new PointF(xCenterPointAbs, location + height + bottomRotationHeight);
880 					sidePoints[2] = new PointF(xCenterPointAbs - endWidth / 2f, location + height);
881 					sidePoints[3] = new PointF(xCenterPointAbs, location + height - bottomRotationHeight);
882 					GraphicsPath topCurve = new GraphicsPath();
883 					topCurve.AddLines(sidePoints);
884 					topCurve.CloseAllFigures();
885 
886 					if( this.Common.ProcessModePaint )
887 					{
888 						// Fill graphics path
889 						this.Graph.DrawPathAbs(
890 							topCurve,
891 							(drawSegment) ? ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.4 ) : Color.Transparent,
892 							point.BackHatchStyle,
893 							point.BackImage,
894 							point.BackImageWrapMode,
895 							point.BackImageTransparentColor,
896 							point.BackImageAlignment,
897 							point.BackGradientStyle,
898 							(drawSegment) ? point.BackSecondaryColor : Color.Transparent,
899 							(drawSegment) ? point.BorderColor : Color.Transparent,
900 							point.BorderWidth,
901 							point.BorderDashStyle,
902 							PenAlignment.Center,
903 							(drawSegmentShadow) ? point.series.ShadowOffset : 0,
904 							point.series.ShadowColor);
905 					}
906 
907 					if( this.Common.ProcessModeRegions )
908 					{
909 						// Add hot region
910 						this.Common.HotRegionsList.AddHotRegion(
911 							topCurve,
912 							false,
913 							this.Graph,
914 							point,
915 							point.series.Name,
916 							pointIndex);
917 					}
918 					topCurve.Dispose();
919 
920 				}
921 			}
922 
923 			// End Svg Selection mode
924 			this.Graph.EndHotRegion( );
925 		}
926 
927 		/// <summary>
928 		/// Draws funnel segment.
929 		/// </summary>
930 		/// <param name="point">Data point</param>
931 		/// <param name="pointIndex">Data point index.</param>
932 		/// <param name="startWidth">Segment top width.</param>
933 		/// <param name="endWidth">Segment bottom width.</param>
934 		/// <param name="location">Segment top location.</param>
935 		/// <param name="height">Segment height.</param>
936 		/// <param name="nothingOnTop">True if nothing is on the top of that segment.</param>
937 		/// <param name="nothingOnBottom">True if nothing is on the bottom of that segment.</param>
938 		/// <param name="drawSegment">True if segment shadow should be drawn.</param>
939 		/// <param name="drawSegmentShadow">True if segment shadow should be drawn.</param>
DrawFunnelCircularSegment( DataPoint point, int pointIndex, float startWidth, float endWidth, float location, float height, bool nothingOnTop, bool nothingOnBottom, bool drawSegment, bool drawSegmentShadow)940 		private void DrawFunnelCircularSegment(
941 			DataPoint point,
942 			int pointIndex,
943 			float startWidth,
944 			float endWidth,
945 			float location,
946 			float height,
947 			bool nothingOnTop,
948 			bool nothingOnBottom,
949 			bool drawSegment,
950 			bool drawSegmentShadow)
951 		{
952 			PointF	leftSideLinePoint = PointF.Empty;
953 			PointF	rightSideLinePoint = PointF.Empty;
954 
955 			// Check if square 3D segment should be drawn
956 			if(this.Area.Area3DStyle.Enable3D && !round3DShape)
957 			{
958 				DrawFunnel3DSquareSegment(
959 					point,
960 					pointIndex,
961 					startWidth,
962 					endWidth,
963 					location,
964 					height,
965 					nothingOnTop,
966 					nothingOnBottom,
967 					drawSegment,
968 					drawSegmentShadow);
969 				return;
970 			}
971 
972 			// Increase the height of the segment to make sure there is no gaps between segments
973 			if(!nothingOnBottom)
974 			{
975 				height += 0.3f;
976 			}
977 
978 			// Segment width can't be smaller than funnel neck width
979 			float	originalStartWidth = startWidth;
980 			float	originalEndWidth = endWidth;
981 			if( this._funnelStyle == FunnelStyle.YIsHeight && !this.isPyramid)
982 			{
983 				if(startWidth < this._funnelNeckSize.Width)
984 				{
985 					startWidth = this._funnelNeckSize.Width;
986 				}
987 				if(endWidth < this._funnelNeckSize.Width)
988 				{
989 					endWidth = this._funnelNeckSize.Width;
990 				}
991 			}
992 
993 			// Get 3D rotation angle
994 			float	tension = 0.8f;
995 			float	topRotationHeight = (float)( (startWidth / 2f) * Math.Sin(this._rotation3D / 180F * Math.PI) );
996 			float	bottomRotationHeight = (float)( (endWidth / 2f) * Math.Sin(this._rotation3D / 180F * Math.PI) );
997 
998 			// Get plotting area position in pixels
999 			RectangleF plotAreaPositionAbs = this.Graph.GetAbsoluteRectangle(this.PlotAreaPosition);
1000 
1001 			// Get the horizontal center point in pixels
1002 			float	xCenterPointAbs = plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f;
1003 
1004 			// Start Svg Selection mode
1005 			this.Graph.StartHotRegion( point );
1006 
1007 			// Create segment path
1008 			GraphicsPath segmentPath = new GraphicsPath();
1009 
1010 			// Add top line
1011 			if(startWidth > 0f)
1012 			{
1013 				if(this.Area.Area3DStyle.Enable3D)
1014 				{
1015 					PointF[] sidePoints = new PointF[4];
1016 					sidePoints[0] = new PointF(xCenterPointAbs + startWidth / 2f, location);
1017 					sidePoints[1] = new PointF(xCenterPointAbs, location + topRotationHeight);
1018 					sidePoints[2] = new PointF(xCenterPointAbs - startWidth / 2f, location);
1019 					sidePoints[3] = new PointF(xCenterPointAbs, location - topRotationHeight);
1020 					GraphicsPath topCurve = new GraphicsPath();
1021 					topCurve.AddClosedCurve(sidePoints, tension);
1022 					topCurve.Flatten();
1023 					topCurve.Reverse();
1024 
1025 					Graph.AddEllipseSegment(
1026 						segmentPath,
1027 						topCurve,
1028 						null,
1029 						true,
1030 						0f,
1031 						out leftSideLinePoint,
1032 						out rightSideLinePoint);
1033 				}
1034 				else
1035 				{
1036 					segmentPath.AddLine(
1037 						xCenterPointAbs - startWidth / 2f, location,
1038 						xCenterPointAbs + startWidth / 2f, location);
1039 				}
1040 			}
1041 
1042 			// Add right line
1043 			if( this._funnelStyle == FunnelStyle.YIsHeight &&
1044 				!this.isPyramid &&
1045 				startWidth > this._funnelNeckSize.Width &&
1046 				endWidth <= this._funnelNeckSize.Width)
1047 			{
1048 				// Get intersection point of the vertical line at the neck border
1049 				// with the left pre-defined wall of the funnel.
1050 				PointF intersection = ChartGraphics.GetLinesIntersection(
1051 					xCenterPointAbs + this._funnelNeckSize.Width / 2f, plotAreaPositionAbs.Top,
1052 					xCenterPointAbs + this._funnelNeckSize.Width / 2f, plotAreaPositionAbs.Bottom,
1053 					xCenterPointAbs + originalStartWidth / 2f, location,
1054 					xCenterPointAbs + originalEndWidth / 2f, location + height);
1055 
1056 				// Adjust intersection point with top of the neck
1057 				intersection.Y = plotAreaPositionAbs.Bottom - this._funnelNeckSize.Height;
1058 
1059 				// Add two segment line
1060 				segmentPath.AddLine(
1061 					xCenterPointAbs + startWidth / 2f, location,
1062 					intersection.X, intersection.Y);
1063 				segmentPath.AddLine(
1064 					intersection.X, intersection.Y,
1065 					intersection.X, location + height);
1066 			}
1067 			else
1068 			{
1069 				// Add straight line
1070 				segmentPath.AddLine(
1071 					xCenterPointAbs + startWidth / 2f, location,
1072 					xCenterPointAbs + endWidth / 2f, location + height);
1073 			}
1074 
1075 			// Add bottom line
1076 			if(endWidth > 0f)
1077 			{
1078 				if(this.Area.Area3DStyle.Enable3D)
1079 				{
1080 					PointF[] sidePoints = new PointF[4];
1081 					sidePoints[0] = new PointF(xCenterPointAbs + endWidth / 2f, location + height);
1082 					sidePoints[1] = new PointF(xCenterPointAbs, location + height + bottomRotationHeight);
1083 					sidePoints[2] = new PointF(xCenterPointAbs - endWidth / 2f, location + height);
1084 					sidePoints[3] = new PointF(xCenterPointAbs, location + height - bottomRotationHeight);
1085 					GraphicsPath topCurve = new GraphicsPath();
1086 					topCurve.AddClosedCurve(sidePoints, tension);
1087 					topCurve.Flatten();
1088 					topCurve.Reverse();
1089 
1090                     using (GraphicsPath tmp = new GraphicsPath())
1091                     {
1092                         Graph.AddEllipseSegment(
1093                             tmp,
1094                             topCurve,
1095                             null,
1096                             true,
1097                             0f,
1098                             out leftSideLinePoint,
1099                             out rightSideLinePoint);
1100 
1101                         tmp.Reverse();
1102                         if (tmp.PointCount > 0)
1103                         {
1104                             segmentPath.AddPath(tmp, false);
1105                         }
1106                     }
1107 				}
1108 				else
1109 				{
1110 					segmentPath.AddLine(
1111 						xCenterPointAbs + endWidth / 2f, location + height,
1112 						xCenterPointAbs - endWidth / 2f, location + height);
1113 				}
1114 			}
1115 
1116 			// Add left line
1117 			if( this._funnelStyle == FunnelStyle.YIsHeight &&
1118 				!this.isPyramid &&
1119 				startWidth > this._funnelNeckSize.Width &&
1120 				endWidth <= this._funnelNeckSize.Width)
1121 			{
1122 				// Get intersection point of the horizontal line at the start of the segment
1123 				// with the left pre-defined wall of the funnel.
1124 				PointF intersection = ChartGraphics.GetLinesIntersection(
1125 					xCenterPointAbs - this._funnelNeckSize.Width / 2f, plotAreaPositionAbs.Top,
1126 					xCenterPointAbs - this._funnelNeckSize.Width / 2f, plotAreaPositionAbs.Bottom,
1127 					xCenterPointAbs - originalStartWidth / 2f, location,
1128 					xCenterPointAbs - originalEndWidth / 2f, location + height);
1129 
1130 				// Adjust intersection point with top of the neck
1131 				intersection.Y = plotAreaPositionAbs.Bottom - this._funnelNeckSize.Height;
1132 
1133 				// Add two segment line
1134 				segmentPath.AddLine(
1135 					intersection.X, location + height,
1136 					intersection.X, intersection.Y);
1137 				segmentPath.AddLine(
1138 					intersection.X, intersection.Y,
1139 					xCenterPointAbs - startWidth / 2f, location);
1140 			}
1141 			else
1142 			{
1143 				segmentPath.AddLine(
1144 					xCenterPointAbs - endWidth / 2f, location + height,
1145 					xCenterPointAbs - startWidth / 2f, location);
1146 			}
1147 
1148 			if( this.Common.ProcessModePaint )
1149 			{
1150 				// Draw lightStyle source blink effect in 3D
1151 				if(this.Area.Area3DStyle.Enable3D &&
1152 					Graph.ActiveRenderingType == RenderingType.Gdi )
1153 				{
1154 					// Get lighter and darker back colors
1155 					Color	lightColor = ChartGraphics.GetGradientColor( point.Color, Color.White, 0.3 );
1156 					Color	darkColor = ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.3 );
1157 
1158 					// Create linear gradient brush
1159 					RectangleF boundsRect = segmentPath.GetBounds();
1160 					if(boundsRect.Width == 0f)
1161 					{
1162 						boundsRect.Width = 1f;
1163 					}
1164 					if(boundsRect.Height == 0f)
1165 					{
1166 						boundsRect.Height = 1f;
1167 					}
1168 					using( LinearGradientBrush brush = new LinearGradientBrush(
1169 							   boundsRect,
1170 							   lightColor,
1171 							   darkColor,
1172 							   0f) )
1173 					{
1174 						// Set linear gradient brush interpolation colors
1175 						ColorBlend colorBlend = new ColorBlend(5);
1176 						colorBlend.Colors[0] = darkColor;
1177 						colorBlend.Colors[1] = darkColor;
1178 						colorBlend.Colors[2] = lightColor;
1179 						colorBlend.Colors[3] = darkColor;
1180 						colorBlend.Colors[4] = darkColor;
1181 
1182 						colorBlend.Positions[0] = 0.0f;
1183 						colorBlend.Positions[1] = 0.0f;
1184 						colorBlend.Positions[2] = 0.5f;
1185 						colorBlend.Positions[3] = 1.0f;
1186 						colorBlend.Positions[4] = 1.0f;
1187 
1188 						brush.InterpolationColors = colorBlend;
1189 
1190 						// Fill path
1191 						this.Graph.Graphics.FillPath(brush, segmentPath);
1192 
1193 						// Draw path border
1194 						Pen pen = new Pen(point.BorderColor, point.BorderWidth);
1195 						pen.DashStyle = this.Graph.GetPenStyle( point.BorderDashStyle );
1196 						if(point.BorderWidth == 0 ||
1197 							point.BorderDashStyle == ChartDashStyle.NotSet ||
1198 							point.BorderColor == Color.Empty)
1199 						{
1200 							// Draw line of the darker color inside the cylinder
1201 							pen = new Pen(ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.3 ), 1);
1202 							pen.Alignment = PenAlignment.Inset;
1203 						}
1204 
1205 						pen.StartCap = LineCap.Round;
1206 						pen.EndCap = LineCap.Round;
1207 						pen.LineJoin = LineJoin.Bevel;
1208 						this.Graph.DrawPath(pen, segmentPath );
1209 						pen.Dispose();
1210 					}
1211 				}
1212 				else
1213 				{
1214 					// Fill graphics path
1215 					this.Graph.DrawPathAbs(
1216 						segmentPath,
1217 						(drawSegment) ? point.Color : Color.Transparent,
1218 						point.BackHatchStyle,
1219 						point.BackImage,
1220 						point.BackImageWrapMode,
1221 						point.BackImageTransparentColor,
1222 						point.BackImageAlignment,
1223 						point.BackGradientStyle,
1224 						(drawSegment) ? point.BackSecondaryColor : Color.Transparent,
1225 						(drawSegment) ? point.BorderColor : Color.Transparent,
1226 						point.BorderWidth,
1227 						point.BorderDashStyle,
1228 						PenAlignment.Center,
1229 						(drawSegmentShadow) ? point.series.ShadowOffset : 0,
1230 						point.series.ShadowColor);
1231 				}
1232 			}
1233 
1234 			if( this.Common.ProcessModeRegions )
1235 			{
1236 				// Add hot region
1237 				this.Common.HotRegionsList.AddHotRegion(
1238 					segmentPath,
1239 					false,
1240 					this.Graph,
1241 					point,
1242 					point.series.Name,
1243 					pointIndex);
1244 			}
1245 			segmentPath.Dispose();
1246 
1247 
1248 			// Add top 3D surface
1249 			if(this._rotation3D > 0f && startWidth > 0f && nothingOnTop)
1250 			{
1251 				if(this.Area.Area3DStyle.Enable3D)
1252 				{
1253 					PointF[] sidePoints = new PointF[4];
1254 					sidePoints[0] = new PointF(xCenterPointAbs + startWidth / 2f, location);
1255 					sidePoints[1] = new PointF(xCenterPointAbs, location + topRotationHeight);
1256 					sidePoints[2] = new PointF(xCenterPointAbs - startWidth / 2f, location);
1257 					sidePoints[3] = new PointF(xCenterPointAbs, location - topRotationHeight);
1258 					GraphicsPath topCurve = new GraphicsPath();
1259 					topCurve.AddClosedCurve(sidePoints, tension);
1260 
1261 					if( this.Common.ProcessModePaint )
1262 					{
1263 						// Fill graphics path
1264 						this.Graph.DrawPathAbs(
1265 							topCurve,
1266 							(drawSegment) ? ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.4 ) : Color.Transparent,
1267 							point.BackHatchStyle,
1268 							point.BackImage,
1269 							point.BackImageWrapMode,
1270 							point.BackImageTransparentColor,
1271 							point.BackImageAlignment,
1272 							point.BackGradientStyle,
1273 							(drawSegment) ? point.BackSecondaryColor : Color.Transparent,
1274 							(drawSegment) ? point.BorderColor : Color.Transparent,
1275 							point.BorderWidth,
1276 							point.BorderDashStyle,
1277 							PenAlignment.Center,
1278 							(drawSegmentShadow) ? point.series.ShadowOffset : 0,
1279 							point.series.ShadowColor);
1280 					}
1281 
1282 					if( this.Common.ProcessModeRegions )
1283 					{
1284 						// Add hot region
1285 						this.Common.HotRegionsList.AddHotRegion(
1286 							topCurve,
1287 							false,
1288 							this.Graph,
1289 							point,
1290 							point.series.Name,
1291 							pointIndex);
1292 					}
1293 					topCurve.Dispose();
1294 				}
1295 			}
1296 
1297 			// Add bottom 3D surface
1298 			if(this._rotation3D < 0f && startWidth > 0f && nothingOnBottom)
1299 			{
1300 				if(this.Area.Area3DStyle.Enable3D)
1301 				{
1302 					PointF[] sidePoints = new PointF[4];
1303 					sidePoints[0] = new PointF(xCenterPointAbs + endWidth / 2f, location + height);
1304 					sidePoints[1] = new PointF(xCenterPointAbs, location + height + bottomRotationHeight);
1305 					sidePoints[2] = new PointF(xCenterPointAbs - endWidth / 2f, location + height);
1306 					sidePoints[3] = new PointF(xCenterPointAbs, location + height - bottomRotationHeight);
1307 					GraphicsPath topCurve = new GraphicsPath();
1308 					topCurve.AddClosedCurve(sidePoints, tension);
1309 
1310 					if( this.Common.ProcessModePaint )
1311 					{
1312 						// Fill graphics path
1313 						this.Graph.DrawPathAbs(
1314 							topCurve,
1315 							(drawSegment) ? ChartGraphics.GetGradientColor( point.Color, Color.Black, 0.4 ) : Color.Transparent,
1316 							point.BackHatchStyle,
1317 							point.BackImage,
1318 							point.BackImageWrapMode,
1319 							point.BackImageTransparentColor,
1320 							point.BackImageAlignment,
1321 							point.BackGradientStyle,
1322 							(drawSegment) ? point.BackSecondaryColor : Color.Transparent,
1323 							(drawSegment) ? point.BorderColor : Color.Transparent,
1324 							point.BorderWidth,
1325 							point.BorderDashStyle,
1326 							PenAlignment.Center,
1327 							(drawSegmentShadow) ? point.series.ShadowOffset : 0,
1328 							point.series.ShadowColor);
1329 					}
1330 
1331 					if( this.Common.ProcessModeRegions )
1332 					{
1333 						// Add hot region
1334 						this.Common.HotRegionsList.AddHotRegion(
1335 							topCurve,
1336 							false,
1337 							this.Graph,
1338 							point,
1339 							point.series.Name,
1340 							pointIndex);
1341 					}
1342 					topCurve.Dispose();
1343 
1344 				}
1345 			}
1346 
1347 			// End Svg Selection mode
1348 			this.Graph.EndHotRegion( );
1349 		}
1350 
1351 
1352 		/// <summary>
1353 		/// Fill list with information about every segment of the funnel.
1354 		/// </summary>
1355 		/// <returns>Funnel segment information list.</returns>
GetFunnelSegmentPositions()1356 		private ArrayList GetFunnelSegmentPositions()
1357 		{
1358 			// Create new list
1359 			ArrayList list = new ArrayList();
1360 
1361 			// Funnel chart process only first series in the chart area
1362 			// and cannot be combined with any other chart types.
1363 			Series series = GetDataSeries();
1364 			if( series != null )
1365 			{
1366 				// Get funnel drawing style
1367 				this._funnelStyle = GetFunnelStyle(series);
1368 
1369 				// Check if round or square base is used in 3D chart
1370 				this.round3DShape = (GetFunnel3DDrawingStyle(series) == Funnel3DDrawingStyle.CircularBase);
1371 
1372 				// Get funnel points gap
1373 				this.funnelSegmentGap = GetFunnelPointGap(series);
1374 
1375 				// Get funnel neck size
1376 				this._funnelNeckSize = GetFunnelNeckSize(series);
1377 
1378 				// Loop through all ponts in the data series
1379 				float	currentLocation = this.Graph.GetAbsolutePoint(this.PlotAreaPosition.Location).Y;
1380 				if(this.isPyramid)
1381 				{
1382 					// Pyramid is drawn in reversed order.
1383 					currentLocation = this.Graph.GetAbsoluteRectangle(this.PlotAreaPosition).Bottom;
1384 				}
1385 				for( int pointIndex = 0; pointIndex >= 0 && pointIndex < series.Points.Count; pointIndex += 1 )
1386 				{
1387 					DataPoint point = series.Points[pointIndex];
1388 
1389 					// Check if first data point should be drawn
1390 					if( pointIndex > 0 || ShouldDrawFirstPoint() )
1391 					{
1392 						// Get height and width of each data point segment
1393 						float startWidth = 0f;
1394 						float endWidth = 0f;
1395 						float height = 0f;
1396 						GetPointWidthAndHeight(
1397 							series,
1398 							pointIndex,
1399 							currentLocation,
1400 							out height,
1401 							out startWidth,
1402 							out endWidth);
1403 
1404 						// Check visibility of previous and next points
1405 						bool nothingOnTop = false;
1406 						bool nothingOnBottom = false;
1407 						if(this.funnelSegmentGap > 0)
1408 						{
1409 							nothingOnTop = true;
1410 							nothingOnBottom = true;
1411 						}
1412 						else
1413 						{
1414 							if(ShouldDrawFirstPoint())
1415 							{
1416 								if(pointIndex == 0 ||
1417 									series.Points[pointIndex-1].Color.A != 255)
1418 								{
1419 									if(this.isPyramid)
1420 									{
1421 										nothingOnBottom = true;
1422 									}
1423 									else
1424 									{
1425 										nothingOnTop = true;
1426 									}
1427 								}
1428 							}
1429 							else
1430 							{
1431 								if(pointIndex == 1 ||
1432 									series.Points[pointIndex-1].Color.A != 255)
1433 								{
1434 									if(this.isPyramid)
1435 									{
1436 										nothingOnBottom = true;
1437 									}
1438 									else
1439 									{
1440 										nothingOnTop = true;
1441 									}
1442 								}
1443 							}
1444 							if( pointIndex == series.Points.Count - 1)
1445 							{
1446 								if(this.isPyramid)
1447 								{
1448 									nothingOnTop = true;
1449 								}
1450 								else
1451 								{
1452 									nothingOnBottom = true;
1453 								}
1454 							}
1455 							else if(series.Points[pointIndex+1].Color.A != 255)
1456 							{
1457 								if(this.isPyramid)
1458 								{
1459 									nothingOnTop = true;
1460 								}
1461 								else
1462 								{
1463 									nothingOnBottom = true;
1464 								}
1465 							}
1466 						}
1467 
1468 						// Add segment information
1469 						FunnelSegmentInfo info = new FunnelSegmentInfo();
1470 						info.Point = point;
1471 						info.PointIndex = pointIndex;
1472 						info.StartWidth = startWidth;
1473 						info.EndWidth = endWidth;
1474 						info.Location = (this.isPyramid) ? currentLocation - height : currentLocation;
1475 						info.Height = height;
1476 						info.NothingOnTop = nothingOnTop;
1477 						info.NothingOnBottom = nothingOnBottom;
1478 						list.Add(info);
1479 
1480 						// Increase current Y location
1481 						if(this.isPyramid)
1482 						{
1483 							currentLocation -= height + this.funnelSegmentGap;
1484 						}
1485 						else
1486 						{
1487 							currentLocation += height + this.funnelSegmentGap;
1488 						}
1489 					}
1490 				}
1491 			}
1492 
1493 			return list;
1494 		}
1495 
1496 		#endregion
1497 
1498 		#region Labels Methods
1499 
1500 		/// <summary>
1501 		/// Draws funnel data point labels.
1502 		/// </summary>
DrawLabels()1503 		private void DrawLabels()
1504 		{
1505 			// Loop through all labels
1506 			foreach(FunnelPointLabelInfo labelInfo in this.labelInfoList)
1507 			{
1508 				if(!labelInfo.Position.IsEmpty &&
1509 					!float.IsNaN(labelInfo.Position.X) &&
1510 					!float.IsNaN(labelInfo.Position.Y) &&
1511 					!float.IsNaN(labelInfo.Position.Width) &&
1512 					!float.IsNaN(labelInfo.Position.Height) )
1513 				{
1514 					// Start Svg Selection mode
1515 					this.Graph.StartHotRegion( labelInfo.Point );
1516 
1517 					// Get size of a single character used for spacing
1518 					SizeF spacing = this.Graph.MeasureString(
1519 						"W",
1520 						labelInfo.Point.Font,
1521 						new SizeF(1000f, 1000F),
1522 						StringFormat.GenericTypographic );
1523 
1524 					// Draw a callout line
1525 					if( !labelInfo.CalloutPoint1.IsEmpty &&
1526 						!labelInfo.CalloutPoint2.IsEmpty &&
1527 						!float.IsNaN(labelInfo.CalloutPoint1.X) &&
1528 						!float.IsNaN(labelInfo.CalloutPoint1.Y) &&
1529 						!float.IsNaN(labelInfo.CalloutPoint2.X) &&
1530 						!float.IsNaN(labelInfo.CalloutPoint2.Y) )
1531 					{
1532 						// Add spacing between text and callout line
1533 						if(labelInfo.OutsidePlacement == FunnelLabelPlacement.Right)
1534 						{
1535 							labelInfo.CalloutPoint2.X -= spacing.Width / 2f;
1536 
1537 							// Add a small spacing between a callout line and a segment
1538 							labelInfo.CalloutPoint1.X += 2;
1539 						}
1540 						else
1541 						{
1542 							labelInfo.CalloutPoint2.X += spacing.Width / 2f;
1543 
1544 							// Add a small spacing between a callout line and a segment
1545 							labelInfo.CalloutPoint1.X += 2;
1546 						}
1547 
1548 						// Get callout line color
1549 						Color lineColor = GetCalloutLineColor(labelInfo.Point);
1550 
1551 						// Draw callout line
1552 						this.Graph.DrawLineAbs(
1553 							lineColor,
1554 							1,
1555 							ChartDashStyle.Solid,
1556 							labelInfo.CalloutPoint1,
1557 							labelInfo.CalloutPoint2 );
1558 
1559 					}
1560 
1561 					// Get label background position
1562 					RectangleF labelBackPosition = labelInfo.Position;
1563 					labelBackPosition.Inflate(spacing.Width / 2f, spacing.Height / 8f);
1564 					labelBackPosition = this.Graph.GetRelativeRectangle(labelBackPosition);
1565 
1566 					// Center label in the middle of the background rectangle
1567                     using (StringFormat format = new StringFormat())
1568                     {
1569                         format.Alignment = StringAlignment.Center;
1570                         format.LineAlignment = StringAlignment.Center;
1571 
1572                         // Draw label text
1573                         using (Brush brush = new SolidBrush(labelInfo.Point.LabelForeColor))
1574                         {
1575 
1576                             this.Graph.DrawPointLabelStringRel(
1577                                 this.Common,
1578                                 labelInfo.Text,
1579                                 labelInfo.Point.Font,
1580                                 brush,
1581                                 labelBackPosition,
1582                                 format,
1583                                 labelInfo.Point.LabelAngle,
1584                                 labelBackPosition,
1585 
1586                                 labelInfo.Point.LabelBackColor,
1587                                 labelInfo.Point.LabelBorderColor,
1588                                 labelInfo.Point.LabelBorderWidth,
1589                                 labelInfo.Point.LabelBorderDashStyle,
1590                                 labelInfo.Point.series,
1591                                 labelInfo.Point,
1592                                 labelInfo.PointIndex);
1593                         }
1594 
1595                         // End Svg Selection mode
1596                         this.Graph.EndHotRegion();
1597                     }
1598 				}
1599 			}
1600 		}
1601 
1602 		/// <summary>
1603 		/// Creates a list of structures with the data point labels information.
1604 		/// </summary>
1605 		/// <returns>Array list of labels information.</returns>
CreateLabelsInfoList()1606 		private ArrayList CreateLabelsInfoList()
1607 		{
1608 			ArrayList list = new ArrayList();
1609 
1610 			// Get area position in pixels
1611 			RectangleF plotAreaPositionAbs = this.Graph.GetAbsoluteRectangle( this.Area.Position.ToRectangleF() );
1612 
1613 			// Get funnel chart type series
1614 			Series series = GetDataSeries();
1615 			if( series != null )
1616 			{
1617 				// Loop through all ponts in the data series
1618 				int pointIndex = 0;
1619 				foreach( DataPoint point in series.Points )
1620 				{
1621 					// Ignore empty points
1622 					if( !point.IsEmpty )
1623 					{
1624 						// Get some properties for performance
1625 						string	pointLabel = point.Label;
1626 						bool	pointShowLabelAsValue = point.IsValueShownAsLabel;
1627 
1628 						// Check if label text exists
1629 						if(pointShowLabelAsValue || pointLabel.Length > 0)
1630 						{
1631 							// Create new point label information class
1632 							FunnelPointLabelInfo labelInfo = new FunnelPointLabelInfo();
1633 							labelInfo.Point = point;
1634 							labelInfo.PointIndex = pointIndex;
1635 
1636 							// Get point label text
1637 							if( pointLabel.Length == 0 )
1638 							{
1639 								labelInfo.Text = ValueConverter.FormatValue(
1640 									point.series.Chart,
1641 									point,
1642                                     point.Tag,
1643 									point.YValues[0],
1644 									point.LabelFormat,
1645 									point.series.YValueType,
1646 									ChartElementType.DataPoint);
1647 							}
1648 							else
1649 							{
1650 								labelInfo.Text = point.ReplaceKeywords(pointLabel);
1651 							}
1652 
1653 							// Get label style
1654 							labelInfo.Style = GetLabelStyle(point);
1655 
1656 							// Get inside label vertical alignment
1657 							if(labelInfo.Style == FunnelLabelStyle.Inside)
1658 							{
1659 								labelInfo.VerticalAlignment = GetInsideLabelAlignment(point);
1660 							}
1661 
1662 							// Get outside labels placement
1663 							if(labelInfo.Style != FunnelLabelStyle.Inside)
1664 							{
1665 								labelInfo.OutsidePlacement = GetOutsideLabelPlacement(point);
1666 							}
1667 
1668 							// Measure string size
1669 							labelInfo.Size = this.Graph.MeasureString(
1670 								labelInfo.Text,
1671 								point.Font,
1672 								plotAreaPositionAbs.Size,
1673 								StringFormat.GenericTypographic);
1674 
1675 							// Add label information into the list
1676 							if(labelInfo.Text.Length > 0 &&
1677 								labelInfo.Style != FunnelLabelStyle.Disabled)
1678 							{
1679 								list.Add(labelInfo);
1680 							}
1681 						}
1682 					}
1683 					++pointIndex;
1684 				}
1685 			}
1686 			return list;
1687 		}
1688 
1689 		/// <summary>
1690 		/// Changes required plotting area spacing, so that all labels fit.
1691 		/// </summary>
1692 		/// <returns>Return True if no resizing required.</returns>
FitPointLabels()1693 		private bool FitPointLabels()
1694 		{
1695 			// Convert plotting area position to pixels.
1696 			// Make rectangle 4 pixels smaller on each side.
1697 			RectangleF plotAreaPositionAbs = this.Graph.GetAbsoluteRectangle(PlotAreaPosition);
1698 			plotAreaPositionAbs.Inflate(-4f, -4f);
1699 
1700 			// Get position of each label
1701 			GetLabelsPosition();
1702 
1703 			// Get spacing required to draw labels
1704 			RectangleF requiredSpacing = this.Graph.GetAbsoluteRectangle( new RectangleF(1f, 1f, 1f, 1f) );
1705 			foreach(FunnelPointLabelInfo labelInfo in this.labelInfoList)
1706 			{
1707 				// Add additional horizontal spacing for outside labels
1708 				RectangleF	position = labelInfo.Position;
1709 				if(labelInfo.Style == FunnelLabelStyle.Outside ||
1710 					labelInfo.Style == FunnelLabelStyle.OutsideInColumn)
1711 				{
1712 					float spacing = 10f;
1713 					if(labelInfo.OutsidePlacement == FunnelLabelPlacement.Right)
1714 					{
1715 						position.Width += spacing;
1716 					}
1717 					else if(labelInfo.OutsidePlacement == FunnelLabelPlacement.Left)
1718 					{
1719 						position.X -= spacing;
1720 						position.Width += spacing;
1721 					}
1722 				}
1723 
1724 				// Horizontal coordinates are ignored for Inside label style
1725 				if(labelInfo.Style != FunnelLabelStyle.Inside)
1726 				{
1727 					if( (plotAreaPositionAbs.X - position.X) > requiredSpacing.X )
1728 					{
1729 						requiredSpacing.X = plotAreaPositionAbs.X - position.X;
1730 					}
1731 
1732 					if( (position.Right - plotAreaPositionAbs.Right) > requiredSpacing.Width )
1733 					{
1734 						requiredSpacing.Width = position.Right - plotAreaPositionAbs.Right;
1735 					}
1736 				}
1737 
1738 				// Vertical spacing
1739 				if( (plotAreaPositionAbs.Y - position.Y) > requiredSpacing.Y )
1740 				{
1741 					requiredSpacing.Y = plotAreaPositionAbs.Y - position.Y;
1742 				}
1743 
1744 				if( (position.Bottom - plotAreaPositionAbs.Bottom) > requiredSpacing.Height )
1745 				{
1746 					requiredSpacing.Height = position.Bottom - plotAreaPositionAbs.Bottom;
1747 				}
1748 			}
1749 
1750 			// Convert spacing rectangle to relative coordinates
1751 			requiredSpacing = this.Graph.GetRelativeRectangle(requiredSpacing);
1752 
1753 			// Check if non-default spacing was used
1754 			if(requiredSpacing.X > 1f ||
1755 				requiredSpacing.Y > 1f ||
1756 				requiredSpacing.Width > 1f ||
1757 				requiredSpacing.Height > 1f )
1758 			{
1759 				this.plotAreaSpacing = requiredSpacing;
1760 
1761 				// Get NEW plotting area position
1762 				this.PlotAreaPosition = GetPlotAreaPosition();
1763 
1764 				// Get NEW list of segments
1765 				this.segmentList = GetFunnelSegmentPositions();
1766 
1767 				// Get NEW position of each label
1768 				GetLabelsPosition();
1769 
1770 				return false;
1771 			}
1772 
1773 			return true;
1774 		}
1775 
1776 		/// <summary>
1777 		/// Loops through the point labels list and calculates labels position
1778 		/// based on their size, position and funnel chart shape.
1779 		/// </summary>
GetLabelsPosition()1780 		private void GetLabelsPosition()
1781 		{
1782 			// Convert plotting area position to pixels
1783 			RectangleF plotAreaPositionAbs = this.Graph.GetAbsoluteRectangle(PlotAreaPosition);
1784 			float plotAreaCenterXAbs = plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f;
1785 
1786 			// Define label spacing
1787 			SizeF labelSpacing = new SizeF(3f, 3f);
1788 
1789 			//Loop through all labels
1790 			foreach(FunnelPointLabelInfo labelInfo in this.labelInfoList)
1791 			{
1792 				// Get ----osiated funnel segment information
1793 				bool	lastLabel = false;
1794 				int pointIndex = labelInfo.PointIndex + ((ShouldDrawFirstPoint()) ? 0 : 1);
1795 				if(pointIndex > this.segmentList.Count && !ShouldDrawFirstPoint() )
1796 				{
1797 					// Use last point index if first point is not drawn
1798 					pointIndex = this.segmentList.Count;
1799 					lastLabel = true;
1800 				}
1801 				FunnelSegmentInfo segmentInfo = null;
1802 				foreach(FunnelSegmentInfo info in this.segmentList)
1803 				{
1804 					if(info.PointIndex == pointIndex)
1805 					{
1806 						segmentInfo = info;
1807 						break;
1808 					}
1809 				}
1810 
1811 				// Check if segment was found
1812 				if(segmentInfo != null)
1813 				{
1814 					// Set label width and height
1815 					labelInfo.Position.Width = labelInfo.Size.Width;
1816 					labelInfo.Position.Height = labelInfo.Size.Height;
1817 
1818 					//******************************************************
1819 					//** Labels are placed OUTSIDE of the funnel
1820 					//******************************************************
1821 					if(labelInfo.Style == FunnelLabelStyle.Outside ||
1822 						labelInfo.Style == FunnelLabelStyle.OutsideInColumn)
1823 					{
1824 						// Define position
1825 						if( this._funnelStyle == FunnelStyle.YIsHeight )
1826 						{
1827 							// Get segment top and bottom diameter
1828 							float topDiameter = segmentInfo.StartWidth;
1829 							float bottomDiameter = segmentInfo.EndWidth;
1830 							if(!this.isPyramid)
1831 							{
1832 								if(topDiameter < this._funnelNeckSize.Width)
1833 								{
1834 									topDiameter = this._funnelNeckSize.Width;
1835 								}
1836 								if(bottomDiameter < this._funnelNeckSize.Width)
1837 								{
1838 									bottomDiameter = this._funnelNeckSize.Width;
1839 								}
1840 
1841 								// Adjust label position because segment is bent to make a neck
1842 								if(segmentInfo.StartWidth >= this._funnelNeckSize.Width &&
1843 									segmentInfo.EndWidth < this._funnelNeckSize.Width)
1844 								{
1845 									bottomDiameter = segmentInfo.EndWidth;
1846 								}
1847 							}
1848 
1849 							// Get Y position
1850 							labelInfo.Position.Y = (segmentInfo.Location + segmentInfo.Height / 2f) -
1851 								labelInfo.Size.Height / 2f;
1852 
1853 							// Get X position
1854 							if(labelInfo.Style == FunnelLabelStyle.OutsideInColumn)
1855 							{
1856 								if(labelInfo.OutsidePlacement == FunnelLabelPlacement.Right)
1857 								{
1858 									labelInfo.Position.X = plotAreaPositionAbs.Right +
1859 										4f * labelSpacing.Width;
1860 
1861 									// Set callout line coordinates
1862 									if(!this.isPyramid)
1863 									{
1864 										labelInfo.CalloutPoint1.X = plotAreaCenterXAbs +
1865 											Math.Max(this._funnelNeckSize.Width/2f, (topDiameter + bottomDiameter) / 4f);
1866 									}
1867 									else
1868 									{
1869 										labelInfo.CalloutPoint1.X = plotAreaCenterXAbs +
1870 											(topDiameter + bottomDiameter) / 4f;
1871 									}
1872 									labelInfo.CalloutPoint2.X = labelInfo.Position.X;
1873 								}
1874 								else
1875 								{
1876 									labelInfo.Position.X = plotAreaPositionAbs.X -
1877 										labelInfo.Size.Width -
1878 										4f * labelSpacing.Width;
1879 
1880 									// Set callout line coordinates
1881 									if(!this.isPyramid)
1882 									{
1883 										labelInfo.CalloutPoint1.X = plotAreaCenterXAbs -
1884 											Math.Max(this._funnelNeckSize.Width/2f, (topDiameter + bottomDiameter) / 4f);
1885 									}
1886 									else
1887 									{
1888 										labelInfo.CalloutPoint1.X = plotAreaCenterXAbs -
1889 											(topDiameter + bottomDiameter) / 4f;
1890 									}
1891 									labelInfo.CalloutPoint2.X = labelInfo.Position.Right;
1892 								}
1893 
1894 								// Fill rest of coordinates required for the callout line
1895 								labelInfo.CalloutPoint1.Y = segmentInfo.Location + segmentInfo.Height / 2f;
1896 								labelInfo.CalloutPoint2.Y = labelInfo.CalloutPoint1.Y;
1897 							}
1898 							else
1899 							{
1900 								if(labelInfo.OutsidePlacement == FunnelLabelPlacement.Right)
1901 								{
1902 									labelInfo.Position.X = plotAreaCenterXAbs +
1903 										(topDiameter + bottomDiameter) / 4f +
1904 										4f * labelSpacing.Width;
1905 								}
1906 								else
1907 								{
1908 									labelInfo.Position.X = plotAreaCenterXAbs -
1909 										labelInfo.Size.Width -
1910 										(topDiameter + bottomDiameter) / 4f -
1911 										4f * labelSpacing.Width;
1912 								}
1913 							}
1914 						}
1915 						else
1916 						{
1917 							// Use bottom part of the segment for the last point
1918 							if(lastLabel)
1919 							{
1920 								if(labelInfo.OutsidePlacement == FunnelLabelPlacement.Right)
1921 								{
1922 									labelInfo.Position.X = plotAreaCenterXAbs +
1923 										segmentInfo.EndWidth / 2f +
1924 										4f * labelSpacing.Width;
1925 								}
1926 								else
1927 								{
1928 									labelInfo.Position.X = plotAreaCenterXAbs -
1929 										labelInfo.Size.Width -
1930 										segmentInfo.EndWidth / 2f -
1931 										4f * labelSpacing.Width;
1932 								}
1933 								labelInfo.Position.Y = segmentInfo.Location +
1934 									segmentInfo.Height -
1935 									labelInfo.Size.Height / 2f;
1936 							}
1937 							else
1938 							{
1939 								if(labelInfo.OutsidePlacement == FunnelLabelPlacement.Right)
1940 								{
1941 									labelInfo.Position.X = plotAreaCenterXAbs +
1942 										segmentInfo.StartWidth / 2f +
1943 										4f * labelSpacing.Width;
1944 								}
1945 								else
1946 								{
1947 									labelInfo.Position.X = plotAreaCenterXAbs -
1948 										labelInfo.Size.Width -
1949 										segmentInfo.StartWidth / 2f -
1950 										4f * labelSpacing.Width;
1951 								}
1952 								labelInfo.Position.Y = segmentInfo.Location -
1953 									labelInfo.Size.Height / 2f;
1954 							}
1955 
1956 							if(labelInfo.Style == FunnelLabelStyle.OutsideInColumn)
1957 							{
1958 								if(labelInfo.OutsidePlacement == FunnelLabelPlacement.Right)
1959 								{
1960 									labelInfo.Position.X = plotAreaPositionAbs.Right +
1961 										4f * labelSpacing.Width;
1962 
1963 									// Set callout line coordinates
1964 									labelInfo.CalloutPoint1.X = plotAreaCenterXAbs +
1965 										( (lastLabel) ? segmentInfo.EndWidth : segmentInfo.StartWidth) / 2f;
1966 									labelInfo.CalloutPoint2.X = labelInfo.Position.X;
1967 
1968 								}
1969 								else
1970 								{
1971 									labelInfo.Position.X = plotAreaPositionAbs.X -
1972 										labelInfo.Size.Width -
1973 										4f * labelSpacing.Width;
1974 
1975 									// Set callout line coordinates
1976 									labelInfo.CalloutPoint1.X = plotAreaCenterXAbs -
1977 										( (lastLabel) ? segmentInfo.EndWidth : segmentInfo.StartWidth) / 2f;
1978 									labelInfo.CalloutPoint2.X = labelInfo.Position.Right;
1979 								}
1980 
1981 								// Fill rest of coordinates required for the callout line
1982 								labelInfo.CalloutPoint1.Y = segmentInfo.Location;
1983 								if(lastLabel)
1984 								{
1985 									labelInfo.CalloutPoint1.Y += segmentInfo.Height;
1986 								}
1987 								labelInfo.CalloutPoint2.Y = labelInfo.CalloutPoint1.Y;
1988 
1989 							}
1990 						}
1991 					}
1992 
1993 					//******************************************************
1994 					//** Labels are placed INSIDE of the funnel
1995 					//******************************************************
1996 					else if(labelInfo.Style == FunnelLabelStyle.Inside)
1997 					{
1998 						// Define position
1999 						labelInfo.Position.X = plotAreaCenterXAbs - labelInfo.Size.Width / 2f;
2000 						if( this._funnelStyle == FunnelStyle.YIsHeight )
2001 						{
2002 							labelInfo.Position.Y = (segmentInfo.Location + segmentInfo.Height / 2f) -
2003 								labelInfo.Size.Height / 2f;
2004 							if(labelInfo.VerticalAlignment == FunnelLabelVerticalAlignment.Top)
2005 							{
2006 								labelInfo.Position.Y -= segmentInfo.Height / 2f - labelInfo.Size.Height / 2f - labelSpacing.Height;
2007 							}
2008 							else if(labelInfo.VerticalAlignment == FunnelLabelVerticalAlignment.Bottom)
2009 							{
2010 								labelInfo.Position.Y += segmentInfo.Height / 2f - labelInfo.Size.Height / 2f - labelSpacing.Height;
2011 							}
2012 						}
2013 						else
2014 						{
2015 							labelInfo.Position.Y = segmentInfo.Location - labelInfo.Size.Height / 2f;
2016 							if(labelInfo.VerticalAlignment == FunnelLabelVerticalAlignment.Top)
2017 							{
2018 								labelInfo.Position.Y -= labelInfo.Size.Height / 2f + labelSpacing.Height;
2019 							}
2020 							else if(labelInfo.VerticalAlignment == FunnelLabelVerticalAlignment.Bottom)
2021 							{
2022 								labelInfo.Position.Y += labelInfo.Size.Height / 2f + labelSpacing.Height;
2023 							}
2024 
2025 							// Use bottom part of the segment for the last point
2026 							if(lastLabel)
2027 							{
2028 								labelInfo.Position.Y += segmentInfo.Height;
2029 							}
2030 						}
2031 
2032 						// Adjust label Y position in 3D
2033 						if(this.Area.Area3DStyle.Enable3D)
2034 						{
2035 							labelInfo.Position.Y += (float)( ( (segmentInfo.EndWidth + segmentInfo.StartWidth) / 4f) * Math.Sin(this._rotation3D / 180F * Math.PI) );
2036 						}
2037 					}
2038 
2039 					//******************************************************
2040 					//** Check if label overlaps any previous label
2041 					//******************************************************
2042 					int interation = 0;
2043 					while( IsLabelsOverlap(labelInfo) && interation < 1000)
2044 					{
2045 						float	shiftSize = (this.isPyramid) ? -3f : 3f;
2046 
2047 						// Move label down
2048 						labelInfo.Position.Y += shiftSize;
2049 
2050 						// Move callout second point down
2051 						if(!labelInfo.CalloutPoint2.IsEmpty)
2052 						{
2053 							labelInfo.CalloutPoint2.Y += shiftSize;
2054 						}
2055 
2056 						++interation;
2057 					}
2058 
2059 				}
2060 			}
2061 		}
2062 
2063 		/// <summary>
2064 		/// Checks if specified label overlaps any previous labels.
2065 		/// </summary>
2066 		/// <param name="testLabelInfo">Label to test.</param>
2067 		/// <returns>True if labels overlapp.</returns>
IsLabelsOverlap(FunnelPointLabelInfo testLabelInfo)2068 		private bool IsLabelsOverlap(FunnelPointLabelInfo testLabelInfo)
2069 		{
2070 			// Increase rectangle size by 1 pixel
2071 			RectangleF rect = testLabelInfo.Position;
2072 			rect.Inflate(1f, 1f);
2073 
2074 			// Increase label rectangle if border is drawn around the label
2075 			if(!testLabelInfo.Point.LabelBackColor.IsEmpty ||
2076 				(testLabelInfo.Point.LabelBorderWidth > 0 &&
2077 				!testLabelInfo.Point.LabelBorderColor.IsEmpty &&
2078 				testLabelInfo.Point.LabelBorderDashStyle != ChartDashStyle.NotSet) )
2079 			{
2080 				rect.Inflate(4f, 4f);
2081 			}
2082 
2083 			//Loop through all labels
2084 			foreach(FunnelPointLabelInfo labelInfo in this.labelInfoList)
2085 			{
2086 				// Stop searching
2087 				if(labelInfo.PointIndex == testLabelInfo.PointIndex)
2088 				{
2089 					break;
2090 				}
2091 
2092 				// Check if label position overlaps
2093 				if(!labelInfo.Position.IsEmpty &&
2094 					labelInfo.Position.IntersectsWith(rect) )
2095 				{
2096 					return true;
2097 				}
2098 			}
2099 
2100 			return false;
2101 		}
2102 
2103 		/// <summary>
2104 		/// Gets label style of the data point.
2105 		/// </summary>
2106 		/// <returns>Label style of the data point.</returns>
GetLabelStyle(DataPointCustomProperties properties)2107         private FunnelLabelStyle GetLabelStyle(DataPointCustomProperties properties)
2108 		{
2109 			// Set default label style
2110 			FunnelLabelStyle labelStyle = FunnelLabelStyle.OutsideInColumn;
2111 
2112 			// Get string value of the custom attribute
2113 			string attrValue = properties[this.funnelLabelStyleAttributeName];
2114 			if(attrValue != null && attrValue.Length > 0)
2115 			{
2116 				// Convert string to the labels style
2117 				try
2118 				{
2119 					labelStyle = (FunnelLabelStyle)Enum.Parse(typeof(FunnelLabelStyle), attrValue, true);
2120 				}
2121 				catch
2122 				{
2123 					throw(new InvalidOperationException( SR.ExceptionCustomAttributeValueInvalid(labelStyle.ToString(), this.funnelLabelStyleAttributeName) ) );
2124 				}
2125 			}
2126 			return labelStyle;
2127 		}
2128 
2129 		#endregion // Labels Methods
2130 
2131 		#region Position Methods
2132 
2133 		/// <summary>
2134 		/// Calculate the spacing required for the labels.
2135 		/// </summary>
GetPlotAreaSpacing()2136 		private void GetPlotAreaSpacing()
2137 		{
2138 			// Provide small spacing on the sides of chart area
2139 			this.plotAreaSpacing = new RectangleF(1f, 1f, 1f, 1f);
2140 
2141 			// Get plotting area position
2142 			this.PlotAreaPosition = GetPlotAreaPosition();
2143 
2144 			// Get list of segments
2145 			this.segmentList = GetFunnelSegmentPositions();
2146 
2147 			// If plotting area position is automatic
2148 			if( Area.InnerPlotPosition.Auto )
2149 			{
2150 				// Set a position so that data labels fit
2151 				// This method is called several time to adjust label position while
2152 				// funnel side angle is changed
2153 				int iteration = 0;
2154 				while(!FitPointLabels() && iteration < 5)
2155 				{
2156 					iteration++;
2157 				}
2158 			}
2159 			else
2160 			{
2161 				// Just get labels position
2162 				GetLabelsPosition();
2163 			}
2164 
2165 		}
2166 
2167 		/// <summary>
2168 		/// Gets a rectangle in relative coordinates where the funnel will chart
2169 		/// will be drawn.
2170 		/// </summary>
2171 		/// <returns>Plotting are of the chart in relative coordinates.</returns>
GetPlotAreaPosition()2172 		private RectangleF GetPlotAreaPosition()
2173 		{
2174 			// Get plotting area rectangle position
2175 			RectangleF	plotAreaPosition = ( Area.InnerPlotPosition.Auto ) ?
2176 				Area.Position.ToRectangleF() : Area.PlotAreaPosition.ToRectangleF();
2177 
2178 			// NOTE: Fixes issue #4085
2179 			// Do not allow decreasing of the plot area height more than 50%
2180 			if(plotAreaSpacing.Y > plotAreaPosition.Height / 2f)
2181 			{
2182 				plotAreaSpacing.Y = plotAreaPosition.Height / 2f;
2183 			}
2184 			if(plotAreaSpacing.Height > plotAreaPosition.Height / 2f)
2185 			{
2186 				plotAreaSpacing.Height = plotAreaPosition.Height / 2f;
2187 			}
2188 
2189 			// Decrease plotting are position using pre-calculated ratio
2190 			plotAreaPosition.X += plotAreaSpacing.X;
2191 			plotAreaPosition.Y += plotAreaSpacing.Y;
2192 			plotAreaPosition.Width -= plotAreaSpacing.X + plotAreaSpacing.Width;
2193 			plotAreaPosition.Height -= plotAreaSpacing.Y + plotAreaSpacing.Height;
2194 
2195 			// Apply vertical spacing on top and bottom to fit the 3D surfaces
2196 			if(this.Area.Area3DStyle.Enable3D)
2197 			{
2198 				// Convert position to pixels
2199 				RectangleF plotAreaPositionAbs = this.Graph.GetAbsoluteRectangle(plotAreaPosition);
2200 
2201 				// Funnel chart process only first series in the chart area
2202 				// and cannot be combined with any other chart types.
2203 				Series series = GetDataSeries();
2204 				if( series != null )
2205 				{
2206 					// Get 3D funnel rotation angle (from 10 to -10)
2207 					this._rotation3D = GetFunnelRotation(series);
2208 				}
2209 
2210 				// Get top and bottom spacing
2211 				float	topSpacing = (float)Math.Abs( (plotAreaPositionAbs.Width/ 2f) * Math.Sin(this._rotation3D / 180F * Math.PI) );
2212 				float	bottomSpacing = (float)Math.Abs( (plotAreaPositionAbs.Width/ 2f) * Math.Sin(this._rotation3D / 180F * Math.PI) );
2213 
2214 				// Adjust position
2215 				if(this.isPyramid)
2216 				{
2217 					// Only bottom spacing for the pyramid
2218 					plotAreaPositionAbs.Height -= bottomSpacing;
2219 				}
2220 				else
2221 				{
2222 					// Add top/bottom spacing
2223 					plotAreaPositionAbs.Y += topSpacing;
2224 					plotAreaPositionAbs.Height -= topSpacing + bottomSpacing;
2225 				}
2226 
2227 				// Convert position back to relative coordinates
2228 				plotAreaPosition = this.Graph.GetRelativeRectangle(plotAreaPositionAbs);
2229 			}
2230 
2231 			return plotAreaPosition;
2232 		}
2233 
2234 		#endregion // Position Methods
2235 
2236 		#region Helper Methods
2237 
2238 		/// <summary>
2239 		/// Checks for minimum segment height.
2240 		/// </summary>
2241 		/// <param name="height">Current segment height.</param>
2242 		/// <returns>Adjusted segment height.</returns>
CheckMinHeight(float height)2243 		protected float CheckMinHeight(float height)
2244 		{
2245 			// When point gap is used do not allow to have the segment heigth to be zero.
2246 			float minSize = Math.Min(2f, this.funnelSegmentGap / 2f);
2247 			if(this.funnelSegmentGap > 0 &&
2248 				height < minSize)
2249 			{
2250 				return minSize;
2251 			}
2252 
2253 			return height;
2254 		}
2255 
2256 		/// <summary>
2257 		/// Gets minimum point height in pixels.
2258 		/// </summary>
2259 		/// <returns>Minimum point height in pixels.</returns>
GetFunnelMinPointHeight(DataPointCustomProperties properties)2260         private void GetFunnelMinPointHeight(DataPointCustomProperties properties)
2261 		{
2262 			// Set default minimum point size
2263 			this._funnelMinPointHeight = 0f;
2264 
2265 			// Get string value of the custom attribute
2266             string attrValue = properties[this.funnelPointMinHeight];
2267             if (attrValue != null && attrValue.Length > 0)
2268             {
2269                 // Convert string to the point gap size
2270 
2271                 float pointHeight;
2272                 bool parseSucceed = float.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out pointHeight);
2273                 if (parseSucceed)
2274                 {
2275                     this._funnelMinPointHeight = pointHeight;
2276                 }
2277 
2278                 if (!parseSucceed || this._funnelMinPointHeight < 0f || this._funnelMinPointHeight > 100f)
2279                 {
2280                     throw (new InvalidOperationException(SR.ExceptionFunnelMinimumPointHeightAttributeInvalid));
2281                 }
2282 
2283                 // Check if specified value is too big
2284                 this._funnelMinPointHeight = (float)(this.yValueTotal * this._funnelMinPointHeight / 100f);
2285 
2286                 // Get data statistic again using Min value
2287                 GetDataPointValuesStatistic();
2288             }
2289 
2290 			return;
2291 		}
2292 
2293 		/// <summary>
2294 		/// Gets 3D funnel rotation angle.
2295 		/// </summary>
2296 		/// <returns>Rotation angle.</returns>
GetFunnelRotation(DataPointCustomProperties properties)2297         private int GetFunnelRotation(DataPointCustomProperties properties)
2298 		{
2299 			// Set default gap size
2300 			int	angle = 5;
2301 
2302 			// Get string value of the custom attribute
2303 			string attrValue = properties[this.funnelRotationAngleAttributeName];
2304             if (attrValue != null && attrValue.Length > 0)
2305             {
2306                 // Convert string to the point gap size
2307 
2308                 int a;
2309                 bool parseSucceed = int.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out a);
2310                 if (parseSucceed)
2311                 {
2312                     angle = a;
2313                 }
2314 
2315                 // Validate attribute value
2316                 if (!parseSucceed || angle < -10 || angle > 10)
2317                 {
2318                     throw (new InvalidOperationException(SR.ExceptionFunnelAngleRangeInvalid));
2319                 }
2320             }
2321 
2322 			return angle;
2323 		}
2324 
2325 		/// <summary>
2326 		/// Gets callout line color.
2327 		/// </summary>
2328 		/// <returns>Callout line color.</returns>
GetCalloutLineColor(DataPointCustomProperties properties)2329         private Color GetCalloutLineColor(DataPointCustomProperties properties)
2330 		{
2331 			// Set default gap size
2332 			Color	color = Color.Black;
2333 
2334 			// Get string value of the custom attribute
2335 			string attrValue = properties[CustomPropertyName.CalloutLineColor];
2336 			if(attrValue != null && attrValue.Length > 0)
2337 			{
2338 				// Convert string to Color
2339 				bool	failed = false;
2340 				ColorConverter colorConverter = new ColorConverter();
2341                 try
2342                 {
2343                     color = (Color)colorConverter.ConvertFromInvariantString(attrValue);
2344                 }
2345                 catch (ArgumentException)
2346                 {
2347                     failed = true;
2348                 }
2349                 catch (NotSupportedException)
2350                 {
2351                     failed = true;
2352                 }
2353 
2354 				// In case of an error try to convert using local settings
2355 				if(failed)
2356 				{
2357 					try
2358 					{
2359 						color = (Color)colorConverter.ConvertFromString(attrValue);
2360 					}
2361 					catch(ArgumentException)
2362 					{
2363 						throw(new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid( attrValue, "CalloutLineColor") ) );
2364 					}
2365 				}
2366 
2367 			}
2368 
2369 			return color;
2370 		}
2371 
2372 		/// <summary>
2373 		/// Gets funnel neck size when shape of the funnel do not change.
2374 		/// </summary>
2375 		/// <returns>Funnel neck width and height.</returns>
GetFunnelNeckSize(DataPointCustomProperties properties)2376         private SizeF GetFunnelNeckSize(DataPointCustomProperties properties)
2377 		{
2378 			// Set default gap size
2379 			SizeF	neckSize = new SizeF(5f, 5f);
2380 
2381 			// Get string value of the custom attribute
2382 			string attrValue = properties[CustomPropertyName.FunnelNeckWidth];
2383             if (attrValue != null && attrValue.Length > 0)
2384             {
2385                 // Convert string to the point gap size
2386 
2387                 float w;
2388                 bool parseSucceed = float.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out w);
2389                 if (parseSucceed)
2390                 {
2391                     neckSize.Width = w;
2392                 }
2393 
2394                 // Validate attribute value
2395                 if (!parseSucceed || neckSize.Width < 0 || neckSize.Width > 100)
2396                 {
2397                     throw (new InvalidOperationException(SR.ExceptionFunnelNeckWidthInvalid));
2398                 }
2399             }
2400 
2401 			// Get string value of the custom attribute
2402 			attrValue = properties[CustomPropertyName.FunnelNeckHeight];
2403             if (attrValue != null && attrValue.Length > 0)
2404             {
2405                 // Convert string to the point gap size
2406                 float h;
2407                 bool parseSucceed = float.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out h);
2408                 if (parseSucceed)
2409                 {
2410                     neckSize.Height = h;
2411                 }
2412 
2413 
2414                 if (!parseSucceed || neckSize.Height < 0 || neckSize.Height > 100)
2415                 {
2416                     throw (new InvalidOperationException(SR.ExceptionFunnelNeckHeightInvalid));
2417                 }
2418             }
2419 
2420 			// Make sure the neck size do not exceed the plotting area size
2421 			if(neckSize.Height > this.PlotAreaPosition.Height/2f)
2422 			{
2423 				neckSize.Height = this.PlotAreaPosition.Height/2f;
2424 			}
2425 			if(neckSize.Width > this.PlotAreaPosition.Width/2f)
2426 			{
2427 				neckSize.Width = this.PlotAreaPosition.Width/2f;
2428 			}
2429 
2430 			// Convert from relative coordinates to pixels
2431 			return this.Graph.GetAbsoluteSize(neckSize);
2432 		}
2433 
2434 		/// <summary>
2435 		/// Gets gap between points in pixels.
2436 		/// </summary>
2437 		/// <returns>Gap between funnel points.</returns>
GetFunnelPointGap(DataPointCustomProperties properties)2438         private float GetFunnelPointGap(DataPointCustomProperties properties)
2439 		{
2440 			// Set default gap size
2441 			float	gapSize = 0f;
2442 
2443 			// Get string value of the custom attribute
2444 			string attrValue = properties[this.funnelPointGapAttributeName];
2445             if (attrValue != null && attrValue.Length > 0)
2446             {
2447                 // Convert string to the point gap size
2448 
2449                 float gs;
2450                 bool parseSucceed = float.TryParse(attrValue, NumberStyles.Any, CultureInfo.InvariantCulture, out gs);
2451                 if (parseSucceed)
2452                 {
2453                     gapSize = gs;
2454                 }
2455                 else
2456                 {
2457                     throw (new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid(attrValue, this.funnelPointGapAttributeName)));
2458                 }
2459 
2460                 // Make sure the total gap size for all points do not exceed the total height of the plotting area
2461                 float maxGapSize = this.PlotAreaPosition.Height / (this.pointNumber - ((ShouldDrawFirstPoint()) ? 1 : 2));
2462                 if (gapSize > maxGapSize)
2463                 {
2464                     gapSize = maxGapSize;
2465                 }
2466                 if (gapSize < 0)
2467                 {
2468                     gapSize = 0;
2469                 }
2470 
2471                 // Convert from relative coordinates to pixels
2472                 gapSize = this.Graph.GetAbsoluteSize(new SizeF(gapSize, gapSize)).Height;
2473             }
2474 
2475 			return gapSize;
2476 		}
2477 
2478 		/// <summary>
2479 		/// Gets funnel drawing style.
2480 		/// </summary>
2481 		/// <returns>funnel drawing style.</returns>
GetFunnelStyle(DataPointCustomProperties properties)2482         private FunnelStyle GetFunnelStyle(DataPointCustomProperties properties)
2483 		{
2484 			// Set default funnel drawing style
2485 			FunnelStyle drawingStyle = FunnelStyle.YIsHeight;
2486 
2487 			// Get string value of the custom attribute
2488 			if(!this.isPyramid)
2489 			{
2490 				string attrValue = properties[CustomPropertyName.FunnelStyle];
2491 				if(attrValue != null && attrValue.Length > 0)
2492 				{
2493 					// Convert string to the labels style
2494 					try
2495 					{
2496 						drawingStyle = (FunnelStyle)Enum.Parse(typeof(FunnelStyle), attrValue, true);
2497 					}
2498 					catch
2499 					{
2500 						throw(new InvalidOperationException( SR.ExceptionCustomAttributeValueInvalid( attrValue, "FunnelStyle") ) );
2501 					}
2502 				}
2503 			}
2504 			return drawingStyle;
2505 		}
2506 
2507 		/// <summary>
2508 		/// Gets outside labels placement.
2509 		/// </summary>
2510 		/// <returns>Outside labels placement.</returns>
GetOutsideLabelPlacement(DataPointCustomProperties properties)2511         private FunnelLabelPlacement GetOutsideLabelPlacement(DataPointCustomProperties properties)
2512 		{
2513 			// Set default vertical alignment for the inside labels
2514 			FunnelLabelPlacement placement = FunnelLabelPlacement.Right;
2515 
2516 			// Get string value of the custom attribute
2517 			string attrValue = properties[this.funnelOutsideLabelPlacementAttributeName];
2518 			if(attrValue != null && attrValue.Length > 0)
2519 			{
2520 				// Convert string to the labels placement
2521 				try
2522 				{
2523 					placement = (FunnelLabelPlacement)Enum.Parse(typeof(FunnelLabelPlacement), attrValue, true);
2524 				}
2525 				catch
2526 				{
2527                     throw (new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid(attrValue, this.funnelOutsideLabelPlacementAttributeName )));
2528 				}
2529 			}
2530 			return placement;
2531 		}
2532 
2533 		/// <summary>
2534 		/// Gets inside labels vertical alignment.
2535 		/// </summary>
2536 		/// <returns>Inside labels vertical alignment.</returns>
GetInsideLabelAlignment(DataPointCustomProperties properties)2537         private FunnelLabelVerticalAlignment GetInsideLabelAlignment(DataPointCustomProperties properties)
2538 		{
2539 			// Set default vertical alignment for the inside labels
2540 			FunnelLabelVerticalAlignment alignment = FunnelLabelVerticalAlignment.Center;
2541 
2542 			// Get string value of the custom attribute
2543 			string attrValue = properties[this.funnelInsideLabelAlignmentAttributeName];
2544 			if(attrValue != null && attrValue.Length > 0)
2545 			{
2546 				// Convert string to the labels style
2547 				try
2548 				{
2549 					alignment = (FunnelLabelVerticalAlignment)Enum.Parse(typeof(FunnelLabelVerticalAlignment), attrValue, true);
2550 				}
2551 				catch
2552 				{
2553                     throw (new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid(attrValue, this.funnelInsideLabelAlignmentAttributeName)));
2554 				}
2555 			}
2556 			return alignment;
2557 		}
2558 
2559 		/// <summary>
2560 		/// Gets funnel 3D drawing style.
2561 		/// </summary>
2562 		/// <returns>funnel drawing style.</returns>
GetFunnel3DDrawingStyle(DataPointCustomProperties properties)2563         private Funnel3DDrawingStyle GetFunnel3DDrawingStyle(DataPointCustomProperties properties)
2564 		{
2565 			// Set default funnel drawing style
2566 			Funnel3DDrawingStyle drawingStyle = (this.isPyramid) ?
2567 				Funnel3DDrawingStyle.SquareBase : Funnel3DDrawingStyle.CircularBase;
2568 
2569 			// Get string value of the custom attribute
2570 			string attrValue = properties[funnel3DDrawingStyleAttributeName];
2571 			if(attrValue != null && attrValue.Length > 0)
2572 			{
2573 				// Convert string to the labels style
2574 				try
2575 				{
2576 					drawingStyle = (Funnel3DDrawingStyle)Enum.Parse(typeof(Funnel3DDrawingStyle), attrValue, true);
2577 				}
2578 				catch
2579 				{
2580                     throw (new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid(attrValue, funnel3DDrawingStyleAttributeName) ) );
2581 				}
2582 			}
2583 
2584 			return drawingStyle;
2585 		}
2586 
2587 		/// <summary>
2588 		/// Get data point Y and X values statistics:
2589 		///   - Total of all Y values
2590 		///   - Total of all X values
2591 		///   - Maximum Y value
2592 		/// Negative values are treated as positive.
2593 		/// </summary>
GetDataPointValuesStatistic()2594 		private void GetDataPointValuesStatistic()
2595 		{
2596 			// Get funnel chart type series
2597 			Series series = GetDataSeries();
2598 			if( series != null )
2599 			{
2600 				// Reset values
2601 				this.yValueTotal = 0.0;
2602 				this._xValueTotal = 0.0;
2603 				this._yValueMax = 0.0;
2604 				this.pointNumber = 0;
2605 
2606 				// Get value type
2607 				this._valuePercentages = null;
2608 				PyramidValueType valueType = this.GetPyramidValueType( series );
2609 				if(valueType == PyramidValueType.Surface)
2610 				{
2611 					// Calculate the total surface area
2612 					double triangleArea = 0.0;
2613 					int pointIndex = 0;
2614 					foreach( DataPoint point in series.Points )
2615 					{
2616 						// Ignore empty points
2617 						if( !point.IsEmpty )
2618 						{
2619 							// Get Y value
2620 							triangleArea += GetYValue(point, pointIndex);
2621 						}
2622 						++pointIndex;
2623 					}
2624 
2625 					// Calculate the base
2626 					double triangleHeight = 100.0;
2627 					double triangleBase = (2* triangleArea) / triangleHeight;
2628 
2629 					// Calculate the base to height ratio
2630 					double baseRatio = triangleBase / triangleHeight;
2631 
2632 					// Calcuate the height percentage for each value
2633 					double[] percentages = new double[series.Points.Count];
2634 					double sumArea = 0.0;
2635 					for(int loop = 0; loop < percentages.Length; loop++)
2636 					{
2637 						double yValue = GetYValue(series.Points[loop], loop);
2638 						sumArea += yValue;
2639 						percentages[loop] = Math.Sqrt((2 * sumArea) / baseRatio);
2640 					}
2641 					this._valuePercentages = percentages;
2642 				}
2643 
2644 				// Loop through all ponts in the data series
2645 				foreach( DataPoint point in series.Points )
2646 				{
2647 					// Ignore empty points
2648 					if( !point.IsEmpty )
2649 					{
2650 						// Get Y value
2651 						double yValue = GetYValue(point, this.pointNumber);
2652 
2653 						// Get data point Y and X values statistics
2654 						this.yValueTotal += yValue;
2655 						this._yValueMax = Math.Max(this._yValueMax, yValue);
2656 						this._xValueTotal += GetXValue(point);
2657 					}
2658 
2659 					++this.pointNumber;
2660 				}
2661 
2662 			}
2663 		}
2664 
2665 		/// <summary>
2666 		/// Gets funnel chart series that belongs to the current chart area.
2667 		/// Method also checks that only one visible Funnel series exists in the chart area.
2668 		/// </summary>
2669 		/// <returns>Funnel chart type series.</returns>
GetDataSeries()2670 		private Series GetDataSeries()
2671 		{
2672 			// Check if funnel series was already found
2673 			if(this._chartTypeSeries == null)
2674 			{
2675 				// Loop through all series
2676 				Series funnelSeries = null;
2677 				foreach( Series series in Common.DataManager.Series )
2678 				{
2679 					// Check if series is visible and belong to the current chart area
2680 					if( series.IsVisible() &&
2681 						series.ChartArea == this.Area.Name )
2682 					{
2683 						// Check series chart type is Funnel
2684 						if( String.Compare( series.ChartTypeName, this.Name, true, System.Globalization.CultureInfo.CurrentCulture ) == 0 )
2685 						{
2686 							if(funnelSeries == null)
2687 							{
2688 								funnelSeries = series;
2689 							}
2690 						}
2691 						else if(!this.Common.ChartPicture.SuppressExceptions)
2692 						{
2693 							// Funnel chart can not be combined with other chart type
2694                             throw (new InvalidOperationException(SR.ExceptionFunnelCanNotCombine));
2695 						}
2696 					}
2697 				}
2698 
2699 				// Remember the chart type series
2700 				this._chartTypeSeries = funnelSeries;
2701 			}
2702 
2703 			return this._chartTypeSeries;
2704 		}
2705 
2706 		/// <summary>
2707 		/// Gets pyramid value type. Each point value may represent a "Linear" height of
2708 		/// the segment or "Surface" of the segment.
2709 		/// </summary>
2710 		/// <returns>Pyramid value type.</returns>
GetPyramidValueType(DataPointCustomProperties properties)2711         private PyramidValueType GetPyramidValueType(DataPointCustomProperties properties)
2712 		{
2713 			// Set default funnel drawing style
2714 			PyramidValueType valueType = PyramidValueType.Linear;
2715 
2716 			// Get string value of the custom attribute
2717 			if(this.isPyramid)
2718 			{
2719 				string attrValue = properties[CustomPropertyName.PyramidValueType];
2720 				if(attrValue != null && attrValue.Length > 0)
2721 				{
2722 					// Convert string to the labels style
2723 					try
2724 					{
2725 						valueType = (PyramidValueType)Enum.Parse(typeof(PyramidValueType), attrValue, true);
2726 					}
2727 					catch
2728 					{
2729                         throw (new InvalidOperationException(SR.ExceptionCustomAttributeValueInvalid(attrValue,"PyramidValueType") ) );
2730 					}
2731 				}
2732 			}
2733 			return valueType;
2734 		}
2735 
2736 		#endregion // Helper Methods
2737 
2738 		#region Y & X values related methods
2739 
2740 		/// <summary>
2741 		/// Helper function, which returns the Y value of the point.
2742 		/// </summary>
2743 		/// <param name="point">Point object.</param>
2744 		/// <param name="pointIndex">Point index.</param>
2745 		/// <returns>Y value of the point.</returns>
GetYValue(DataPoint point, int pointIndex)2746 		virtual public double GetYValue(DataPoint point, int pointIndex)
2747 		{
2748 			double	yValue = 0.0;
2749 			if( !point.IsEmpty )
2750 			{
2751 				// Get Y value
2752 				yValue = point.YValues[0];
2753 
2754 				// Adjust point value
2755 				if(this._valuePercentages != null &&
2756 					this._valuePercentages.Length > pointIndex )
2757 				{
2758 					yValue = yValue / 100.0 * this._valuePercentages[pointIndex];
2759 				}
2760 
2761 				if(this.Area.AxisY.IsLogarithmic)
2762 				{
2763 					yValue = Math.Abs(Math.Log( yValue, this.Area.AxisY.LogarithmBase ));
2764 				}
2765 				else
2766 				{
2767 					yValue = Math.Abs( yValue );
2768 					if(yValue < this._funnelMinPointHeight)
2769 					{
2770 						yValue = this._funnelMinPointHeight;
2771 					}
2772 				}
2773 			}
2774 			return yValue;
2775 		}
2776 
2777 		/// <summary>
2778 		/// Helper function, which returns the X value of the point.
2779 		/// </summary>
2780 		/// <param name="point">Point object.</param>
2781 		/// <returns>X value of the point.</returns>
GetXValue(DataPoint point)2782 		virtual public double GetXValue(DataPoint point)
2783 		{
2784 			if(this.Area.AxisX.IsLogarithmic)
2785 			{
2786 				return Math.Abs(Math.Log( point.XValue, this.Area.AxisX.LogarithmBase ));
2787 			}
2788 			return Math.Abs(point.XValue);
2789 		}
2790 
2791 		/// <summary>
2792 		/// Helper function, which returns the Y value of the point.
2793 		/// </summary>
2794 		/// <param name="common">Chart common elements.</param>
2795 		/// <param name="area">Chart area the series belongs to.</param>
2796 		/// <param name="series">Sereis of the point.</param>
2797 		/// <param name="point">Point object.</param>
2798 		/// <param name="pointIndex">Index of the point.</param>
2799 		/// <param name="yValueIndex">Index of the Y value to get.</param>
2800 		/// <returns>Y value of the point.</returns>
GetYValue( CommonElements common, ChartArea area, Series series, DataPoint point, int pointIndex, int yValueIndex)2801 		virtual public double GetYValue(
2802 			CommonElements common,
2803 			ChartArea area,
2804 			Series series,
2805 			DataPoint point,
2806 			int pointIndex,
2807 			int yValueIndex)
2808 		{
2809 			return point.YValues[yValueIndex];
2810 		}
2811 
2812 		#endregion // Y & X values related methods
2813 
2814 		#region SmartLabelStyle methods
2815 
2816 		/// <summary>
2817 		/// Adds markers position to the list. Used to check SmartLabelStyle overlapping.
2818 		/// </summary>
2819 		/// <param name="common">Common chart elements.</param>
2820 		/// <param name="area">Chart area.</param>
2821 		/// <param name="series">Series values to be used.</param>
2822 		/// <param name="list">List to add to.</param>
AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list)2823 		public void AddSmartLabelMarkerPositions(CommonElements common, ChartArea area, Series series, ArrayList list)
2824 		{
2825 			// Fast Line chart type do not support labels
2826 		}
2827 
2828 		#endregion
2829 
2830         #region IDisposable interface implementation
2831         /// <summary>
2832         /// Releases unmanaged and - optionally - managed resources
2833         /// </summary>
2834         /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
Dispose(bool disposing)2835         protected virtual void Dispose(bool disposing)
2836         {
2837             //Nothing to dispose at the base class.
2838         }
2839 
2840         /// <summary>
2841         /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
2842         /// </summary>
Dispose()2843         public void Dispose()
2844         {
2845             Dispose(true);
2846             GC.SuppressFinalize(this);
2847         }
2848         #endregion
2849 	}
2850 
2851 	/// <summary>
2852 	/// PyramidChart class overrides some of the functionality of FunnelChart class.
2853     /// Most of drawing and othere processing is done in the FunnelChart.
2854 	/// </summary>
2855 	internal class PyramidChart : FunnelChart
2856 	{
2857 		#region Fields and Constructor
2858 
2859 		/// <summary>
2860 		/// Default constructor
2861 		/// </summary>
PyramidChart()2862 		public PyramidChart()
2863 		{
2864 			// Renering of the pyramid chart type
2865 			base.isPyramid = true;
2866 
2867 			// Pyramid chart type uses square base by default
2868 			base.round3DShape = false;
2869 
2870 			// Pyramid properties names
2871 			base.funnelLabelStyleAttributeName = CustomPropertyName.PyramidLabelStyle;
2872 			base.funnelPointGapAttributeName = CustomPropertyName.PyramidPointGap;
2873 			base.funnelRotationAngleAttributeName = CustomPropertyName.Pyramid3DRotationAngle;
2874 			base.funnelPointMinHeight = CustomPropertyName.PyramidMinPointHeight;
2875 			base.funnel3DDrawingStyleAttributeName = CustomPropertyName.Pyramid3DDrawingStyle;
2876 			base.funnelInsideLabelAlignmentAttributeName = CustomPropertyName.PyramidInsideLabelAlignment;
2877 			base.funnelOutsideLabelPlacementAttributeName = CustomPropertyName.PyramidOutsideLabelPlacement;
2878 		}
2879 
2880 		#endregion
2881 
2882 		#region IChartType interface implementation
2883 
2884 		/// <summary>
2885 		/// Chart type name
2886 		/// </summary>
2887 		override public string Name			{ get{ return ChartTypeNames.Pyramid;}}
2888 
2889 		#endregion
2890 
2891 		#region Methods
2892 
2893 		/// <summary>
2894 		/// Gets pyramid data point segment height and width.
2895 		/// </summary>
2896 		/// <param name="series">Chart type series.</param>
2897 		/// <param name="pointIndex">Data point index in the series.</param>
2898 		/// <param name="location">Segment top location. Bottom location if reversed drawing order.</param>
2899 		/// <param name="height">Returns the height of the segment.</param>
2900 		/// <param name="startWidth">Returns top width of the segment.</param>
2901 		/// <param name="endWidth">Returns botom width of the segment.</param>
GetPointWidthAndHeight( Series series, int pointIndex, float location, out float height, out float startWidth, out float endWidth)2902 		protected override void GetPointWidthAndHeight(
2903 			Series series,
2904 			int pointIndex,
2905 			float location,
2906 			out float height,
2907 			out float startWidth,
2908 			out float endWidth)
2909 		{
2910 			PointF	pointPositionAbs = PointF.Empty;
2911 
2912 			// Get plotting area position in pixels
2913 			RectangleF plotAreaPositionAbs = this.Graph.GetAbsoluteRectangle(this.PlotAreaPosition);
2914 
2915 			// Calculate total height of plotting area minus reserved space for the gaps
2916 			float plotAreaHeightAbs = plotAreaPositionAbs.Height -
2917 				this.funnelSegmentGap * (this.pointNumber - ((ShouldDrawFirstPoint()) ? 1 : 2) );
2918 			if(plotAreaHeightAbs < 0f)
2919 			{
2920 				plotAreaHeightAbs = 0f;
2921 			}
2922 
2923 			// Calculate segment height as a part of total Y values in series
2924 			height = (float)(plotAreaHeightAbs * (GetYValue(series.Points[pointIndex], pointIndex) / this.yValueTotal));
2925 
2926 			// Check for minimum segment height
2927 			height = CheckMinHeight(height);
2928 
2929 			// Get intersection point of the horizontal line at the start of the segment
2930 			// with the left pre-defined wall of the funnel.
2931 			PointF startIntersection = ChartGraphics.GetLinesIntersection(
2932 				plotAreaPositionAbs.X, location - height,
2933 				plotAreaPositionAbs.Right, location - height,
2934 				plotAreaPositionAbs.X, plotAreaPositionAbs.Bottom,
2935 				plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f, plotAreaPositionAbs.Y );
2936 			if(startIntersection.X > (plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f) )
2937 			{
2938 				startIntersection.X = plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f;
2939 			}
2940 
2941 			// Get intersection point of the horizontal line at the end of the segment
2942 			// with the left pre-defined wall of the funnel.
2943 			PointF endIntersection = ChartGraphics.GetLinesIntersection(
2944 				plotAreaPositionAbs.X, location,
2945 				plotAreaPositionAbs.Right, location,
2946 				plotAreaPositionAbs.X, plotAreaPositionAbs.Bottom,
2947 				plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f, plotAreaPositionAbs.Y );
2948 			if(endIntersection.X > (plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f))
2949 			{
2950 				endIntersection.X = plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f;
2951 			}
2952 
2953 			// Get segment start and end width
2954 			startWidth = (float)Math.Abs( plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f -
2955 				startIntersection.X) * 2f;
2956 			endWidth = (float)Math.Abs( plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f -
2957 				endIntersection.X) * 2f;
2958 
2959 			// Set point position for annotation anchoring
2960 			pointPositionAbs  = new PointF(
2961 				plotAreaPositionAbs.X + plotAreaPositionAbs.Width / 2f,
2962 				location - height / 2f);
2963 
2964 			// Set pre-calculated point position
2965 			series.Points[pointIndex].positionRel = Graph.GetRelativePoint(pointPositionAbs);
2966 		}
2967 
2968 		#endregion // Methods
2969 	}
2970 
2971 	/// <summary>
2972 	/// Helper data structure used to store information about single funnel segment.
2973 	/// </summary>
2974 	internal class FunnelSegmentInfo
2975 	{
2976 		#region Fields
2977 
2978 		// ----osiated data point
2979 		public	DataPoint	Point = null;
2980 
2981 		// Data point index
2982 		public	int			PointIndex = 0;
2983 
2984 		// Segment top position
2985 		public	float		Location = 0f;
2986 
2987 		// Segment height
2988 		public	float		Height = 0f;
2989 
2990 		// Segment top width
2991 		public	float		StartWidth = 0f;
2992 
2993 		// Segment bottom width
2994 		public	float		EndWidth = 0f;
2995 
2996 		// Segment has nothing on the top
2997 		public	bool		NothingOnTop = false;
2998 
2999 		// Segment has nothing on the bottom
3000 		public	bool		NothingOnBottom = false;
3001 
3002 		#endregion // Fields
3003 	}
3004 
3005 	/// <summary>
3006 	/// Helper data structure used to store information about funnel data point label.
3007 	/// </summary>
3008 	internal class FunnelPointLabelInfo
3009 	{
3010 		#region Fields
3011 
3012 		// ----osiated data point
3013 		public	DataPoint			Point = null;
3014 
3015 		// Data point index
3016 		public	int					PointIndex = 0;
3017 
3018 		// Label text
3019 		public	string				Text = string.Empty;
3020 
3021 		// Data point label size
3022 		public	SizeF				Size = SizeF.Empty;
3023 
3024 		// Position of the data point label
3025 		public	RectangleF			Position = RectangleF.Empty;
3026 
3027 		// Label style
3028 		public	FunnelLabelStyle	Style = FunnelLabelStyle.OutsideInColumn;
3029 
3030 		// Inside label vertical alignment
3031 		public	FunnelLabelVerticalAlignment	VerticalAlignment = FunnelLabelVerticalAlignment.Center;
3032 
3033 		// Outside labels placement
3034 		public	FunnelLabelPlacement OutsidePlacement = FunnelLabelPlacement.Right;
3035 
3036 		// Label callout first point
3037 		public	PointF				CalloutPoint1 = PointF.Empty;
3038 
3039 		// Label callout second point
3040 		public	PointF				CalloutPoint2 = PointF.Empty;
3041 
3042 		#endregion // Fields
3043 	}
3044 }
3045