|
//-------------------------------------------------------------
// <copyright company=’Microsoft Corporation’>
// Copyright © Microsoft Corporation. All Rights Reserved.
// </copyright>
//-------------------------------------------------------------
// @owner=alexgor, deliant
//=================================================================
// File: AxisLabels.cs
//
// Namespace: System.Web.UI.WebControls[Windows.Forms].Charting
//
// Classes: AxisLabels
//
// Purpose: Base class for the Axis class which defines axis
// labels related properties and methods.
//
// Reviewed: GS - August 8, 2002
// AG - August 8, 2002
//
//===================================================================
#region Used namespaces
using System;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Data;
using System.Drawing;
using System.Drawing.Design;
using System.Drawing.Drawing2D;
#if Microsoft_CONTROL
using System.Windows.Forms.DataVisualization.Charting.Data;
using System.Windows.Forms.DataVisualization.Charting.ChartTypes;
using System.Windows.Forms.DataVisualization.Charting.Utilities;
using System.Windows.Forms.DataVisualization.Charting.Borders3D;
using System.Windows.Forms.DataVisualization.Charting;
#else
using System.Web;
using System.Web.UI;
using System.Web.UI.DataVisualization.Charting;
using System.Web.UI.DataVisualization.Charting.Data;
using System.Web.UI.DataVisualization.Charting.ChartTypes;
using System.Web.UI.DataVisualization.Charting.Utilities;
#endif
#endregion
#if Microsoft_CONTROL
namespace System.Windows.Forms.DataVisualization.Charting
#else
namespace System.Web.UI.DataVisualization.Charting
#endif
{
/// <summary>
/// The Axis class provides functionality for
/// drawing axis labels.
/// </summary>
public partial class Axis
{
#region Fields
// Custom Labels collection
private CustomLabelsCollection _customLabels = null;
#endregion
#region Axis labels properties
/// <summary>
/// Gets or sets the style of the label.
/// </summary>
[
SRCategory("CategoryAttributeLabels"),
Bindable(true),
NotifyParentPropertyAttribute(true),
SRDescription("DescriptionAttributeLabelStyle"),
#if Microsoft_CONTROL
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
#else
PersistenceMode(PersistenceMode.InnerProperty),
#endif
TypeConverter(typeof(NoNameExpandableObjectConverter))
]
public LabelStyle LabelStyle
{
get
{
return labelStyle;
}
set
{
labelStyle = value;
labelStyle.Axis = (Axis)this;
this.Invalidate();
}
}
/// <summary>
/// Gets a collection of custom labels.
/// </summary>
[
SRCategory("CategoryAttributeLabels"),
Bindable(true),
SRDescription("DescriptionAttributeCustomLabels"),
#if Microsoft_CONTROL
DesignerSerializationVisibility(DesignerSerializationVisibility.Content),
#else
PersistenceMode(PersistenceMode.InnerProperty),
#endif
Editor(Editors.ChartCollectionEditor.Editor, Editors.ChartCollectionEditor.Base)
]
public CustomLabelsCollection CustomLabels
{
get
{
return _customLabels;
}
}
#endregion
#region Axis labels methods
/// <summary>
/// Indicates that custom grid lines should be painted.
/// </summary>
/// <returns>Indicates that custom grid lines should be painted.</returns>
internal bool IsCustomGridLines()
{
if(this.CustomLabels.Count > 0)
{
// Check if at least one custom label has a flag set
foreach(CustomLabel label in this.CustomLabels)
{
if((label.GridTicks & GridTickTypes.Gridline) == GridTickTypes.Gridline)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Indicates that custom tick marks should be painted.
/// </summary>
/// <returns>Indicates that custom tick marks should be painted.</returns>
internal bool IsCustomTickMarks()
{
if(this.CustomLabels.Count > 0)
{
// Check if at least one custom label has a flag set
foreach(CustomLabel label in this.CustomLabels)
{
if((label.GridTicks & GridTickTypes.TickMark) == GridTickTypes.TickMark)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Gets the type of the axis.
/// </summary>
/// <value>The type of the axis.</value>
internal AxisType GetAxisType()
{
if (this.axisType == AxisName.X || this.axisType == AxisName.Y)
{
return AxisType.Primary;
}
else
{
return AxisType.Secondary;
}
}
/// <summary>
/// Gets the axis series.
/// </summary>
/// <returns></returns>
internal ArrayList GetAxisSeries()
{
ArrayList dataSeries = new ArrayList();
// check for attached series.
foreach (string seriesName in this.ChartArea.Series)
{
Series series = this.Common.DataManager.Series[seriesName];
if (this.axisType == AxisName.X || this.axisType == AxisName.X2)
{
if (series.XAxisType == this.GetAxisType())
{
dataSeries.Add(series);
}
}
else
{
if (series.YAxisType == this.GetAxisType())
{
dataSeries.Add(series);
}
}
}
return dataSeries;
}
/// <summary>
/// Gets the other (primary/secondary) axis.
/// </summary>
/// <returns></returns>
internal Axis GetOtherTypeAxis()
{
return ChartArea.GetAxis(
this.axisType,
this.GetAxisType() == AxisType.Primary ? AxisType.Secondary : AxisType.Primary,
String.Empty
);
}
/// <summary>
/// Checks if the other (primary/secondary) axis has custom labels labels.
/// These labels will be added if this axis has no series attached and no custom labels.
/// This works only on category axes.
/// </summary>
internal void PostFillLabels()
{
foreach (CustomLabel label in this.CustomLabels)
{
if (label.customLabel)
{
return;
}
}
// Labels are disabled for this axis
if (
!this.LabelStyle.Enabled ||
!this.enabled ||
!String.IsNullOrEmpty(((Axis)this).SubAxisName) ||
this.axisType == AxisName.Y ||
this.axisType == AxisName.Y2
)
{
return;
}
// check if no series attached.
if (this.GetAxisSeries().Count > 0)
{
return;
}
this.CustomLabels.Clear();
foreach (CustomLabel label in this.GetOtherTypeAxis().CustomLabels)
{
this.CustomLabels.Add(label.Clone());
}
}
/// <summary>
/// Fill labels from data from data manager or
/// from axis scale.
/// </summary>
/// <param name="removeFirstRow">True if first row of auto generated labels must be removed.</param>
internal void FillLabels(bool removeFirstRow)
{
#if SUBAXES
// Process all sub-axis
foreach(SubAxis subAxis in ((Axis)this).SubAxes)
{
subAxis.FillLabels(true);
}
#endif // SUBAXES
// Labels are disabled for this axis
if( !this.LabelStyle.Enabled || !this.enabled )
{
return;
}
// For circular chart area fill only Y axis labels
if(this.ChartArea != null && this.ChartArea.chartAreaIsCurcular)
{
if(this.axisType != AxisName.Y)
{
ICircularChartType type = this.ChartArea.GetCircularChartType();
if(type == null || !type.XAxisLabelsSupported())
{
return;
}
}
}
// Check if the custom labels exist
bool customLabelsFlag = false;
foreach( CustomLabel lab in CustomLabels )
{
if( lab.customLabel )
{
if( lab.RowIndex == 0 ||
this.ChartArea.chartAreaIsCurcular)
{
customLabelsFlag = true;
}
}
}
// Remove the first row of labels if custom labels not exist
if(removeFirstRow)
{
if( customLabelsFlag == false )
{
for( int index = 0; index < CustomLabels.Count; index++ )
{
if( CustomLabels[index].RowIndex == 0 )
{
CustomLabels.RemoveAt( index );
index = -1;
}
}
}
else
{
return;
}
}
// Get data series for this axis.
List<string> dataSeries = null;
switch( axisType )
{
case AxisName.X:
dataSeries = ChartArea.GetXAxesSeries( AxisType.Primary, ((Axis)this).SubAxisName );
break;
case AxisName.Y:
dataSeries = ChartArea.GetYAxesSeries( AxisType.Primary, ((Axis)this).SubAxisName );
break;
case AxisName.X2:
dataSeries = ChartArea.GetXAxesSeries( AxisType.Secondary, ((Axis)this).SubAxisName );
break;
case AxisName.Y2:
dataSeries = ChartArea.GetYAxesSeries( AxisType.Secondary, ((Axis)this).SubAxisName );
break;
}
// There aren't data series connected with this axis.
if( dataSeries.Count == 0 )
{
return;
}
//Let's convert the ArrayList of the series names into to string[]
string[] dataSeriesNames = new string[dataSeries.Count];
for (int i = 0; i < dataSeries.Count; i++)
dataSeriesNames[i] = (string)dataSeries[i];
// Check if series X values all set to zeros
bool seriesXValuesZeros = ChartHelper.SeriesXValuesZeros(this.Common, dataSeriesNames);
// Check if series is indexed (All X values zeros or IsXValueIndexed flag set)
bool indexedSeries = true;
if (!seriesXValuesZeros)
{
indexedSeries = ChartHelper.IndexedSeries(this.Common, dataSeriesNames);
}
// Show End Labels
int endLabels = 0;
if( labelStyle.IsEndLabelVisible )
{
endLabels = 1;
}
// Get chart type of the first series
IChartType chartType = Common.ChartTypeRegistry.GetChartType( ChartArea.GetFirstSeries().ChartTypeName );
bool fromSeries = false;
if( !chartType.RequireAxes )
{
return;
}
else if( axisType == AxisName.Y || axisType == AxisName.Y2 )
{
fromSeries = false;
}
else
{
fromSeries = true;
}
// X values from data points are not 0.
if (fromSeries && !ChartHelper.SeriesXValuesZeros(this.Common, dataSeries.ToArray()))
{
fromSeries = false;
}
// X values from data points are not 0.
if( fromSeries && ( labelStyle.GetIntervalOffset() != 0 || labelStyle.GetInterval() != 0 ) )
{
fromSeries = false;
}
// Get value type
ChartValueType valueType;
if( axisType == AxisName.X || axisType == AxisName.X2 )
{
// If X value is indexed the type is always String. So we use indexed type instead
valueType = Common.DataManager.Series[dataSeries[0]].indexedXValueType;
}
else
{
valueType = Common.DataManager.Series[dataSeries[0]].YValueType;
}
if( labelStyle.GetIntervalType() != DateTimeIntervalType.Auto &&
labelStyle.GetIntervalType() != DateTimeIntervalType.Number )
{
if (valueType != ChartValueType.Time &&
valueType != ChartValueType.Date &&
valueType != ChartValueType.DateTimeOffset)
{
valueType = ChartValueType.DateTime;
}
}
// ***********************************
// Pre calculate some values
// ***********************************
double viewMaximum = this.ViewMaximum;
double viewMinimum = this.ViewMinimum;
// ***********************************
// Labels are filled from data series.
// ***********************************
if( fromSeries )
{
int numOfPoints;
numOfPoints = Common.DataManager.GetNumberOfPoints( dataSeries.ToArray() );
// Show end labels
if( endLabels == 1 )
{
// min position
CustomLabels.Add( - 0.5, 0.5, ValueConverter.FormatValue(
this.Common.Chart,
this,
null,
0.0,
this.LabelStyle.Format,
valueType,
ChartElementType.AxisLabels),
false);
}
// Labels from point position
for( int point = 0; point < numOfPoints; point++ )
{
CustomLabels.Add( ((double)point)+ 0.5, ((double)point)+ 1.5,
ValueConverter.FormatValue(
this.Common.Chart,
this,
null,
point + 1,
this.LabelStyle.Format,
valueType,
ChartElementType.AxisLabels),
false);
}
// Show end labels
if( endLabels == 1 )
{
// max position
CustomLabels.Add( ((double)numOfPoints)+ 0.5, ((double)numOfPoints)+ 1.5,
ValueConverter.FormatValue(
this.Common.Chart,
this,
null,
numOfPoints + 1,
this.LabelStyle.Format,
valueType,
ChartElementType.AxisLabels),
false);
}
int pointIndx;
foreach( string seriesIndx in dataSeries )
{
// End labels enabled
if( endLabels == 1 )
pointIndx = 1;
else
pointIndx = 0;
// Set labels from data points labels
foreach( DataPoint dataPoint in Common.DataManager.Series[ seriesIndx ].Points )
{
// Find first row of labels
while( CustomLabels[pointIndx].RowIndex > 0 )
{
pointIndx++;
}
// Add X labels
if( axisType == AxisName.X || axisType == AxisName.X2 )
{
if( dataPoint.AxisLabel.Length > 0 )
{
CustomLabels[pointIndx].Text = dataPoint.AxisLabel;
}
}
pointIndx++;
}
}
}
// ***********************************
// Labels are filled from axis scale.
// ***********************************
else
{
if( viewMinimum == viewMaximum )
return;
double labValue; // Value, which will be converted to text and used for, labels.
double beginPosition; // Begin position for a label
double endPosition; // End position for a label
double start; // Start position for all labels
// Get first series attached to this axis
Series axisSeries = null;
if(axisType == AxisName.X || axisType == AxisName.X2)
{
List<string> seriesArray = ChartArea.GetXAxesSeries((axisType == AxisName.X) ? AxisType.Primary : AxisType.Secondary, ((Axis)this).SubAxisName);
if(seriesArray.Count > 0)
{
axisSeries = Common.DataManager.Series[seriesArray[0]];
if(axisSeries != null && !axisSeries.IsXValueIndexed)
{
axisSeries = null;
}
}
}
// ***********************************
// Check if the AJAX zooming and scrolling mode is enabled.
// Labels are filled slightly different in this case.
// ***********************************
DateTimeIntervalType offsetType = (labelStyle.GetIntervalOffsetType() == DateTimeIntervalType.Auto) ? labelStyle.GetIntervalType() : labelStyle.GetIntervalOffsetType();
// By default start is equal to minimum
start = viewMinimum;
// Adjust start position depending on the interval type
if(!this.ChartArea.chartAreaIsCurcular ||
this.axisType == AxisName.Y ||
this.axisType == AxisName.Y2 )
{
start = ChartHelper.AlignIntervalStart(start, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries);
}
// Move start if there is start position
if( labelStyle.GetIntervalOffset() != 0 && axisSeries == null)
{
start += ChartHelper.GetIntervalSize(start, labelStyle.GetIntervalOffset(),
offsetType, axisSeries, 0, DateTimeIntervalType.Number, true, false);
}
// ***************************************
// Date type
// ***************************************
if( valueType == ChartValueType.DateTime ||
valueType == ChartValueType.Date ||
valueType == ChartValueType.Time ||
valueType == ChartValueType.DateTimeOffset ||
axisSeries != null)
{
double position = start;
double dateInterval;
// Too many labels
if ((viewMaximum - start) / ChartHelper.GetIntervalSize(start, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, 0, DateTimeIntervalType.Number, true) > ChartHelper.MaxNumOfGridlines)
return;
int counter = 0;
double endLabelMaxPosition = viewMaximum - ChartHelper.GetIntervalSize(viewMaximum, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true) / 2f;
double endLabelMinPosition = viewMinimum + ChartHelper.GetIntervalSize(viewMinimum, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true) / 2f;
while( (decimal)position <= (decimal)viewMaximum )
{
dateInterval = ChartHelper.GetIntervalSize(position, labelStyle.GetInterval(), labelStyle.GetIntervalType(), axisSeries, labelStyle.GetIntervalOffset(), offsetType, true);
labValue = position;
// For IsLogarithmic axes
if( this.IsLogarithmic )
{
labValue = Math.Pow( this.logarithmBase, labValue );
}
// Check if we do not exceed max number of elements
if (counter++ > ChartHelper.MaxNumOfGridlines)
{
break;
}
if (endLabels == 0 && position >= endLabelMaxPosition)
{
break;
}
beginPosition = position - dateInterval * 0.5;
endPosition = position + dateInterval * 0.5;
if(endLabels == 0 && position <= endLabelMinPosition)
{
position += dateInterval;
continue;
}
if( (decimal)beginPosition > (decimal)viewMaximum )
{
position += dateInterval;
continue;
}
// NOTE: Fixes issue #6466
// Following code is removed due to the issues caused by the rounding error
//if( (((decimal)beginPosition + (decimal)endPosition) / 2.0m) < (decimal)viewMinimum )
//{
// position += dateInterval;
// continue;
//}
//if ((decimal)viewMaximum < (((decimal)beginPosition + (decimal)endPosition) / 2m))
//{
// position += dateInterval;
// continue;
//}
string pointLabel = GetPointLabel( dataSeries, labValue, !seriesXValuesZeros, indexedSeries );
if( pointLabel.Length == 0 )
{
// Do not draw last label for indexed series
if( position <= this.maximum )
{
// Add a label to the collection
if( position != this.maximum || !Common.DataManager.Series[ dataSeries[0] ].IsXValueIndexed )
{
CustomLabels.Add( beginPosition,
endPosition,
ValueConverter.FormatValue(
this.Common.Chart,
this,
null,
labValue,
this.LabelStyle.Format,
valueType,
ChartElementType.AxisLabels),
false);
}
}
}
else
{
// Add a label to the collection
CustomLabels.Add( beginPosition,
endPosition,
pointLabel,
false);
}
position += dateInterval;
}
}
else
{
// ***************************************
// Scale value type
// ***************************************
// Show First label if Start Label position is used
if( start != viewMinimum )
endLabels = 1;
// Set labels
int labelCounter = 0;
for (double position = start - endLabels * labelStyle.GetInterval(); position < viewMaximum - 1.5 * labelStyle.GetInterval() * (1 - endLabels); position = (double)((decimal)position + (decimal)labelStyle.GetInterval()))
{
// Prevent endless loop that may be caused by very small interval
// and double/decimal rounding errors
++labelCounter;
if(labelCounter > ChartHelper.MaxNumOfGridlines)
{
break;
}
labValue = (double)((decimal)position + (decimal)labelStyle.GetInterval());
// This line is introduce because sometimes 0 value will appear as
// very small value close to zero.
double inter = Math.Log(labelStyle.GetInterval());
double valu = Math.Log(Math.Abs(labValue));
int digits = (int)Math.Abs(inter)+5;
if( digits > 15 )
{
digits = 15;
}
if( Math.Abs(inter) < Math.Abs(valu)-5 )
{
labValue = Math.Round(labValue,digits);
}
// Too many labels
if( ( viewMaximum - start ) / labelStyle.GetInterval() > ChartHelper.MaxNumOfGridlines )
{
return;
}
// For IsLogarithmic axes
if( this.IsLogarithmic )
labValue = Math.Pow( this.logarithmBase, labValue );
beginPosition = (double)((decimal)position + (decimal)labelStyle.GetInterval() * 0.5m);
endPosition = (double)((decimal)position + (decimal)labelStyle.GetInterval() * 1.5m);
if( (decimal)beginPosition > (decimal)viewMaximum )
{
continue;
}
// Show End label if Start Label position is used
// Use decimal type to solve rounding issues
if( (decimal)(( beginPosition + endPosition )/2.0) > (decimal)viewMaximum )
{
continue;
}
string pointLabel = GetPointLabel( dataSeries, labValue, !seriesXValuesZeros, indexedSeries );
if( pointLabel.Length > 15 && labValue < 0.000001)
{
labValue = 0.0;
}
if( pointLabel.Length == 0 )
{
// Do not draw last label for indexed series
if( !(Common.DataManager.Series[ dataSeries[0] ].IsXValueIndexed && position > this.maximum) )
{
// Add a label to the collection
CustomLabels.Add( beginPosition,
endPosition,
ValueConverter.FormatValue(
this.Common.Chart,
this,
null,
labValue,
this.LabelStyle.Format,
valueType,
ChartElementType.AxisLabels),
false);
}
}
else
{
// Add a label to the collection
CustomLabels.Add( beginPosition,
endPosition,
pointLabel,
false);
}
}
}
}
}
/// <summary>
/// This method checks if there is a data point which has value X equal
/// to valuePosition, and returns label from data point if such value exist.
/// If data point with this value not exists empty string will be returned.
/// If all data points have X value zero, index is used instead of X value.
/// </summary>
/// <param name="series">Data series</param>
/// <param name="valuePosition">A value which should be found in data points x values</param>
/// <param name="nonZeroXValues">Series X values are not zeros.</param>
/// <param name="indexedSeries">Series is indexed. All X values are zeros or IsXValueIndexed flag set.</param>
/// <returns>LabelStyle</returns>
private string GetPointLabel(
List<string> series,
double valuePosition,
bool nonZeroXValues,
bool indexedSeries
)
{
// Get max number of data points in the series
int maxPointCount = 0;
foreach (string seriesName in series)
{
Series ser = Common.DataManager.Series[seriesName];
maxPointCount = Math.Max(maxPointCount, ser.Points.Count);
}
// Check if axis only contains axis abels
bool allEmpty = true;
foreach( string seriesName in series )
{
// Get series by name
Series ser = Common.DataManager.Series[ seriesName ];
// Check if series has axis labels set
if ((axisType == AxisName.X || axisType == AxisName.X2) && (margin != 0 || maxPointCount == 1 || !this._autoMinimum) && !ser.IsXValueIndexed)
{
if( ser.Points[ 0 ].AxisLabel.Length > 0 && ser.Points[ ser.Points.Count - 1 ].AxisLabel.Length > 0 )
{
allEmpty = false;
}
}
// Try getting label from the point
if(!ser.noLabelsInPoints || (nonZeroXValues && indexedSeries))
{
string result = GetPointLabel( ser, valuePosition, nonZeroXValues, indexedSeries );
if(!String.IsNullOrEmpty(result))
{
return result;
}
}
// VSTS 140676: Serach for IndexedSeriesLabelsSourceAttr attribute
// to find if we have indexed series as source of formula generated nonindexed series.
String labelSeriesName = ser[DataFormula.IndexedSeriesLabelsSourceAttr];
if (!String.IsNullOrEmpty(labelSeriesName))
{
Series labelsSeries = Common.DataManager.Series[labelSeriesName];
if (labelsSeries != null)
{
string result = GetPointLabel(labelsSeries, valuePosition, nonZeroXValues, true);
if (!String.IsNullOrEmpty(result))
{
return result;
}
}
}
}
if( !allEmpty )
{
return " ";
}
else
{
return "";
}
}
/// <summary>
/// This method checks if there is a data point which has value X equal
/// to valuePosition, and returns label from data point if such value exist.
/// If data point with this value not exists empty string will be returned.
/// If all data points have X value zero, index is used instead of X value.
/// </summary>
/// <param name="series">Data series</param>
/// <param name="valuePosition">A value which should be found in data points x values</param>
/// <param name="nonZeroXValues">Series X values are not zeros.</param>
/// <param name="indexedSeries">Series is indexed. All X values are zeros or IsXValueIndexed flag set.</param>
/// <returns>LabelStyle</returns>
private string GetPointLabel(
Series series,
double valuePosition,
bool nonZeroXValues,
bool indexedSeries)
{
int pointIndx = 1;
if( axisType == AxisName.Y || axisType == AxisName.Y2 )
{
return "";
}
if( !(( axisType == AxisName.X && series.XAxisType == AxisType.Primary ) || ( axisType == AxisName.X2 && series.XAxisType == AxisType.Secondary )) )
{
#if SUBAXES
if(series.XSubAxisName != ((Axis)this).SubAxisName)
{
return "";
}
#endif // SUBAXES
return "";
}
// Loop through all series data points
foreach( DataPoint point in series.Points )
{
// If series is indexed (all X values are zeros or IsXValueIndexed flag set)
if( indexedSeries )
{
// If axis label position matches point index
if( valuePosition == pointIndx )
{
// Use X value if axis label is not set and X values in series are not zeros
if(point.AxisLabel.Length == 0 && nonZeroXValues)
{
return ValueConverter.FormatValue(
this.Common.Chart,
this,
null,
point.XValue,
this.LabelStyle.Format,
series.XValueType,
ChartElementType.AxisLabels);
}
// Return axis label from data point
return point.ReplaceKeywords(point.AxisLabel);
}
}
else
{
// Find x value using Data point X values
if( point.XValue == valuePosition )
{
// Return label
return point.ReplaceKeywords(point.AxisLabel);
}
}
pointIndx++;
}
return "";
}
#endregion
}
}
|