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