1 //-------------------------------------------------------------
2 // <copyright company=�Microsoft Corporation�>
3 //   Copyright � Microsoft Corporation. All Rights Reserved.
4 // </copyright>
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 //  File:		AxisLabels.cs
9 //
10 //  Namespace:	System.Web.UI.WebControls[Windows.Forms].Charting
11 //
12 //	Classes:	AxisLabels
13 //
14 //  Purpose:	Base class for the Axis class which defines axis
15 //				labels related properties and methods.
16 //
17 //	Reviewed:	GS - August 8, 2002
18 //				AG - August 8, 2002
19 //
20 //===================================================================
21 
22 #region Used namespaces
23 using System;
24 using System.Collections;
25 using System.Collections.Specialized;
26 using System.Collections.Generic;
27 using System.ComponentModel;
28 using System.ComponentModel.Design;
29 using System.Data;
30 using System.Drawing;
31 using System.Drawing.Design;
32 using System.Drawing.Drawing2D;
33 
34 #if Microsoft_CONTROL
35 	using System.Windows.Forms.DataVisualization.Charting.Data;
36 	using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
37 	using System.Windows.Forms.DataVisualization.Charting.Utilities;
38 	using System.Windows.Forms.DataVisualization.Charting.Borders3D;
39 	using System.Windows.Forms.DataVisualization.Charting;
40 #else
41 	using System.Web;
42 	using System.Web.UI;
43 	using System.Web.UI.DataVisualization.Charting;
44 	using System.Web.UI.DataVisualization.Charting.Data;
45 	using System.Web.UI.DataVisualization.Charting.ChartTypes;
46 	using System.Web.UI.DataVisualization.Charting.Utilities;
47 #endif
48 
49 #endregion
50 
51 #if Microsoft_CONTROL
52 	namespace System.Windows.Forms.DataVisualization.Charting
53 #else
54 namespace System.Web.UI.DataVisualization.Charting
55 
56 #endif
57 {
58 	/// <summary>
59 	/// The Axis class provides functionality for
60 	/// drawing axis labels.
61 	/// </summary>
62 	public partial class Axis
63 	{
64         #region Fields
65 
66         // Custom Labels collection
67 		private CustomLabelsCollection	_customLabels = null;
68 
69 		#endregion
70 
71 		#region Axis labels properties
72 
73 		/// <summary>
74 		/// Gets or sets the style of the label.
75 		/// </summary>
76 		[
77 		SRCategory("CategoryAttributeLabels"),
78 		Bindable(true),
79 		NotifyParentPropertyAttribute(true),
80 		SRDescription("DescriptionAttributeLabelStyle"),
81 #if Microsoft_CONTROL
82 		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
83 #else
84 		PersistenceMode(PersistenceMode.InnerProperty),
85 #endif
86 		TypeConverter(typeof(NoNameExpandableObjectConverter))
87 		]
88 		public LabelStyle LabelStyle
89 		{
90 			get
91 			{
92 				return labelStyle;
93 			}
94 			set
95 			{
96 				labelStyle = value;
97 				labelStyle.Axis = (Axis)this;
98 				this.Invalidate();
99 			}
100 		}
101 
102 		/// <summary>
103 		/// Gets a collection of custom labels.
104 		/// </summary>
105 		[
106 		SRCategory("CategoryAttributeLabels"),
107 		Bindable(true),
108 		SRDescription("DescriptionAttributeCustomLabels"),
109 #if Microsoft_CONTROL
110 		DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
111 #else
112 		PersistenceMode(PersistenceMode.InnerProperty),
113 #endif
114         Editor(Editors.ChartCollectionEditor.Editor, Editors.ChartCollectionEditor.Base)
115 		]
116 		public CustomLabelsCollection CustomLabels
117 		{
118 			get
119 			{
120 				return _customLabels;
121 			}
122 		}
123 
124 		#endregion
125 
126 		#region Axis labels methods
127 
128 		/// <summary>
129 		/// Indicates that custom grid lines should be painted.
130 		/// </summary>
131 		/// <returns>Indicates that custom grid lines should be painted.</returns>
IsCustomGridLines()132 		internal bool IsCustomGridLines()
133 		{
134 			if(this.CustomLabels.Count > 0)
135 			{
136 				// Check if at least one custom label has a flag set
137 				foreach(CustomLabel label in this.CustomLabels)
138 				{
139 					if((label.GridTicks & GridTickTypes.Gridline) == GridTickTypes.Gridline)
140 					{
141 						return true;
142 					}
143 				}
144 			}
145 
146 			return false;
147 		}
148 
149 		/// <summary>
150 		/// Indicates that custom tick marks should be painted.
151 		/// </summary>
152 		/// <returns>Indicates that custom tick marks should be painted.</returns>
IsCustomTickMarks()153 		internal bool IsCustomTickMarks()
154 		{
155 			if(this.CustomLabels.Count > 0)
156 			{
157 				// Check if at least one custom label has a flag set
158 				foreach(CustomLabel label in this.CustomLabels)
159 				{
160 					if((label.GridTicks & GridTickTypes.TickMark) == GridTickTypes.TickMark)
161 					{
162 						return true;
163 					}
164 				}
165 			}
166 
167 			return false;
168 		}
169 
170         /// <summary>
171         /// Gets the type of the axis.
172         /// </summary>
173         /// <value>The type of the axis.</value>
GetAxisType()174         internal AxisType GetAxisType()
175         {
176             if (this.axisType == AxisName.X || this.axisType == AxisName.Y)
177             {
178                 return AxisType.Primary;
179             }
180             else
181             {
182                 return AxisType.Secondary;
183             }
184         }
185 
186         /// <summary>
187         /// Gets the axis series.
188         /// </summary>
189         /// <returns></returns>
GetAxisSeries()190         internal ArrayList GetAxisSeries()
191         {
192             ArrayList dataSeries = new ArrayList();
193 
194             // check for attached series.
195             foreach (string seriesName in this.ChartArea.Series)
196             {
197                 Series series = this.Common.DataManager.Series[seriesName];
198                 if (this.axisType == AxisName.X || this.axisType == AxisName.X2)
199                 {
200                     if (series.XAxisType == this.GetAxisType())
201                     {
202                         dataSeries.Add(series);
203                     }
204                 }
205                 else
206                 {
207                     if (series.YAxisType == this.GetAxisType())
208                     {
209                         dataSeries.Add(series);
210                     }
211                 }
212             }
213             return dataSeries;
214         }
215 
216         /// <summary>
217         /// Gets the other (primary/secondary) axis.
218         /// </summary>
219         /// <returns></returns>
GetOtherTypeAxis()220         internal Axis GetOtherTypeAxis()
221         {
222             return ChartArea.GetAxis(
223                     this.axisType,
224                     this.GetAxisType() == AxisType.Primary ? AxisType.Secondary : AxisType.Primary,
225                     String.Empty
226                 );
227         }
228 
229         /// <summary>
230         /// Checks if the other (primary/secondary) axis has custom labels labels.
231         /// These labels will be added if this axis has no series attached and no custom labels.
232         /// This works only on category axes.
233         /// </summary>
PostFillLabels()234         internal void PostFillLabels()
235         {
236             foreach (CustomLabel label in this.CustomLabels)
237             {
238                 if (label.customLabel)
239                 {
240                     return;
241                 }
242             }
243 
244             // Labels are disabled for this axis
245             if (
246                 !this.LabelStyle.Enabled ||
247                 !this.enabled ||
248                 !String.IsNullOrEmpty(((Axis)this).SubAxisName) ||
249                 this.axisType == AxisName.Y ||
250                 this.axisType == AxisName.Y2
251                 )
252             {
253                 return;
254             }
255 
256             // check if no series attached.
257             if (this.GetAxisSeries().Count > 0)
258             {
259                 return;
260             }
261             this.CustomLabels.Clear();
262             foreach (CustomLabel label in this.GetOtherTypeAxis().CustomLabels)
263             {
264                 this.CustomLabels.Add(label.Clone());
265             }
266         }
267 
268         /// <summary>
269         /// Fill labels from data from data manager or
270         /// from axis scale.
271         /// </summary>
272         /// <param name="removeFirstRow">True if first row of auto generated labels must be removed.</param>
FillLabels(bool removeFirstRow)273 		internal void FillLabels(bool removeFirstRow)
274         {
275 #if SUBAXES
276 			// Process all sub-axis
277 			foreach(SubAxis subAxis in ((Axis)this).SubAxes)
278 			{
279 				subAxis.FillLabels(true);
280 			}
281 #endif // SUBAXES
282 
283             // Labels are disabled for this axis
284 			if( !this.LabelStyle.Enabled || !this.enabled )
285 			{
286 				return;
287 			}
288 
289 			// For circular chart area fill only Y axis labels
290 			if(this.ChartArea != null && this.ChartArea.chartAreaIsCurcular)
291 			{
292 				if(this.axisType != AxisName.Y)
293 				{
294 					ICircularChartType type = this.ChartArea.GetCircularChartType();
295 					if(type == null || !type.XAxisLabelsSupported())
296 					{
297 						return;
298 					}
299 				}
300 			}
301 
302 			// Check if the custom labels exist
303 			bool customLabelsFlag = false;
304 			foreach( CustomLabel lab in CustomLabels )
305 			{
306 				if( lab.customLabel )
307 				{
308 					if( lab.RowIndex == 0 ||
309 						this.ChartArea.chartAreaIsCurcular)
310 					{
311 						customLabelsFlag = true;
312 					}
313 				}
314 			}
315 
316 
317 			// Remove the first row of labels if custom labels not exist
318 			if(removeFirstRow)
319 			{
320 				if( customLabelsFlag == false )
321 				{
322 					for( int index = 0; index < CustomLabels.Count; index++ )
323 					{
324 						if( CustomLabels[index].RowIndex == 0 )
325 						{
326 							CustomLabels.RemoveAt( index );
327 							index = -1;
328 						}
329 					}
330 				}
331 				else
332 				{
333 					return;
334 				}
335 			}
336 
337 			// Get data series for this axis.
338 			List<string> dataSeries = null;
339 			switch( axisType )
340 			{
341 				case AxisName.X:
342 					dataSeries = ChartArea.GetXAxesSeries( AxisType.Primary, ((Axis)this).SubAxisName );
343 					break;
344 				case AxisName.Y:
345 					dataSeries = ChartArea.GetYAxesSeries( AxisType.Primary, ((Axis)this).SubAxisName );
346 					break;
347 				case AxisName.X2:
348 					dataSeries = ChartArea.GetXAxesSeries( AxisType.Secondary, ((Axis)this).SubAxisName );
349 					break;
350 				case AxisName.Y2:
351 					dataSeries = ChartArea.GetYAxesSeries( AxisType.Secondary, ((Axis)this).SubAxisName );
352 					break;
353 			}
354 
355 			// There aren't data series connected with this axis.
356 			if( dataSeries.Count == 0 )
357 			{
358 				return;
359 			}
360 
361             //Let's convert the ArrayList of the series names into to string[]
362             string[] dataSeriesNames = new string[dataSeries.Count];
363             for (int i = 0; i < dataSeries.Count; i++)
364                 dataSeriesNames[i] = (string)dataSeries[i];
365 
366 			// Check if series X values all set to zeros
367             bool seriesXValuesZeros = ChartHelper.SeriesXValuesZeros(this.Common, dataSeriesNames);
368 
369             // Check if series is indexed (All X values zeros or IsXValueIndexed flag set)
370             bool indexedSeries = true;
371             if (!seriesXValuesZeros)
372             {
373                 indexedSeries = ChartHelper.IndexedSeries(this.Common, dataSeriesNames);
374             }
375 
376 			// Show End Labels
377 			int endLabels = 0;
378 			if( labelStyle.IsEndLabelVisible )
379 			{
380 				endLabels = 1;
381 			}
382 
383 			// Get chart type of the first series
384 			IChartType	chartType = Common.ChartTypeRegistry.GetChartType( ChartArea.GetFirstSeries().ChartTypeName );
385 			bool		fromSeries = false;
386 			if( !chartType.RequireAxes )
387 			{
388 				return;
389 			}
390 			else if( axisType == AxisName.Y || axisType == AxisName.Y2 )
391 			{
392 				fromSeries = false;
393 			}
394 			else
395 			{
396 				fromSeries = true;
397 			}
398 
399 			// X values from data points are not 0.
400             if (fromSeries && !ChartHelper.SeriesXValuesZeros(this.Common, dataSeries.ToArray()))
401 			{
402 				fromSeries = false;
403 			}
404 
405 			// X values from data points are not 0.
406 			if( fromSeries && ( labelStyle.GetIntervalOffset() != 0 || labelStyle.GetInterval() != 0 ) )
407 			{
408 				fromSeries = false;
409 			}
410 
411 			// Get value type
412 			ChartValueType valueType;
413 			if( axisType == AxisName.X || axisType == AxisName.X2 )
414 			{
415 				// If X value is indexed the type is always String. So we use indexed type instead
416 				valueType = Common.DataManager.Series[dataSeries[0]].indexedXValueType;
417 			}
418 			else
419 			{
420 				valueType = Common.DataManager.Series[dataSeries[0]].YValueType;
421 			}
422 
423 			if( labelStyle.GetIntervalType() != DateTimeIntervalType.Auto &&
424                 labelStyle.GetIntervalType() != DateTimeIntervalType.Number )
425 			{
426                 if (valueType != ChartValueType.Time &&
427                     valueType != ChartValueType.Date &&
428                     valueType != ChartValueType.DateTimeOffset)
429 				{
430 					valueType = ChartValueType.DateTime;
431 				}
432 			}
433 
434 			// ***********************************
435 			// Pre calculate some values
436 			// ***********************************
437 			double viewMaximum = this.ViewMaximum;
438 			double viewMinimum = this.ViewMinimum;
439 
440 			// ***********************************
441 			// Labels are filled from data series.
442 			// ***********************************
443 			if( fromSeries )
444 			{
445 				int numOfPoints;
446 				numOfPoints = Common.DataManager.GetNumberOfPoints( dataSeries.ToArray() );
447 
448 				// Show end labels
449 				if( endLabels == 1 )
450 				{
451 					// min position
452 					CustomLabels.Add( - 0.5, 0.5, ValueConverter.FormatValue(
453 						this.Common.Chart,
454 						this,
455                         null,
456 						0.0,
457 						this.LabelStyle.Format,
458 						valueType,
459 						ChartElementType.AxisLabels),
460 						false);
461 				}
462 
463 				// Labels from point position
464 				for( int point = 0; point < numOfPoints; point++ )
465 				{
466 					CustomLabels.Add( ((double)point)+ 0.5, ((double)point)+ 1.5,
467 						ValueConverter.FormatValue(
468 							this.Common.Chart,
469 							this,
470                             null,
471 							point + 1,
472 							this.LabelStyle.Format,
473 							valueType,
474 							ChartElementType.AxisLabels),
475 							false);
476 				}
477 
478 				// Show end labels
479 				if( endLabels == 1 )
480 				{
481 					// max position
482 					CustomLabels.Add( ((double)numOfPoints)+ 0.5, ((double)numOfPoints)+ 1.5,
483 						ValueConverter.FormatValue(
484 							this.Common.Chart,
485 							this,
486                             null,
487                             numOfPoints + 1,
488 							this.LabelStyle.Format,
489 							valueType,
490 							ChartElementType.AxisLabels),
491 							false);
492 				}
493 
494 				int pointIndx;
495 				foreach( string seriesIndx in dataSeries )
496 				{
497 					// End labels enabled
498 					if( endLabels == 1 )
499 						pointIndx = 1;
500 					else
501 						pointIndx = 0;
502 
503 					// Set labels from data points labels
504 					foreach( DataPoint dataPoint in Common.DataManager.Series[ seriesIndx ].Points )
505 					{
506 						// Find first row of labels
507 						while( CustomLabels[pointIndx].RowIndex > 0 )
508 						{
509 							pointIndx++;
510 						}
511 
512 						// Add X labels
513 						if( axisType == AxisName.X || axisType == AxisName.X2 )
514 						{
515 							if( dataPoint.AxisLabel.Length > 0 )
516 							{
517 								CustomLabels[pointIndx].Text = dataPoint.AxisLabel;
518 							}
519 						}
520 
521 						pointIndx++;
522 					}
523 				}
524 			}
525 			// ***********************************
526 			// Labels are filled from axis scale.
527 			// ***********************************
528 			else
529 			{
530 				if( viewMinimum == viewMaximum )
531 					return;
532 
533 				double labValue; // Value, which will be converted to text and used for, labels.
534 				double beginPosition; // Begin position for a label
535 				double endPosition; // End position for a label
536 				double start; // Start position for all labels
537 
538 				// Get first series attached to this axis
539 				Series	axisSeries = null;
540 				if(axisType == AxisName.X || axisType == AxisName.X2)
541 				{
542 					List<string> seriesArray = ChartArea.GetXAxesSeries((axisType == AxisName.X) ? AxisType.Primary : AxisType.Secondary, ((Axis)this).SubAxisName);
543 					if(seriesArray.Count > 0)
544 					{
545 						axisSeries = Common.DataManager.Series[seriesArray[0]];
546 						if(axisSeries != null && !axisSeries.IsXValueIndexed)
547 						{
548 							axisSeries = null;
549 						}
550 					}
551 				}
552 
553                 // ***********************************
554                 // Check if the AJAX zooming and scrolling mode is enabled.
555                 // Labels are filled slightly different in this case.
556                 // ***********************************
557                 DateTimeIntervalType offsetType = (labelStyle.GetIntervalOffsetType() == DateTimeIntervalType.Auto) ? labelStyle.GetIntervalType() : labelStyle.GetIntervalOffsetType();
558 
559 				// By default start is equal to minimum
560 				start = viewMinimum;
561 
562 				// Adjust start position depending on the interval type
563 				if(!this.ChartArea.chartAreaIsCurcular ||
564 					this.axisType == AxisName.Y ||
565 					this.axisType == AxisName.Y2 )
566 				{
567                     start = ChartHelper.AlignIntervalStart(start, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries);
568 				}
569 
570 				// Move start if there is start position
571 				if( labelStyle.GetIntervalOffset() != 0 && axisSeries == null)
572 				{
573                     start += ChartHelper.GetIntervalSize(start, labelStyle.GetIntervalOffset(),
574 						offsetType, axisSeries, 0, DateTimeIntervalType.Number, true, false);
575 				}
576 
577 				// ***************************************
578 				// Date type
579 				// ***************************************
580 				if( valueType == ChartValueType.DateTime ||
581 					valueType == ChartValueType.Date ||
582 					valueType == ChartValueType.Time ||
583                     valueType == ChartValueType.DateTimeOffset ||
584 					axisSeries != null)
585 				{
586 					double position = start;
587 					double dateInterval;
588 
589 					// Too many labels
590                     if ((viewMaximum - start) / ChartHelper.GetIntervalSize(start, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, 0, DateTimeIntervalType.Number, true) > ChartHelper.MaxNumOfGridlines)
591 						return;
592 
593 					int	counter = 0;
594                     double endLabelMaxPosition = viewMaximum - ChartHelper.GetIntervalSize(viewMaximum, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true) / 2f;
595                     double endLabelMinPosition = viewMinimum + ChartHelper.GetIntervalSize(viewMinimum, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true) / 2f;
596 					while( (decimal)position <= (decimal)viewMaximum )
597 					{
598                         dateInterval = ChartHelper.GetIntervalSize(position, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true);
599 						labValue = position;
600 
601 						// For IsLogarithmic axes
602 						if( this.IsLogarithmic )
603 						{
604 							labValue = Math.Pow( this.logarithmBase, labValue );
605 						}
606 
607                         // Check if we do not exceed max number of elements
608                         if (counter++ > ChartHelper.MaxNumOfGridlines)
609                         {
610                             break;
611                         }
612 
613                         if (endLabels == 0 && position >= endLabelMaxPosition)
614                         {
615                             break;
616                         }
617 
618 						beginPosition = position - dateInterval * 0.5;
619 						endPosition = position + dateInterval * 0.5;
620 
621 						if(endLabels == 0 && position <=  endLabelMinPosition)
622 						{
623 							position += dateInterval;
624 							continue;
625 						}
626 
627 						if( (decimal)beginPosition > (decimal)viewMaximum )
628 						{
629 							position += dateInterval;
630 							continue;
631 						}
632 
633                         // NOTE: Fixes issue #6466
634                         // Following code is removed due to the issues caused by the rounding error
635 
636                         //if( (((decimal)beginPosition + (decimal)endPosition) / 2.0m) < (decimal)viewMinimum )
637                         //{
638                         //    position += dateInterval;
639                         //    continue;
640                         //}
641                         //if ((decimal)viewMaximum < (((decimal)beginPosition + (decimal)endPosition) / 2m))
642                         //{
643                         //    position += dateInterval;
644                         //    continue;
645                         //}
646 
647 						string pointLabel = GetPointLabel( dataSeries, labValue, !seriesXValuesZeros, indexedSeries );
648 						if( pointLabel.Length == 0 )
649 						{
650 							// Do not draw last label for indexed series
651 							if( position <= this.maximum )
652 							{
653 								// Add a label to the collection
654 								if( position != this.maximum || !Common.DataManager.Series[ dataSeries[0] ].IsXValueIndexed )
655 								{
656 									CustomLabels.Add( beginPosition,
657 										endPosition,
658 										ValueConverter.FormatValue(
659 											this.Common.Chart,
660 											this,
661                                             null,
662 											labValue,
663 											this.LabelStyle.Format,
664 											valueType,
665 											ChartElementType.AxisLabels),
666 										false);
667 								}
668 							}
669 						}
670 						else
671 						{
672 							// Add a label to the collection
673 							CustomLabels.Add( beginPosition,
674 								endPosition,
675 								pointLabel,
676 								false);
677 						}
678 						position += dateInterval;
679 					}
680 				}
681 				else
682 				{
683 					// ***************************************
684 					// Scale value type
685 					// ***************************************
686 
687 					// Show First label if Start Label position is used
688 					if( start != viewMinimum )
689 						endLabels = 1;
690 
691 					// Set labels
692 					int labelCounter = 0;
693                     for (double position = start - endLabels * labelStyle.GetInterval(); position < viewMaximum - 1.5 * labelStyle.GetInterval() * (1 - endLabels); position = (double)((decimal)position + (decimal)labelStyle.GetInterval()))
694 					{
695 						// Prevent endless loop that may be caused by very small interval
696 						// and double/decimal rounding errors
697 						++labelCounter;
698 						if(labelCounter > ChartHelper.MaxNumOfGridlines)
699 						{
700 							break;
701 						}
702 
703 						labValue = (double)((decimal)position + (decimal)labelStyle.GetInterval());
704 
705 						// This line is introduce because sometimes 0 value will appear as
706 						// very small value close to zero.
707 						double inter = Math.Log(labelStyle.GetInterval());
708 						double valu = Math.Log(Math.Abs(labValue));
709 						int digits = (int)Math.Abs(inter)+5;
710 
711 						if( digits > 15 )
712 						{
713 							digits = 15;
714 						}
715 
716 						if( Math.Abs(inter) < Math.Abs(valu)-5 )
717 						{
718 							labValue = Math.Round(labValue,digits);
719 						}
720 
721 						// Too many labels
722 						if( ( viewMaximum - start ) / labelStyle.GetInterval() > ChartHelper.MaxNumOfGridlines )
723 						{
724 							return;
725 						}
726 
727 						// For IsLogarithmic axes
728 						if( this.IsLogarithmic )
729 							labValue = Math.Pow( this.logarithmBase, labValue );
730 
731 						beginPosition = (double)((decimal)position + (decimal)labelStyle.GetInterval() * 0.5m);
732 						endPosition = (double)((decimal)position + (decimal)labelStyle.GetInterval() * 1.5m);
733 
734 						if( (decimal)beginPosition > (decimal)viewMaximum )
735 						{
736 							continue;
737 						}
738 
739 						// Show End label if Start Label position is used
740 						// Use decimal type to solve rounding issues
741 						if( (decimal)(( beginPosition + endPosition )/2.0) > (decimal)viewMaximum )
742 						{
743 							continue;
744 						}
745 
746 						string pointLabel = GetPointLabel( dataSeries, labValue, !seriesXValuesZeros, indexedSeries  );
747 						if( pointLabel.Length > 15 && labValue < 0.000001)
748 						{
749 							labValue = 0.0;
750 						}
751 
752 						if( pointLabel.Length == 0 )
753 						{
754 							// Do not draw last label for indexed series
755 							if( !(Common.DataManager.Series[ dataSeries[0] ].IsXValueIndexed && position > this.maximum) )
756 							{
757 								// Add a label to the collection
758 								CustomLabels.Add( beginPosition,
759 									endPosition,
760 									ValueConverter.FormatValue(
761 										this.Common.Chart,
762 										this,
763                                         null,
764 										labValue,
765 										this.LabelStyle.Format,
766 										valueType,
767 										ChartElementType.AxisLabels),
768 									false);
769 							}
770 						}
771 						else
772 						{
773 							// Add a label to the collection
774 							CustomLabels.Add( beginPosition,
775 								endPosition,
776 								pointLabel,
777 								false);
778 						}
779 					}
780 				}
781 			}
782 		}
783 
784 		/// <summary>
785 		/// This method checks if there is a data point which has value X equal
786 		/// to valuePosition, and returns label from data point if such value exist.
787 		/// If data point with this value not exists empty string will be returned.
788 		/// If all data points have X value zero, index is used instead of X value.
789 		/// </summary>
790 		/// <param name="series">Data series</param>
791 		/// <param name="valuePosition">A value which should be found in data points x values</param>
792 		/// <param name="nonZeroXValues">Series X values are not zeros.</param>
793 		/// <param name="indexedSeries">Series is indexed. All X values are zeros or IsXValueIndexed flag set.</param>
794 		/// <returns>LabelStyle</returns>
GetPointLabel( List<string> series, double valuePosition, bool nonZeroXValues, bool indexedSeries )795 		private string GetPointLabel(
796 			List<string> series,
797 			double valuePosition,
798 			bool nonZeroXValues,
799 			bool indexedSeries
800 			)
801 		{
802             // Get max number of data points in the series
803             int maxPointCount = 0;
804             foreach (string seriesName in series)
805             {
806                 Series ser = Common.DataManager.Series[seriesName];
807                 maxPointCount = Math.Max(maxPointCount, ser.Points.Count);
808             }
809 
810             // Check if axis only contains axis abels
811 			bool allEmpty = true;
812 			foreach( string seriesName in series )
813 			{
814 				// Get series by name
815 				Series ser = Common.DataManager.Series[ seriesName ];
816 
817 				// Check if series has axis labels set
818                 if ((axisType == AxisName.X || axisType == AxisName.X2) && (margin != 0 || maxPointCount == 1 || !this._autoMinimum) && !ser.IsXValueIndexed)
819 				{
820 					if( ser.Points[ 0 ].AxisLabel.Length > 0 && ser.Points[ ser.Points.Count - 1 ].AxisLabel.Length > 0 )
821 					{
822 						allEmpty = false;
823 					}
824 				}
825 
826 				// Try getting label from the point
827 				if(!ser.noLabelsInPoints || (nonZeroXValues && indexedSeries))
828 				{
829 					string result = GetPointLabel( ser, valuePosition, nonZeroXValues, indexedSeries );
830 					if(!String.IsNullOrEmpty(result))
831 					{
832 						return result;
833 					}
834 				}
835 
836                 // VSTS 140676: Serach for IndexedSeriesLabelsSourceAttr attribute
837                 // to find if we have indexed series as source of formula generated nonindexed series.
838                 String labelSeriesName = ser[DataFormula.IndexedSeriesLabelsSourceAttr];
839                 if (!String.IsNullOrEmpty(labelSeriesName))
840                 {
841                     Series labelsSeries = Common.DataManager.Series[labelSeriesName];
842                     if (labelsSeries != null)
843                     {
844                         string result = GetPointLabel(labelsSeries, valuePosition, nonZeroXValues, true);
845                         if (!String.IsNullOrEmpty(result))
846                         {
847                             return result;
848                         }
849                     }
850                 }
851 
852 			}
853 
854 			if( !allEmpty )
855 			{
856                 return " ";
857 			}
858 			else
859 			{
860 				return "";
861 			}
862 		}
863 
864 		/// <summary>
865 		/// This method checks if there is a data point which has value X equal
866 		/// to valuePosition, and returns label from data point if such value exist.
867 		/// If data point with this value not exists empty string will be returned.
868 		/// If all data points have X value zero, index is used instead of X value.
869 		/// </summary>
870 		/// <param name="series">Data series</param>
871 		/// <param name="valuePosition">A value which should be found in data points x values</param>
872 		/// <param name="nonZeroXValues">Series X values are not zeros.</param>
873 		/// <param name="indexedSeries">Series is indexed. All X values are zeros or IsXValueIndexed flag set.</param>
874 		/// <returns>LabelStyle</returns>
GetPointLabel( Series series, double valuePosition, bool nonZeroXValues, bool indexedSeries)875 		private string GetPointLabel(
876 			Series series,
877 			double valuePosition,
878 			bool nonZeroXValues,
879 			bool indexedSeries)
880 		{
881 			int pointIndx = 1;
882 
883 			if( axisType == AxisName.Y || axisType == AxisName.Y2 )
884 			{
885 				return "";
886 			}
887 
888 			if( !(( axisType == AxisName.X && series.XAxisType == AxisType.Primary ) || ( axisType == AxisName.X2 && series.XAxisType == AxisType.Secondary )) )
889             {
890 #if SUBAXES
891 				if(series.XSubAxisName != ((Axis)this).SubAxisName)
892 				{
893 					return "";
894 				}
895 #endif // SUBAXES
896                 return "";
897 			}
898 
899 			// Loop through all series data points
900 			foreach( DataPoint point in series.Points )
901 			{
902 				// If series is indexed (all X values are zeros or IsXValueIndexed flag set)
903 				if( indexedSeries )
904 				{
905 					// If axis label position matches point index
906 					if( valuePosition == pointIndx )
907 					{
908 						// Use X value if axis label is not set and X values in series are not zeros
909 						if(point.AxisLabel.Length == 0 && nonZeroXValues)
910 						{
911 							return ValueConverter.FormatValue(
912 								this.Common.Chart,
913 								this,
914                                 null,
915 								point.XValue,
916 								this.LabelStyle.Format,
917 								series.XValueType,
918 								ChartElementType.AxisLabels);
919 						}
920 
921                         // Return axis label from data point
922 						return point.ReplaceKeywords(point.AxisLabel);
923 					}
924 				}
925 				else
926 				{
927 					// Find x value using Data point X values
928 					if( point.XValue == valuePosition )
929 					{
930 						// Return  label
931 						return point.ReplaceKeywords(point.AxisLabel);
932 					}
933 				}
934 				pointIndx++;
935 			}
936 			return "";
937 		}
938 
939 		#endregion
940 	}
941 }
942