1 //-------------------------------------------------------------
2 // <copyright company=
\92Microsoft Corporation
\92>
3 // Copyright © Microsoft Corporation. All Rights Reserved.
5 //-------------------------------------------------------------
6 // @owner=alexgor, deliant
7 //=================================================================
8 // File: ChartElement.cs
10 // Namespace: System.Web.UI.WebControls[Windows.Forms].Charting
12 // Classes: ChartHelper
14 // Purpose: The chart element is base class for the big number
15 // of classes. It stores common methods and data.
17 // Reviewed: GS - August 2, 2002
18 // AG - August 8, 2002
19 // AG - Microsoft 16, 2007
21 //===================================================================
24 #region Used namespaces
28 using System.Drawing.Drawing2D;
29 using System.Collections;
30 using System.Diagnostics.CodeAnalysis;
31 using System.Collections.Generic;
32 using System.Collections.ObjectModel;
37 namespace System.Windows.Forms.DataVisualization.Charting
39 namespace System.Web.UI.DataVisualization.Charting
46 /// An enumeration that specifies a label alignment.
51 public enum LabelAlignmentStyles
54 /// Label is aligned to the top of the data point.
58 /// Label is aligned to the bottom of the data point.
62 /// Label is aligned to the right of the data point.
66 /// Label is aligned to the left of the data point.
70 /// Label is aligned to the top-left corner of the data point.
74 /// Label is aligned to the top-right corner of the data point.
78 /// Label is aligned to the bottom-left of the data point.
82 /// Label is aligned to the bottom-right of the data point.
86 /// Label is aligned to the center of the data point.
92 /// An enumeration of chart types.
94 public enum SeriesChartType
102 /// FastPoint chart type.
107 /// Bubble chart type.
115 /// Spline chart type.
119 /// StepLine chart type.
124 /// FastLine chart type.
133 /// Stacked bar chart type.
137 /// Hundred percent stacked bar chart type.
141 /// Column chart type.
145 /// Stacked column chart type.
149 /// Hundred percent stacked column chart type.
157 /// Spline area chart type.
161 /// Stacked area chart type.
165 /// Hundred percent stacked area chart type.
173 /// Doughnut chart type.
177 /// Stock chart type.
181 /// CandleStick chart type.
185 /// Range chart type.
189 /// Spline range chart type.
193 /// RangeBar chart type.
197 /// Range column chart type.
201 /// Radar chart type.
205 /// Polar chart type.
209 /// Error bar chart type.
213 /// Box plot chart type.
217 /// Renko chart type.
219 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Renko")]
222 /// ThreeLineBreak chart type.
228 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Kagi")]
231 /// PointAndFigure chart type.
235 /// Funnel chart type.
239 /// Pyramid chart type.
245 /// Axis Arrow orientation
247 internal enum ArrowOrientation
250 /// Arrow direction is Right - Left
254 /// Arrow direction is Left - Right
258 /// Arrow direction is Bottom - Top
262 /// Arrow direction is Top - Bottom
268 /// An enumeration of image alignment.
270 public enum ChartImageAlignmentStyle
273 /// The mage is aligned to the top left corner of the chart element.
277 /// The image is aligned to the top boundary of the chart element.
281 /// The image is aligned to the top right corner of the chart element.
285 /// The image is aligned to the right boundary of the chart element.
289 /// The image is aligned to the bottom right corner of the chart element.
293 /// The image is aligned to the bottom boundary of the chart element.
297 /// The image is aligned to the bottom left corner of the chart element.
301 /// The image is aligned to the left boundary of the chart element.
305 /// The image is aligned in the center of the chart element.
311 /// An enumeration that specifies a background image drawing mode.
313 public enum ChartImageWrapMode
316 /// Background image is scaled to fit the entire chart element.
318 Scaled = WrapMode.Clamp,
321 /// Background image is tiled to fit the entire chart element.
323 Tile = WrapMode.Tile,
326 /// Every other tiled image is reversed around the X-axis.
328 TileFlipX = WrapMode.TileFlipX,
331 /// Every other tiled image is reversed around the X-axis and Y-axis.
333 TileFlipXY = WrapMode.TileFlipXY,
336 /// Every other tiled image is reversed around the Y-axis.
338 TileFlipY = WrapMode.TileFlipY,
341 /// Background image is not scaled.
343 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Unscaled")]
348 /// An enumeration that specifies the state of an axis.
350 public enum AxisEnabled
353 /// The axis is only enabled if it used to plot a Series.
358 /// The axis is always enabled.
363 /// The axis is never enabled.
370 /// An enumeration of units of measurement of an interval.
372 public enum DateTimeIntervalType
375 /// Automatically determined by the Chart control.
380 /// The interval is numerical.
385 /// The interval is years.
390 /// The interval is months.
395 /// The interval is weeks.
400 /// The interval is days.
405 /// The interval is hours.
410 /// The interval is minutes.
415 /// The interval is seconds.
420 /// The interval is milliseconds.
425 /// The interval type is not defined.
431 /// An enumeration that specifies value types for various chart properties
433 public enum ChartValueType
436 /// Property type is set automatically by the Chart control.
463 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly",
464 Justification = "These names are patterned after the standard CLR types for consistency")]
470 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly",
471 Justification = "These names are patterned after the standard CLR types for consistency")]
485 /// Date portion of the DateTime value.
490 /// Time portion of the DateTime value.
495 /// DateTime with offset
501 /// An enumeration that specifies a hatching style.
503 public enum ChartHatchStyle
506 /// No hatching style.
510 /// Backward diagonal style.
518 /// Dark downward diagonal style.
520 DarkDownwardDiagonal,
522 /// Dark horizontal style.
526 /// Dark upward diagonal style.
530 /// Dark vertical style.
534 /// Dashed downward diagonal style.
536 DashedDownwardDiagonal,
538 /// Dashed horizontal style.
542 /// Dashed upward diagonal style.
544 DashedUpwardDiagonal,
546 /// Dashed vertical style.
550 /// Diagonal brick style.
554 /// Diagonal cross style.
562 /// Dotted diamond style.
566 /// Dotted grid style.
570 /// Forward diagonal style.
574 /// Horizontal style.
578 /// Horizontal brick style.
582 /// Large checker board style.
584 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "CheckerBoard")]
587 /// Large confetti style.
591 /// Large grid style.
595 /// Light downward diagonal style.
597 LightDownwardDiagonal,
599 /// Light horizontal style.
603 /// Light upward diagonal style.
607 /// Light vertical style.
611 /// Narrow horizontal style.
615 /// Narrow vertical style.
619 /// Outlined diamond style.
679 /// Small checker board style.
681 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "CheckerBoard")]
684 /// Small confetti style.
688 /// Small grid style.
692 /// Solid diamond style.
716 /// Wide downward diagonal style.
718 WideDownwardDiagonal,
720 /// Wide upward diagonal style.
726 [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "ZigZag")]
727 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Zig")]
728 [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Zag")]
733 /// An enumeration that specifies the level of anti-aliasing quality.
735 public enum TextAntiAliasingQuality
738 /// Normal anti-aliasing quality.
742 /// High anti-aliasing quality.
746 /// System default anti-aliasing quality.
752 /// An enumeration of anti-aliasing flags.
755 public enum AntiAliasingStyles
758 /// No anti-aliasing.
763 /// Use anti-aliasing when drawing text.
768 /// Use anti-aliasing when drawing grahics primitives (e.g. lines, rectangle)
773 /// Use anti-alias for everything.
775 All = Text | Graphics
780 /// An enumeration of marker styles.
782 public enum MarkerStyle
785 /// No marker is displayed for the series/data point.
790 /// A square marker is displayed.
795 /// A circle marker is displayed.
800 /// A diamond-shaped marker is displayed.
805 /// A triangular marker is displayed.
810 /// A cross-shaped marker is displayed.
815 /// A 4-point star-shaped marker is displayed.
820 /// A 5-point star-shaped marker is displayed.
825 /// A 6-point star-shaped marker is displayed.
830 /// A 10-point star-shaped marker is displayed.
837 /// An enumeration of gradient styles.
839 public enum GradientStyle
842 /// No gradient is used.
847 /// Gradient is applied from left to right.
852 /// Gradient is applied from top to bottom.
857 /// Gradient is applied from the center outwards.
862 /// Gradient is applied diagonally from left to right.
867 /// Gradient is applied diagonally from right to left.
872 /// Gradient is applied horizontally from the center outwards.
877 /// Gradient is applied vertically from the center outwards.
887 /// Common chart helper methods used across different chart elements.
889 internal class ChartHelper
894 /// Maximum number of grid lines per Axis
896 internal const int MaxNumOfGridlines = 10000;
903 /// Private constructor to avoid instantiating the class
905 private ChartHelper() { }
907 #endregion // Constructor
912 /// Adjust the beginnin of the first interval depending on the type and size.
914 /// <param name="start">Original start point.</param>
915 /// <param name="intervalSize">Interval size.</param>
916 /// <param name="type">AxisName of the interval (Month, Year, ...).</param>
917 /// <returns>Adjusted interval start position as double.</returns>
918 internal static double AlignIntervalStart(double start, double intervalSize, DateTimeIntervalType type)
920 return AlignIntervalStart(start, intervalSize, type, null);
924 /// Adjust the beginnin of the first interval depending on the type and size.
926 /// <param name="start">Original start point.</param>
927 /// <param name="intervalSize">Interval size.</param>
928 /// <param name="type">AxisName of the interval (Month, Year, ...).</param>
929 /// <param name="series">First series connected to the axis.</param>
930 /// <returns>Adjusted interval start position as double.</returns>
931 internal static double AlignIntervalStart(double start, double intervalSize, DateTimeIntervalType type, Series series)
933 return AlignIntervalStart( start, intervalSize, type, series, true );
937 /// Adjust the beginnin of the first interval depending on the type and size.
939 /// <param name="start">Original start point.</param>
940 /// <param name="intervalSize">Interval size.</param>
941 /// <param name="type">AxisName of the interval (Month, Year, ...).</param>
942 /// <param name="series">First series connected to the axis.</param>
943 /// <param name="majorInterval">Interval is used for major gridlines or tickmarks.</param>
944 /// <returns>Adjusted interval start position as double.</returns>
945 internal static double AlignIntervalStart(double start, double intervalSize, DateTimeIntervalType type, Series series, bool majorInterval)
947 // Special case for indexed series
948 if(series != null && series.IsXValueIndexed)
950 if(type == DateTimeIntervalType.Auto ||
951 type == DateTimeIntervalType.Number)
963 return -(series.Points.Count + 1);
966 // Non indexed series
969 // Do not adjust start position for these interval type
970 if(type == DateTimeIntervalType.Auto ||
971 type == DateTimeIntervalType.Number)
976 // Get the beginning of the interval depending on type
977 DateTime newStartDate = DateTime.FromOADate(start);
979 // Adjust the months interval depending on size
980 if(intervalSize > 0.0 && intervalSize != 1.0)
982 if(type == DateTimeIntervalType.Months && intervalSize <= 12.0 && intervalSize > 1)
984 // Make sure that the beginning is aligned correctly for cases
985 // like quarters and half years
986 DateTime resultDate = newStartDate;
987 DateTime sizeAdjustedDate = new DateTime(newStartDate.Year, 1, 1, 0, 0, 0);
988 while(sizeAdjustedDate < newStartDate)
990 resultDate = sizeAdjustedDate;
991 sizeAdjustedDate = sizeAdjustedDate.AddMonths((int)intervalSize);
994 newStartDate = resultDate;
995 return newStartDate.ToOADate();
999 // Check interval type
1002 case(DateTimeIntervalType.Years):
1003 int year = (int)((int)(newStartDate.Year / intervalSize) * intervalSize);
1008 newStartDate = new DateTime(year,
1012 case(DateTimeIntervalType.Months):
1013 int month = (int)((int)(newStartDate.Month / intervalSize) * intervalSize);
1018 newStartDate = new DateTime(newStartDate.Year,
1022 case(DateTimeIntervalType.Days):
1023 int day = (int)((int)(newStartDate.Day / intervalSize) * intervalSize);
1028 newStartDate = new DateTime(newStartDate.Year,
1029 newStartDate.Month, day, 0, 0, 0);
1032 case(DateTimeIntervalType.Hours):
1033 int hour = (int)((int)(newStartDate.Hour / intervalSize) * intervalSize);
1034 newStartDate = new DateTime(newStartDate.Year,
1035 newStartDate.Month, newStartDate.Day, hour, 0, 0);
1038 case(DateTimeIntervalType.Minutes):
1039 int minute = (int)((int)(newStartDate.Minute / intervalSize) * intervalSize);
1040 newStartDate = new DateTime(newStartDate.Year,
1048 case(DateTimeIntervalType.Seconds):
1049 int second = (int)((int)(newStartDate.Second / intervalSize) * intervalSize);
1050 newStartDate = new DateTime(newStartDate.Year,
1054 newStartDate.Minute,
1059 case(DateTimeIntervalType.Milliseconds):
1060 int milliseconds = (int)((int)(newStartDate.Millisecond / intervalSize) * intervalSize);
1061 newStartDate = new DateTime(newStartDate.Year,
1065 newStartDate.Minute,
1066 newStartDate.Second,
1070 case(DateTimeIntervalType.Weeks):
1072 // NOTE: Code below was changed to fix issue #5962
1073 // Elements that have interval set to weeks should be aligned to the
1074 // nearest Monday no matter how many weeks is the interval.
1075 //newStartDate = newStartDate.AddDays(-((int)newStartDate.DayOfWeek * intervalSize));
1076 newStartDate = newStartDate.AddDays(-((int)newStartDate.DayOfWeek));
1077 newStartDate = new DateTime(newStartDate.Year,
1078 newStartDate.Month, newStartDate.Day, 0, 0, 0);
1082 return newStartDate.ToOADate();
1088 /// Gets interval size as double number.
1090 /// <param name="current">Current value.</param>
1091 /// <param name="interval">Interval size.</param>
1092 /// <param name="type">AxisName of the interval (Month, Year, ...).</param>
1093 /// <returns>Interval size as double.</returns>
1094 internal static double GetIntervalSize(double current, double interval, DateTimeIntervalType type)
1096 return GetIntervalSize(
1102 DateTimeIntervalType.Number,
1108 /// Gets interval size as double number.
1110 /// <param name="current">Current value.</param>
1111 /// <param name="interval">Interval size.</param>
1112 /// <param name="type">AxisName of the interval (Month, Year, ...).</param>
1113 /// <param name="series">First series connected to the axis.</param>
1114 /// <param name="intervalOffset">Offset size.</param>
1115 /// <param name="intervalOffsetType">Offset type(Month, Year, ...).</param>
1116 /// <param name="forceIntIndex">Force Integer indexed</param>
1117 /// <returns>Interval size as double.</returns>
1118 internal static double GetIntervalSize(
1121 DateTimeIntervalType type,
1123 double intervalOffset,
1124 DateTimeIntervalType intervalOffsetType,
1127 return GetIntervalSize(
1139 /// Gets interval size as double number.
1141 /// <param name="current">Current value.</param>
1142 /// <param name="interval">Interval size.</param>
1143 /// <param name="type">AxisName of the interval (Month, Year, ...).</param>
1144 /// <param name="series">First series connected to the axis.</param>
1145 /// <param name="intervalOffset">Offset size.</param>
1146 /// <param name="intervalOffsetType">Offset type(Month, Year, ...).</param>
1147 /// <param name="forceIntIndex">Force Integer indexed</param>
1148 /// <param name="forceAbsInterval">Force Integer indexed</param>
1149 /// <returns>Interval size as double.</returns>
1150 internal static double GetIntervalSize(
1153 DateTimeIntervalType type,
1155 double intervalOffset,
1156 DateTimeIntervalType intervalOffsetType,
1158 bool forceAbsInterval)
1160 // AxisName is not date.
1161 if( type == DateTimeIntervalType.Number || type == DateTimeIntervalType.Auto )
1166 // Special case for indexed series
1167 if(series != null && series.IsXValueIndexed)
1169 // Check point index
1170 int pointIndex = (int)Math.Ceiling(current - 1);
1175 if(pointIndex >= series.Points.Count || series.Points.Count <= 1)
1180 // Get starting and ending values of the closest interval
1181 double adjuster = 0;
1182 double xValue = series.Points[pointIndex].XValue;
1183 xValue = AlignIntervalStart(xValue, 1, type, null);
1184 double xEndValue = xValue + GetIntervalSize(xValue, interval, type);
1185 xEndValue += GetIntervalSize(xEndValue, intervalOffset, intervalOffsetType);
1186 xValue += GetIntervalSize(xValue, intervalOffset, intervalOffsetType);
1187 if(intervalOffset < 0)
1189 xValue = xValue + GetIntervalSize(xValue, interval, type);
1190 xEndValue = xEndValue + GetIntervalSize(xEndValue, interval, type);
1193 // The first point in the series
1194 if(pointIndex == 0 && current < 0)
1196 // Round the first point value depending on the interval type
1197 DateTime dateValue = DateTime.FromOADate(series.Points[pointIndex].XValue);
1198 DateTime roundedDateValue = dateValue;
1201 case(DateTimeIntervalType.Years): // Ignore hours,...
1202 roundedDateValue = new DateTime(dateValue.Year,
1203 dateValue.Month, dateValue.Day, 0, 0, 0);
1206 case(DateTimeIntervalType.Months): // Ignore hours,...
1207 roundedDateValue = new DateTime(dateValue.Year,
1208 dateValue.Month, dateValue.Day, 0, 0, 0);
1211 case(DateTimeIntervalType.Days): // Ignore hours,...
1212 roundedDateValue = new DateTime(dateValue.Year,
1213 dateValue.Month, dateValue.Day, 0, 0, 0);
1216 case(DateTimeIntervalType.Hours): //
1217 roundedDateValue = new DateTime(dateValue.Year,
1218 dateValue.Month, dateValue.Day, dateValue.Hour,
1219 dateValue.Minute, 0);
1222 case(DateTimeIntervalType.Minutes):
1223 roundedDateValue = new DateTime(dateValue.Year,
1231 case(DateTimeIntervalType.Seconds):
1232 roundedDateValue = new DateTime(dateValue.Year,
1241 case(DateTimeIntervalType.Weeks):
1242 roundedDateValue = new DateTime(dateValue.Year,
1243 dateValue.Month, dateValue.Day, 0, 0, 0);
1247 // The first point value is exactly on the interval boundaries
1248 if(roundedDateValue.ToOADate() == xValue || roundedDateValue.ToOADate() == xEndValue)
1250 return - current + 1;
1254 // Adjuster of 0.5 means that position should be between points
1256 while(pointIndex < series.Points.Count)
1258 if(series.Points[pointIndex].XValue >= xEndValue)
1260 if(series.Points[pointIndex].XValue > xEndValue && !forceIntIndex)
1270 // If last point outside of the max series index
1271 if(pointIndex == series.Points.Count)
1273 pointIndex += series.Points.Count/5 + 1;
1276 double size = (pointIndex + 1) - current + adjuster;
1278 return (size != 0) ? size : interval;
1281 // Non indexed series
1284 DateTime date = DateTime.FromOADate(current);
1285 TimeSpan span = new TimeSpan(0);
1287 if(type == DateTimeIntervalType.Days)
1289 span = TimeSpan.FromDays(interval);
1291 else if(type == DateTimeIntervalType.Hours)
1293 span = TimeSpan.FromHours(interval);
1295 else if(type == DateTimeIntervalType.Milliseconds)
1297 span = TimeSpan.FromMilliseconds(interval);
1299 else if(type == DateTimeIntervalType.Seconds)
1301 span = TimeSpan.FromSeconds(interval);
1303 else if(type == DateTimeIntervalType.Minutes)
1305 span = TimeSpan.FromMinutes(interval);
1307 else if(type == DateTimeIntervalType.Weeks)
1309 span = TimeSpan.FromDays(7.0 * interval);
1311 else if(type == DateTimeIntervalType.Months)
1313 // Special case handling when current date points
1314 // to the last day of the month
1315 bool lastMonthDay = false;
1316 if(date.Day == DateTime.DaysInMonth(date.Year, date.Month))
1318 lastMonthDay = true;
1321 // Add specified amount of months
1322 date = date.AddMonths((int)Math.Floor(interval));
1323 span = TimeSpan.FromDays(30.0 * ( interval - Math.Floor(interval) ));
1325 // Check if last month of the day was used
1326 if(lastMonthDay && span.Ticks == 0)
1328 // Make sure the last day of the month is selected
1329 int daysInMobth = DateTime.DaysInMonth(date.Year, date.Month);
1330 date = date.AddDays(daysInMobth - date.Day);
1333 else if(type == DateTimeIntervalType.Years)
1335 date = date.AddYears((int)Math.Floor(interval));
1336 span = TimeSpan.FromDays(365.0 * ( interval - Math.Floor(interval) ));
1339 // Check if an absolute interval size must be returned
1340 double result = date.Add(span).ToOADate() - current;
1341 if(forceAbsInterval)
1343 result = Math.Abs(result);
1350 /// Check if series is indexed. IsXValueIndexed flag is set or all X values are zeros.
1352 /// <param name="series">Data series to test.</param>
1353 /// <returns>True if series is indexed.</returns>
1354 static internal bool IndexedSeries( Series series)
1356 // X value indexed flag set
1357 if (series.IsXValueIndexed)
1362 if (Utilities.CustomPropertyRegistry.IsXAxisQuantitativeChartTypes.Contains(series.ChartType) &&
1363 series.IsCustomPropertySet(Utilities.CustomPropertyName.IsXAxisQuantitative))
1365 string attribValue = series[Utilities.CustomPropertyName.IsXAxisQuantitative];
1366 if (String.Compare(attribValue, "True", StringComparison.OrdinalIgnoreCase) == 0)
1372 // Check if series has all X values set to zero
1373 return SeriesXValuesZeros(series);
1377 /// Check if all data points in the series have X value set to 0.
1379 /// <param name="series">Data series to check.</param>
1380 static private bool SeriesXValuesZeros( Series series )
1382 // Check if X value zeros check was already done
1383 if(series.xValuesZerosChecked)
1385 return series.xValuesZeros;
1389 series.xValuesZerosChecked = true;
1390 series.xValuesZeros = true;
1391 foreach( DataPoint point in series.Points )
1393 if( point.XValue != 0.0 )
1395 // If any data point has value different than 0 return false
1396 series.xValuesZeros = false;
1400 return series.xValuesZeros;
1404 /// Check if any series is indexed. IsXValueIndexed flag is set or all X values are zeros.
1406 /// <param name="common">Reference to common chart classes.</param>
1407 /// <param name="series">Data series names.</param>
1408 /// <returns>True if any series is indexed.</returns>
1409 static internal bool IndexedSeries(CommonElements common, params string[] series)
1412 bool zeroXValues = true;
1413 foreach (string ser in series)
1415 Series localSeries = common.DataManager.Series[ser];
1417 // Check series indexed flag
1418 if (localSeries.IsXValueIndexed)
1420 // If flag set in at least one series - all series are indexed
1424 // Check if series has all X values set to zero
1425 if (zeroXValues && !IndexedSeries(localSeries))
1427 zeroXValues = false;
1435 /// Check if all data points in many series have X value set to 0.
1437 /// <param name="common">Reference to common chart classes.</param>
1438 /// <param name="series">Data series.</param>
1439 /// <returns>True if all data points have value 0.</returns>
1440 static internal bool SeriesXValuesZeros(CommonElements common, params string[] series)
1443 foreach( string ser in series )
1445 // Check one series X values
1446 if(!SeriesXValuesZeros(common.DataManager.Series[ ser ]))
1457 #endregion //ChartElement