/// <summary> /// Gets position of the axis break line. Break line may be shown as a single /// line or two lines separated with a spacing. /// </summary> /// <param name="graph">Chart graphics.</param> /// <param name="nextSegment">Next segment reference.</param> /// <returns>Position of the axis break line in pixel coordinates.</returns> internal RectangleF GetBreakLinePosition(ChartGraphics graph, AxisScaleSegment nextSegment) { // Start with the plotting rectangle position RectangleF breakPosition = this.axis.PlotAreaPosition.ToRectangleF(); // Find maximum scale value of the current segment and minimuj of the next double from = this.axis.GetLinearPosition(nextSegment.ScaleMinimum); double to = this.axis.GetLinearPosition(this.ScaleMaximum); if (this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left) { breakPosition.Y = (float)Math.Min(from, to); breakPosition.Height = (float)Math.Max(from, to); } else { breakPosition.X = (float)Math.Min(from, to); breakPosition.Width = (float)Math.Max(from, to);; } // Convert to pixels breakPosition = Rectangle.Round(graph.GetAbsoluteRectangle(breakPosition)); // Add border width if (this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left) { breakPosition.Height = (float)Math.Abs(breakPosition.Y - breakPosition.Height); breakPosition.X -= this.axis.ChartArea.BorderWidth; breakPosition.Width += 2 * this.axis.ChartArea.BorderWidth; } else { breakPosition.Width = (float)Math.Abs(breakPosition.X - breakPosition.Width); breakPosition.Y -= this.axis.ChartArea.BorderWidth; breakPosition.Height += 2 * this.axis.ChartArea.BorderWidth; } return(breakPosition); }
/// <summary> /// Ensures that specified axis scale segment is used for all coordinate transformations. /// Set tot NULL to reset. /// </summary> /// <param name="segment"></param> internal void EnforceSegment(AxisScaleSegment segment) { this._enforcedSegment = segment; }
/// <summary> /// Adds a segment to the end of the collection. /// </summary> /// <param name="segment"> /// <see cref="AxisScaleSegment"/> object to add. /// </param> /// <returns> /// Index of the newly added object. /// </returns> public int Add(AxisScaleSegment segment) { return(this.List.Add(segment)); }
/// <summary> /// Paints the axis break line. /// </summary> /// <param name="graph">Chart graphics to use.</param> /// <param name="nextSegment">Axis scale segment next to current.</param> internal void PaintBreakLine(ChartGraphics graph, AxisScaleSegment nextSegment) { // Get break line position RectangleF breakPosition = this.GetBreakLinePosition(graph, nextSegment); // Get top line graphics path GraphicsPath breakLinePathTop = this.GetBreakLinePath(breakPosition, true); GraphicsPath breakLinePathBottom = null; // Clear break line space using chart color behind the area if (breakPosition.Width > 0f && breakPosition.Height > 0f) { // Get bottom line graphics path breakLinePathBottom = this.GetBreakLinePath(breakPosition, false); // Clear plotting area background using (GraphicsPath fillPath = new GraphicsPath()) { // Create fill path out of top and bottom break lines fillPath.AddPath(breakLinePathTop, true); fillPath.Reverse(); fillPath.AddPath(breakLinePathBottom, true); fillPath.CloseAllFigures(); // Use chart back color to fill the area using (Brush fillBrush = this.GetChartFillBrush(graph)) { graph.FillPath(fillBrush, fillPath); // Check if shadow exsits in chart area if (this.axis.ChartArea.ShadowOffset != 0 && !this.axis.ChartArea.ShadowColor.IsEmpty) { // Clear shadow RectangleF shadowPartRect = breakPosition; if (this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left) { shadowPartRect.Y += this.axis.ChartArea.ShadowOffset; shadowPartRect.Height -= this.axis.ChartArea.ShadowOffset; shadowPartRect.X = shadowPartRect.Right - 1; shadowPartRect.Width = this.axis.ChartArea.ShadowOffset + 2; } else { shadowPartRect.X += this.axis.ChartArea.ShadowOffset; shadowPartRect.Width -= this.axis.ChartArea.ShadowOffset; shadowPartRect.Y = shadowPartRect.Bottom - 1; shadowPartRect.Height = this.axis.ChartArea.ShadowOffset + 2; } graph.FillRectangle(fillBrush, shadowPartRect); // Draw new shadow using (GraphicsPath shadowPath = new GraphicsPath()) { shadowPath.AddPath(breakLinePathTop, false); // Define maximum size float size = this.axis.ChartArea.ShadowOffset; if (this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left) { size = Math.Min(size, breakPosition.Height); } else { size = Math.Min(size, breakPosition.Width); } // Define step to increase transperancy int transparencyStep = (int)(this.axis.ChartArea.ShadowColor.A / size); // Set clip region to achieve spacing of the shadow // Start with the plotting rectangle position RectangleF clipRegion = graph.GetAbsoluteRectangle(this.axis.PlotAreaPosition.ToRectangleF()); if (this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left) { clipRegion.X += this.axis.ChartArea.ShadowOffset; clipRegion.Width += this.axis.ChartArea.ShadowOffset; } else { clipRegion.Y += this.axis.ChartArea.ShadowOffset; clipRegion.Height += this.axis.ChartArea.ShadowOffset; } graph.SetClip(graph.GetRelativeRectangle(clipRegion)); // Draw several lines to form shadow for (int index = 0; index < size; index++) { using (Matrix newMatrix = new Matrix()) { // Shift top break line by 1 pixel if (this.axis.AxisPosition == AxisPosition.Right || this.axis.AxisPosition == AxisPosition.Left) { newMatrix.Translate(0f, 1f); } else { newMatrix.Translate(1f, 0f); } shadowPath.Transform(newMatrix); } // Get line color Color color = Color.FromArgb( this.axis.ChartArea.ShadowColor.A - transparencyStep * index, this.axis.ChartArea.ShadowColor); using (Pen shadowPen = new Pen(color, 1)) { // Draw shadow graph.DrawPath(shadowPen, shadowPath); } } graph.ResetClip(); } } } } } // Draw Separator Line(s) if (this.axis.ScaleBreakStyle.BreakLineStyle != BreakLineStyle.None) { using (Pen pen = new Pen(this.axis.ScaleBreakStyle.LineColor, this.axis.ScaleBreakStyle.LineWidth)) { // Set line style pen.DashStyle = graph.GetPenStyle(this.axis.ScaleBreakStyle.LineDashStyle); // Draw break lines graph.DrawPath(pen, breakLinePathTop); if (breakPosition.Width > 0f && breakPosition.Height > 0f) { graph.DrawPath(pen, breakLinePathBottom); } } } // Dispose break line paths breakLinePathTop.Dispose(); breakLinePathTop = null; if (breakLinePathBottom != null) { breakLinePathBottom.Dispose(); breakLinePathBottom = null; } }
/// <summary> /// Fill collection of axis scale segments. /// </summary> /// <param name="axisSegments">Collection of axis segments.</param> private void FillAxisSegmentCollection(AxisScaleSegmentCollection axisSegments) { // Clear axis segments collection axisSegments.Clear(); // Get statistics for the series attached to the axis double minYValue = 0.0; double maxYValue = 0.0; double segmentSize = 0.0; double[] segmentMaxValue = null; double[] segmentMinValue = null; int[] segmentPointNumber = GetSeriesDataStatistics( this._totalNumberOfSegments, out minYValue, out maxYValue, out segmentSize, out segmentMaxValue, out segmentMinValue); if (segmentPointNumber == null) { return; } // Calculate scale maximum and minimum double minimum = minYValue; double maximum = maxYValue; this.axis.EstimateNumberAxis( ref minimum, ref maximum, this.axis.IsStartedFromZero, this.axis.prefferedNumberofIntervals, true, true); // Make sure max/min Y values are not the same if (maxYValue == minYValue) { return; } // Calculate the percentage of the scale range covered by the data range. double dataRangePercent = (maxYValue - minYValue) / ((maximum - minimum) / 100.0); // Get sequences of empty segments ArrayList emptySequences = new ArrayList(); bool doneFlag = false; while (!doneFlag) { doneFlag = true; // Get longest sequence of segments with no points int startSegment = 0; int numberOfSegments = 0; this.GetLargestSequenseOfSegmentsWithNoPoints( segmentPointNumber, out startSegment, out numberOfSegments); // Adjust minimum empty segments number depending on current segments int minEmptySegments = (int)(this._minimumNumberOfEmptySegments * (100.0 / dataRangePercent)); if (axisSegments.Count > 0 && numberOfSegments > 0) { // Find the segment which contain newly found empty segments sequence foreach (AxisScaleSegment axisScaleSegment in axisSegments) { if (startSegment > 0 && (startSegment + numberOfSegments) <= segmentMaxValue.Length - 1) { if (segmentMaxValue[startSegment - 1] >= axisScaleSegment.ScaleMinimum && segmentMinValue[startSegment + numberOfSegments] <= axisScaleSegment.ScaleMaximum) { // Get percentage of segment scale that is empty and suggested for collapsing double segmentScaleRange = axisScaleSegment.ScaleMaximum - axisScaleSegment.ScaleMinimum; double emptySpaceRange = segmentMinValue[startSegment + numberOfSegments] - segmentMaxValue[startSegment - 1]; double emptySpacePercent = emptySpaceRange / (segmentScaleRange / 100.0); emptySpacePercent = emptySpacePercent / 100 * axisScaleSegment.Size; if (emptySpacePercent > minEmptySegments && numberOfSegments > this._minSegmentSize) { minEmptySegments = numberOfSegments; } } } } } // Check if found sequence is long enough if (numberOfSegments >= minEmptySegments) { doneFlag = false; // Store start segment and number of segments in the list emptySequences.Add(startSegment); emptySequences.Add(numberOfSegments); // Check if there are any emty segments sequence found axisSegments.Clear(); if (emptySequences.Count > 0) { double segmentFrom = double.NaN; double segmentTo = double.NaN; // Based on the segments that need to be excluded create axis segments that // will present on the axis scale. int numberOfPoints = 0; for (int index = 0; index < segmentPointNumber.Length; index++) { // Check if current segment is excluded bool excludedSegment = this.IsExcludedSegment(emptySequences, index); // If not excluded segment - update from/to range if they were set if (!excludedSegment && !double.IsNaN(segmentMinValue[index]) && !double.IsNaN(segmentMaxValue[index])) { // Calculate total number of points numberOfPoints += segmentPointNumber[index]; // Set From/To of the visible segment if (double.IsNaN(segmentFrom)) { segmentFrom = segmentMinValue[index]; segmentTo = segmentMaxValue[index]; } else { segmentTo = segmentMaxValue[index]; } } // If excluded or last segment - add current visible segment range if (!double.IsNaN(segmentFrom) && (excludedSegment || index == (segmentPointNumber.Length - 1))) { // Make sure To and From do not match if (segmentTo == segmentFrom) { segmentFrom -= segmentSize; segmentTo += segmentSize; } // Add axis scale segment AxisScaleSegment axisScaleSegment = new AxisScaleSegment(); axisScaleSegment.ScaleMaximum = segmentTo; axisScaleSegment.ScaleMinimum = segmentFrom; axisScaleSegment.Tag = numberOfPoints; axisSegments.Add(axisScaleSegment); // Reset segment range segmentFrom = double.NaN; segmentTo = double.NaN; numberOfPoints = 0; } } } // Calculate the position of each segment this.SetAxisSegmentPosition(axisSegments); } // Make sure we do not exceed specified number of breaks if ((axisSegments.Count - 1) >= this._maximumNumberOfBreaks) { doneFlag = true; } } }
/// <summary> /// Get collection of axis segments to present scale breaks. /// </summary> /// <param name="axisSegments">Collection of axis scale segments.</param> internal void GetAxisSegmentForScaleBreaks(AxisScaleSegmentCollection axisSegments) { // Clear segment collection axisSegments.Clear(); // Check if scale breaks are enabled if (this.IsEnabled()) { // Fill collection of segments this.FillAxisSegmentCollection(axisSegments); // Check if more than 1 segments were defined if (axisSegments.Count >= 1) { // Get index of segment which scale should start from zero int startFromZeroSegmentIndex = this.GetStartScaleFromZeroSegmentIndex(axisSegments); // Calculate segment interaval and round the scale int index = 0; foreach (AxisScaleSegment axisScaleSegment in axisSegments) { // Check if segment scale should start from zero bool startFromZero = (index == startFromZeroSegmentIndex) ? true : false; // Calculate interval and round scale double minimum = axisScaleSegment.ScaleMinimum; double maximum = axisScaleSegment.ScaleMaximum; axisScaleSegment.Interval = this.axis.EstimateNumberAxis( ref minimum, ref maximum, startFromZero, this.axis.prefferedNumberofIntervals, true, true); axisScaleSegment.ScaleMinimum = minimum; axisScaleSegment.ScaleMaximum = maximum; // Make sure new scale break value range do not exceed axis current scale if (axisScaleSegment.ScaleMinimum < this.axis.Minimum) { axisScaleSegment.ScaleMinimum = this.axis.Minimum; } if (axisScaleSegment.ScaleMaximum > this.axis.Maximum) { axisScaleSegment.ScaleMaximum = this.axis.Maximum; } // Increase segment index ++index; } // Defined axis scale segments cannot overlap. // Check for overlapping and join segments or readjust min/max. bool adjustPosition = false; AxisScaleSegment prevSegment = axisSegments[0]; for (int segmentIndex = 1; segmentIndex < axisSegments.Count; segmentIndex++) { AxisScaleSegment currentSegment = axisSegments[segmentIndex]; if (currentSegment.ScaleMinimum <= prevSegment.ScaleMaximum) { if (currentSegment.ScaleMaximum > prevSegment.ScaleMaximum) { // If segments are partially overlapping make sure the previous // segment scale is extended prevSegment.ScaleMaximum = currentSegment.ScaleMaximum; } // Remove the overlapped segment adjustPosition = true; axisSegments.RemoveAt(segmentIndex); --segmentIndex; } else { prevSegment = currentSegment; } } // Calculate the position of each segment if (adjustPosition) { this.SetAxisSegmentPosition(axisSegments); } } } }