/// <summary> /// Draw the this <see cref="CurveItem"/> to the specified <see cref="Graphics"/> /// device. The format (stair-step or line) of the curve is defined by the <see cref="StepType"/> property. The routine only draws the line segments; /// the symbols are drawn by the /// <see cref="Symbol.Draw"/> method. This method is normally only called by the Draw method of the /// <see cref="CurveItem"/> object /// </summary> /// <param name="g"> /// A graphic device object to be drawn into. This is normally e.Graphics from the PaintEventArgs argument to the Paint() method. /// </param> /// <param name="pane"> /// A reference to the <see cref="GraphPane"/> object that is the parent or owner of this object. /// </param> /// <param name="curve"> /// A <see cref="LineItem"/> representing this curve. /// </param> /// <param name="scaleFactor"> /// The scaling factor to be used for rendering objects. This is calculated and passed down by the parent <see cref="GraphPane"/> object using the /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust font sizes, etc. according to the actual size of the graph. /// </param> public void DrawCurveOriginal(Graphics g, GraphPane pane, CurveItem curve, float scaleFactor) { Line source = this; if (curve.IsSelected) { source = Selection.Line; } float tmpX, tmpY, lastX = float.MaxValue, lastY = float.MaxValue; double curX, curY, lowVal; PointPair curPt, lastPt = new PointPair(); bool lastBad = true; IPointList points = curve.Points; ValueHandler valueHandler = new ValueHandler(pane, false); Axis yAxis = curve.GetYAxis(pane); Axis xAxis = curve.GetXAxis(pane); bool xIsLog = xAxis._scale.IsLog; bool yIsLog = yAxis._scale.IsLog; float minX = pane.Chart.Rect.Left; float maxX = pane.Chart.Rect.Right; float minY = pane.Chart.Rect.Top; float maxY = pane.Chart.Rect.Bottom; using (Pen pen = source.GetPen(pane, scaleFactor)) { if (points != null && !this._color.IsEmpty && this.IsVisible) { // bool lastOut = false; bool isOut; // Loop over each point in the curve for (int i = 0; i < points.Count; i++) { curPt = points[i]; if (pane.LineType == LineType.Stack) { if (!valueHandler.GetValues(curve, i, out curX, out lowVal, out curY)) { curX = PointPairBase.Missing; curY = PointPairBase.Missing; } } else { curX = curPt.X; curY = curPt.Y; } // Any value set to double max is invalid and should be skipped // This is used for calculated values that are out of range, divide // by zero, etc. // Also, any value <= zero on a log scale is invalid if (curX == PointPairBase.Missing || curY == PointPairBase.Missing || double.IsNaN(curX) || double.IsNaN(curY) || double.IsInfinity(curX) || double.IsInfinity(curY) || (xIsLog && curX <= 0.0) || (yIsLog && curY <= 0.0)) { // If the point is invalid, then make a linebreak only if IsIgnoreMissing is false // LastX and LastY are always the last valid point, so this works out lastBad = lastBad || !pane.IsIgnoreMissing; isOut = true; } else { // Transform the current point from user scale units to // screen coordinates tmpX = xAxis.Scale.Transform(curve.IsOverrideOrdinal, i, curX); tmpY = yAxis.Scale.Transform(curve.IsOverrideOrdinal, i, curY); isOut = (tmpX < minX && lastX < minX) || (tmpX > maxX && lastX > maxX) || (tmpY < minY && lastY < minY) || (tmpY > maxY && lastY > maxY); if (!lastBad) { try { // GDI+ plots the data wrong and/or throws an exception for // outrageous coordinates, so we do a sanity check here if (lastX > 5000000 || lastX < -5000000 || lastY > 5000000 || lastY < -5000000 || tmpX > 5000000 || tmpX < -5000000 || tmpY > 5000000 || tmpY < -5000000) { this.InterpolatePoint(g, pane, curve, lastPt, scaleFactor, pen, lastX, lastY, tmpX, tmpY); } else if (!isOut) { if (!curve.IsSelected && this._gradientFill.IsGradientValueType) { using (Pen tPen = this.GetPen(pane, scaleFactor, lastPt)) { if (this.StepType == StepType.NonStep) { g.DrawLine(tPen, lastX, lastY, tmpX, tmpY); } else if (this.StepType == StepType.ForwardStep) { g.DrawLine(tPen, lastX, lastY, tmpX, lastY); g.DrawLine(tPen, tmpX, lastY, tmpX, tmpY); } else if (this.StepType == StepType.RearwardStep) { g.DrawLine(tPen, lastX, lastY, lastX, tmpY); g.DrawLine(tPen, lastX, tmpY, tmpX, tmpY); } else if (this.StepType == StepType.ForwardSegment) { g.DrawLine(tPen, lastX, lastY, tmpX, lastY); } else { g.DrawLine(tPen, lastX, tmpY, tmpX, tmpY); } } } else { if (this.StepType == StepType.NonStep) { g.DrawLine(pen, lastX, lastY, tmpX, tmpY); } else if (this.StepType == StepType.ForwardStep) { g.DrawLine(pen, lastX, lastY, tmpX, lastY); g.DrawLine(pen, tmpX, lastY, tmpX, tmpY); } else if (this.StepType == StepType.RearwardStep) { g.DrawLine(pen, lastX, lastY, lastX, tmpY); g.DrawLine(pen, lastX, tmpY, tmpX, tmpY); } else if (this.StepType == StepType.ForwardSegment) { g.DrawLine(pen, lastX, lastY, tmpX, lastY); } else if (this.StepType == StepType.RearwardSegment) { g.DrawLine(pen, lastX, tmpY, tmpX, tmpY); } } } } catch { this.InterpolatePoint(g, pane, curve, lastPt, scaleFactor, pen, lastX, lastY, tmpX, tmpY); } } lastPt = curPt; lastX = tmpX; lastY = tmpY; lastBad = false; // lastOut = isOut; } } } } }
/// <summary> /// Determine the coords for the rectangle associated with a specified point for this <see cref="CurveItem"/> /// </summary> /// <param name="pane"> /// The <see cref="GraphPane"/> to which this curve belongs /// </param> /// <param name="i"> /// The index of the point of interest /// </param> /// <param name="coords"> /// A list of coordinates that represents the "rect" for this point (used in an html AREA tag) /// </param> /// <returns> /// true if it's a valid point, false otherwise /// </returns> public override bool GetCoords(GraphPane pane, int i, out string coords) { coords = string.Empty; if (i < 0 || i >= this._points.Count) { return false; } PointPair pt = this._points[i]; if (pt.IsInvalid) { return false; } double x, y, z; ValueHandler valueHandler = new ValueHandler(pane, false); valueHandler.GetValues(this, i, out x, out z, out y); Axis yAxis = this.GetYAxis(pane); Axis xAxis = this.GetXAxis(pane); PointF pixPt = new PointF(xAxis.Scale.Transform(this._isOverrideOrdinal, i, x), yAxis.Scale.Transform(this._isOverrideOrdinal, i, y)); if (!pane.Chart.Rect.Contains(pixPt)) { return false; } float halfSize = this._symbol.Size * pane.CalcScaleFactor(); coords = string.Format("{0:f0},{1:f0},{2:f0},{3:f0}", pixPt.X - halfSize, pixPt.Y - halfSize, pixPt.X + halfSize, pixPt.Y + halfSize); return true; }
/// <summary> /// Build an array of <see cref="PointF"/> values (pixel coordinates) that represents the low values for the current curve. /// </summary> /// <remarks> /// Note that this drawing routine ignores <see cref="PointPairBase.Missing"/> /// values, but it does not "break" the line to indicate values are missing. /// </remarks> /// <param name="pane"> /// A reference to the <see cref="GraphPane"/> object that is the parent or owner of this object. /// </param> /// <param name="curve"> /// A <see cref="LineItem"/> representing this curve. /// </param> /// <param name="arrPoints"> /// An array of <see cref="PointF"/> values in pixel coordinates representing the current curve. /// </param> /// <param name="count"> /// The number of points contained in the "arrPoints" parameter. /// </param> /// <returns> /// true for a successful points array build, false for data problems /// </returns> public bool BuildLowPointsArray(GraphPane pane, CurveItem curve, out PointF[] arrPoints, out int count) { arrPoints = null; count = 0; IPointList points = curve.Points; if (this.IsVisible && !this.Color.IsEmpty && points != null) { int index = 0; float curX, curY, lastX = 0, lastY = 0; double x, y, hiVal; ValueHandler valueHandler = new ValueHandler(pane, false); // Step type plots get twice as many points. Always add three points so there is // room to close out the curve for area fills. arrPoints = new PointF[(this._stepType == StepType.NonStep ? 1 : 2) * (pane.LineType == LineType.Stack ? 2 : 1) * points.Count + 1]; // Loop backwards over all points in the curve // In this case an array of points was already built forward by BuildPointsArray(). // This time we build backwards to complete a loop around the area between two curves. for (int i = points.Count - 1; i >= 0; i--) { // Make sure the current point is valid if (!points[i].IsInvalid) { // Get the user scale values for the current point valueHandler.GetValues(curve, i, out x, out y, out hiVal); if (x == PointPairBase.Missing || y == PointPairBase.Missing) { continue; } // Transform the user scale values to pixel locations Axis xAxis = curve.GetXAxis(pane); curX = xAxis.Scale.Transform(curve.IsOverrideOrdinal, i, x); Axis yAxis = curve.GetYAxis(pane); curY = yAxis.Scale.Transform(curve.IsOverrideOrdinal, i, y); // Add the pixel value pair into the points array // Two points are added for step type curves // ignore step-type setting for smooth curves if (this._isSmooth || index == 0 || this.StepType == StepType.NonStep) { arrPoints[index].X = curX; arrPoints[index].Y = curY; } else if (this.StepType == StepType.ForwardStep) { arrPoints[index].X = curX; arrPoints[index].Y = lastY; index++; arrPoints[index].X = curX; arrPoints[index].Y = curY; } else if (this.StepType == StepType.RearwardStep) { arrPoints[index].X = lastX; arrPoints[index].Y = curY; index++; arrPoints[index].X = curX; arrPoints[index].Y = curY; } lastX = curX; lastY = curY; index++; } } // Make sure there is at least one valid point if (index == 0) { return false; } // Add an extra point at the end, since the smoothing algorithm requires it arrPoints[index] = arrPoints[index - 1]; index++; count = index; return true; } return false; }
/// <summary> /// Build an array of <see cref="PointF"/> values (pixel coordinates) that represents the current curve. Note that this drawing routine ignores /// <see cref="PointPairBase.Missing"/> /// values, but it does not "break" the line to indicate values are missing. /// </summary> /// <param name="pane"> /// A reference to the <see cref="GraphPane"/> object that is the parent or owner of this object. /// </param> /// <param name="curve"> /// A <see cref="LineItem"/> representing this curve. /// </param> /// <param name="arrPoints"> /// An array of <see cref="PointF"/> values in pixel coordinates representing the current curve. /// </param> /// <param name="count"> /// The number of points contained in the "arrPoints" parameter. /// </param> /// <returns> /// true for a successful points array build, false for data problems /// </returns> public bool BuildPointsArray(GraphPane pane, CurveItem curve, out PointF[] arrPoints, out int count) { arrPoints = null; count = 0; IPointList points = curve.Points; if (this.IsVisible && !this.Color.IsEmpty && points != null) { int index = 0; float curX, curY, lastX = 0, lastY = 0; double x, y, lowVal; ValueHandler valueHandler = new ValueHandler(pane, false); // Step type plots get twice as many points. Always add three points so there is // room to close out the curve for area fills. arrPoints = new PointF[(this._stepType == StepType.NonStep ? 1 : 2) * points.Count + 1]; // Loop over all points in the curve for (int i = 0; i < points.Count; i++) { // make sure that the current point is valid if (!points[i].IsInvalid) { // Get the user scale values for the current point // use the valueHandler only for stacked types if (pane.LineType == LineType.Stack) { valueHandler.GetValues(curve, i, out x, out lowVal, out y); } // otherwise, just access the values directly. Avoiding the valueHandler for // non-stacked types is an optimization to minimize overhead in case there are // a large number of points. else { x = points[i].X; y = points[i].Y; } if (x == PointPairBase.Missing || y == PointPairBase.Missing) { continue; } // Transform the user scale values to pixel locations Axis xAxis = curve.GetXAxis(pane); curX = xAxis.Scale.Transform(curve.IsOverrideOrdinal, i, x); Axis yAxis = curve.GetYAxis(pane); curY = yAxis.Scale.Transform(curve.IsOverrideOrdinal, i, y); if (curX < -1000000 || curY < -1000000 || curX > 1000000 || curY > 1000000) { continue; } // Add the pixel value pair into the points array // Two points are added for step type curves // ignore step-type setting for smooth curves if (this._isSmooth || index == 0 || this.StepType == StepType.NonStep) { arrPoints[index].X = curX; arrPoints[index].Y = curY; } else if (this.StepType == StepType.ForwardStep || this.StepType == StepType.ForwardSegment) { arrPoints[index].X = curX; arrPoints[index].Y = lastY; index++; arrPoints[index].X = curX; arrPoints[index].Y = curY; } else if (this.StepType == StepType.RearwardStep || this.StepType == StepType.RearwardSegment) { arrPoints[index].X = lastX; arrPoints[index].Y = curY; index++; arrPoints[index].X = curX; arrPoints[index].Y = curY; } lastX = curX; lastY = curY; index++; } } // Make sure there is at least one valid point if (index == 0) { return false; } // Add an extra point at the end, since the smoothing algorithm requires it arrPoints[index] = arrPoints[index - 1]; index++; count = index; return true; } return false; }
/// <summary> /// Protected internal routine that draws the specified single bar (an individual "point") of this series to the specified <see cref="Graphics"/> /// device. /// </summary> /// <param name="g"> /// A graphic device object to be drawn into. This is normally e.Graphics from the PaintEventArgs argument to the Paint() method. /// </param> /// <param name="pane"> /// A reference to the <see cref="GraphPane"/> object that is the parent or owner of this object. /// </param> /// <param name="curve"> /// A <see cref="CurveItem"/> object representing the /// <see cref="Bar"/>'s to be drawn. /// </param> /// <param name="index"> /// The zero-based index number for the single bar to be drawn. /// </param> /// <param name="pos"> /// The ordinal position of the this bar series (0=first bar, 1=second bar, etc.) in the cluster of bars. /// </param> /// <param name="baseAxis"> /// The <see cref="Axis"/> class instance that defines the base (independent) axis for the <see cref="Bar"/> /// </param> /// <param name="valueAxis"> /// The <see cref="Axis"/> class instance that defines the value (dependent) axis for the <see cref="Bar"/> /// </param> /// <param name="barWidth"> /// The width of each bar, in pixels. /// </param> /// <param name="scaleFactor"> /// The scaling factor to be used for rendering objects. This is calculated and passed down by the parent <see cref="GraphPane"/> object using the /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust font sizes, etc. according to the actual size of the graph. /// </param> protected virtual void DrawSingleBar( Graphics g, GraphPane pane, CurveItem curve, int index, int pos, Axis baseAxis, Axis valueAxis, float barWidth, float scaleFactor) { // pixBase = pixel value for the bar center on the base axis // pixHiVal = pixel value for the bar top on the value axis // pixLowVal = pixel value for the bar bottom on the value axis float pixBase, pixHiVal, pixLowVal; float clusterWidth = pane.BarSettings.GetClusterWidth(); // float barWidth = curve.GetBarWidth( pane ); float clusterGap = pane._barSettings.MinClusterGap * barWidth; float barGap = barWidth * pane._barSettings.MinBarGap; // curBase = the scale value on the base axis of the current bar // curHiVal = the scale value on the value axis of the current bar // curLowVal = the scale value of the bottom of the bar double curBase, curLowVal, curHiVal; ValueHandler valueHandler = new ValueHandler(pane, false); valueHandler.GetValues(curve, index, out curBase, out curLowVal, out curHiVal); // Any value set to double max is invalid and should be skipped // This is used for calculated values that are out of range, divide // by zero, etc. // Also, any value <= zero on a log scale is invalid if (!curve.Points[index].IsInvalid) { // calculate a pixel value for the top of the bar on value axis pixLowVal = valueAxis.Scale.Transform(curve.IsOverrideOrdinal, index, curLowVal); pixHiVal = valueAxis.Scale.Transform(curve.IsOverrideOrdinal, index, curHiVal); // calculate a pixel value for the center of the bar on the base axis pixBase = baseAxis.Scale.Transform(curve.IsOverrideOrdinal, index, curBase); // Calculate the pixel location for the side of the bar (on the base axis) float pixSide = pixBase - clusterWidth / 2.0F + clusterGap / 2.0F + pos * (barWidth + barGap); // Draw the bar if (pane._barSettings.Base == BarBase.X) { this.Draw(g, pane, pixSide, pixSide + barWidth, pixLowVal, pixHiVal, scaleFactor, true, curve.IsSelected, curve.Points[index]); } else { this.Draw(g, pane, pixLowVal, pixHiVal, pixSide, pixSide + barWidth, scaleFactor, true, curve.IsSelected, curve.Points[index]); } } }
/// <summary> /// Find the data point that lies closest to the specified mouse (screen) point. /// </summary> /// <remarks> /// This method will search through the specified list of curves to find which point is nearest. It will only consider points that are within /// <see cref="Default.NearestTol"/> pixels of the screen point, and it will only consider <see cref="CurveItem"/>'s that are in /// <paramref name="targetCurveList"/>. /// </remarks> /// <param name="mousePt"> /// The screen point, in pixel coordinates. /// </param> /// <param name="targetCurveList"> /// A <see cref="CurveList"/> object containing a subset of <see cref="CurveItem"/>'s to be searched. /// </param> /// <param name="nearestCurve"> /// A reference to the <see cref="CurveItem"/> /// instance that contains the closest point. nearestCurve will be null if no data points are available. /// </param> /// <param name="iNearest"> /// The index number of the closest point. The actual data vpoint will then be <see cref="CurveItem.Points">CurveItem.Points[iNearest]</see> /// . iNearest will be -1 if no data points are available. /// </param> /// <returns> /// true if a point was found and that point lies within /// <see cref="Default.NearestTol"/> pixels of the screen point, false otherwise. /// </returns> public bool FindNearestPoint(PointF mousePt, CurveList targetCurveList, out CurveItem nearestCurve, out int iNearest) { CurveItem nearestBar = null; int iNearestBar = -1; nearestCurve = null; iNearest = -1; // If the point is outside the ChartRect, always return false if (!this._chart._rect.Contains(mousePt)) { return false; } double x, x2; double[] y; double[] y2; // ReverseTransform( mousePt, out x, out y, out y2 ); this.ReverseTransform(mousePt, out x, out x2, out y, out y2); if (!this.AxisRangesValid()) { return false; } ValueHandler valueHandler = new ValueHandler(this, false); // double yPixPerUnit = chartRect.Height / ( yAxis.Max - yAxis.Min ); // double y2PixPerUnit; // = chartRect.Height / ( y2Axis.Max - y2Axis.Min ); double yPixPerUnitAct, yAct, yMinAct, yMaxAct, xAct; double minDist = 1e20; double xVal, yVal, dist = 99999, distX, distY; double tolSquared = Default.NearestTol * Default.NearestTol; int iBar = 0; foreach (CurveItem curve in targetCurveList) { // test for pie first...if it's a pie rest of method superfluous if (curve is PieItem && curve.IsVisible) { if (((PieItem)curve).SlicePath != null && ((PieItem)curve).SlicePath.IsVisible(mousePt)) { nearestBar = curve; iNearestBar = 0; } } else if (curve.IsVisible) { int yIndex = curve.GetYAxisIndex(this); Axis yAxis = curve.GetYAxis(this); Axis xAxis = curve.GetXAxis(this); if (curve.IsY2Axis) { yAct = y2[yIndex]; yMinAct = this._y2AxisList[yIndex]._scale._min; yMaxAct = this._y2AxisList[yIndex]._scale._max; } else { yAct = y[yIndex]; yMinAct = this._yAxisList[yIndex]._scale._min; yMaxAct = this._yAxisList[yIndex]._scale._max; } yPixPerUnitAct = this._chart._rect.Height / (yMaxAct - yMinAct); double xPixPerUnit = this._chart._rect.Width / (xAxis._scale._max - xAxis._scale._min); xAct = xAxis is XAxis ? x : x2; IPointList points = curve.Points; float barWidth = curve.GetBarWidth(this); double barWidthUserHalf; Axis baseAxis = curve.BaseAxis(this); bool isXBaseAxis = baseAxis is XAxis || baseAxis is X2Axis; if (isXBaseAxis) { barWidthUserHalf = barWidth / xPixPerUnit / 2.0; } else { barWidthUserHalf = barWidth / yPixPerUnitAct / 2.0; } if (points != null) { for (int iPt = 0; iPt < curve.NPts; iPt++) { // xVal is the user scale X value of the current point if (xAxis._scale.IsAnyOrdinal && !curve.IsOverrideOrdinal) { xVal = iPt + 1.0; } else { xVal = points[iPt].X; } // yVal is the user scale Y value of the current point if (yAxis._scale.IsAnyOrdinal && !curve.IsOverrideOrdinal) { yVal = iPt + 1.0; } else { yVal = points[iPt].Y; } if (xVal != PointPairBase.Missing && yVal != PointPairBase.Missing) { if (curve.IsBar || curve is ErrorBarItem || curve is HiLowBarItem || curve is OHLCBarItem || curve is JapaneseCandleStickItem) { double baseVal, lowVal, hiVal; valueHandler.GetValues(curve, iPt, out baseVal, out lowVal, out hiVal); if (lowVal > hiVal) { double tmpVal = lowVal; lowVal = hiVal; hiVal = tmpVal; } if (isXBaseAxis) { double centerVal = valueHandler.BarCenterValue(curve, barWidth, iPt, xVal, iBar); if (xAct < centerVal - barWidthUserHalf || xAct > centerVal + barWidthUserHalf || yAct < lowVal || yAct > hiVal) { continue; } } else { double centerVal = valueHandler.BarCenterValue(curve, barWidth, iPt, yVal, iBar); if (yAct < centerVal - barWidthUserHalf || yAct > centerVal + barWidthUserHalf || xAct < lowVal || xAct > hiVal) { continue; } } if (nearestBar == null) { iNearestBar = iPt; nearestBar = curve; } } else if (xVal >= xAxis._scale._min && xVal <= xAxis._scale._max && yVal >= yMinAct && yVal <= yMaxAct) { if (curve is LineItem && this._lineType == LineType.Stack) { double zVal; valueHandler.GetValues(curve, iPt, out xVal, out zVal, out yVal); } distX = (xVal - xAct) * xPixPerUnit; distY = (yVal - yAct) * yPixPerUnitAct; dist = distX * distX + distY * distY; if (dist >= minDist) { continue; } minDist = dist; iNearest = iPt; nearestCurve = curve; } } } if (curve.IsBar) { iBar++; } } } } if (nearestCurve is LineItem) { float halfSymbol = ((LineItem)nearestCurve).Symbol.Size * this.CalcScaleFactor() / 2; minDist -= halfSymbol * halfSymbol; if (minDist < 0) { minDist = 0; } } if (minDist >= tolSquared && nearestBar != null) { // if no point met the tolerance, but a bar was found, use it nearestCurve = nearestBar; iNearest = iNearestBar; return true; } if (minDist < tolSquared) { // Did we find a close point, and is it within the tolerance? // (minDist is the square of the distance in pixel units) return true; } return false; }
/// <summary> /// The handle point values. /// </summary> /// <param name="mousePt"> /// The mouse pt. /// </param> /// <returns> /// The <see cref="Point"/>. /// </returns> private Point HandlePointValues(Point mousePt) { int iPt; GraphPane pane; object nearestObj; using (Graphics g = this.CreateGraphics()) { if (this._masterPane.FindNearestPaneObject(mousePt, g, out pane, out nearestObj, out iPt)) { if (nearestObj is CurveItem && iPt >= 0) { CurveItem curve = (CurveItem)nearestObj; if (this.PointValueEvent != null) { string label = this.PointValueEvent(this, pane, curve, iPt); if (label != null && label.Length > 0) { if (this.pointToolTip.GetToolTip(this) != label) { this.pointToolTip.Show(label, this); } } else { this.pointToolTip.Hide(this); } } else { if (curve is PieItem) { string label = ((PieItem)curve).Value.ToString(this._pointValueFormat); if (this.pointToolTip.GetToolTip(this) != label) { this.pointToolTip.Show(label, this); } } else { PointPair pt = curve.Points[iPt]; if (pt.Tag is string) { this.pointToolTip.SetToolTip(this, (string)pt.Tag); } else { double xVal, yVal, lowVal; ValueHandler valueHandler = new ValueHandler(pane, false); if ((curve is BarItem || curve is ErrorBarItem || curve is HiLowBarItem) && pane.BarSettings.Base != BarBase.X) { valueHandler.GetValues(curve, iPt, out yVal, out lowVal, out xVal); } else { valueHandler.GetValues(curve, iPt, out xVal, out lowVal, out yVal); } string xStr = this.MakeValueLabel(curve.GetXAxis(pane), xVal, iPt, curve.IsOverrideOrdinal); string yStr = this.MakeValueLabel(curve.GetYAxis(pane), yVal, iPt, curve.IsOverrideOrdinal); string label = "( " + xStr + ", " + yStr + " )"; if (this.pointToolTip.GetToolTip(this) != label) { this.pointToolTip.Show(label, this); } } } } } else { this.pointToolTip.Hide(this); } } else { this.pointToolTip.Hide(this); } } return mousePt; }
/// <summary> /// Draw this <see cref="CurveItem"/> to the specified <see cref="Graphics"/> /// device as a symbol at each defined point. The routine only draws the symbols; the lines are draw by the /// <see cref="Line.DrawCurve"/> method. This method is normally only called by the Draw method of the /// <see cref="CurveItem"/> object /// </summary> /// <param name="g"> /// A graphic device object to be drawn into. This is normally e.Graphics from the PaintEventArgs argument to the Paint() method. /// </param> /// <param name="pane"> /// A reference to the <see cref="GraphPane"/> object that is the parent or owner of this object. /// </param> /// <param name="curve"> /// A <see cref="LineItem"/> representing this curve. /// </param> /// <param name="scaleFactor"> /// The scaling factor to be used for rendering objects. This is calculated and passed down by the parent <see cref="GraphPane"/> object using the /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust font sizes, etc. according to the actual size of the graph. /// </param> /// <param name="isSelected"> /// Indicates that the <see cref="Symbol"/> should be drawn with attributes from the <see cref="Selection"/> class. /// </param> public void Draw(Graphics g, GraphPane pane, LineItem curve, float scaleFactor, bool isSelected) { Symbol source = this; if (isSelected) { source = Selection.Symbol; } int tmpX, tmpY; int minX = (int)pane.Chart.Rect.Left; int maxX = (int)pane.Chart.Rect.Right; int minY = (int)pane.Chart.Rect.Top; int maxY = (int)pane.Chart.Rect.Bottom; // (Dale-a-b) we'll set an element to true when it has been drawn bool[,] isPixelDrawn = new bool[maxX + 1, maxY + 1]; double curX, curY, lowVal; IPointList points = curve.Points; if (points != null && (this._border.IsVisible || this._fill.IsVisible)) { SmoothingMode sModeSave = g.SmoothingMode; if (this._isAntiAlias) { g.SmoothingMode = SmoothingMode.HighQuality; } // For the sake of speed, go ahead and create a solid brush and a pen // If it's a gradient fill, it will be created on the fly for each symbol // SolidBrush brush = new SolidBrush( this.fill.Color ); using (Pen pen = source._border.GetPen(pane, scaleFactor)) using (GraphicsPath path = this.MakePath(g, scaleFactor)) { RectangleF rect = path.GetBounds(); using (Brush brush = source.Fill.MakeBrush(rect)) { ValueHandler valueHandler = new ValueHandler(pane, false); Scale xScale = curve.GetXAxis(pane).Scale; Scale yScale = curve.GetYAxis(pane).Scale; bool xIsLog = xScale.IsLog; bool yIsLog = yScale.IsLog; bool xIsOrdinal = xScale.IsAnyOrdinal; double xMin = xScale.Min; double xMax = xScale.Max; // Loop over each defined point for (int i = 0; i < points.Count; i++) { // Get the user scale values for the current point // use the valueHandler only for stacked types if (pane.LineType == LineType.Stack) { valueHandler.GetValues(curve, i, out curX, out lowVal, out curY); } // otherwise, just access the values directly. Avoiding the valueHandler for // non-stacked types is an optimization to minimize overhead in case there are // a large number of points. else { curX = points[i].X; if (curve is StickItem) { curY = points[i].Z; } else { curY = points[i].Y; } } // Any value set to double max is invalid and should be skipped // This is used for calculated values that are out of range, divide // by zero, etc. // Also, any value <= zero on a log scale is invalid if (curX != PointPairBase.Missing && curY != PointPairBase.Missing && !double.IsNaN(curX) && !double.IsNaN(curY) && !double.IsInfinity(curX) && !double.IsInfinity(curY) && (curX > 0 || !xIsLog) && (!yIsLog || curY > 0.0) && (xIsOrdinal || (curX >= xMin && curX <= xMax))) { // Transform the user scale values to pixel locations tmpX = (int)xScale.Transform(curve.IsOverrideOrdinal, i, curX); tmpY = (int)yScale.Transform(curve.IsOverrideOrdinal, i, curY); // Maintain an array of "used" pixel locations to avoid duplicate drawing operations if (tmpX >= minX && tmpX <= maxX && tmpY >= minY && tmpY <= maxY) { // guard against the zoom-in case if (isPixelDrawn[tmpX, tmpY]) { continue; } isPixelDrawn[tmpX, tmpY] = true; } // If the fill type for this symbol is a Gradient by value type, // the make a brush corresponding to the appropriate current value if (this._fill.IsGradientValueType || this._border._gradientFill.IsGradientValueType) { using (Brush tBrush = this._fill.MakeBrush(rect, points[i])) using (Pen tPen = this._border.GetPen(pane, scaleFactor, points[i])) this.DrawSymbol(g, tmpX, tmpY, path, tPen, tBrush); } else { // Otherwise, the brush is already defined // Draw the symbol at the specified pixel location this.DrawSymbol(g, tmpX, tmpY, path, pen, brush); } } } } } g.SmoothingMode = sModeSave; } }
/// <summary> /// Draw all the <see cref="ErrorBar"/>'s to the specified <see cref="Graphics"/> /// device as a an error bar at each defined point. /// </summary> /// <param name="g"> /// A graphic device object to be drawn into. This is normally e.Graphics from the PaintEventArgs argument to the Paint() method. /// </param> /// <param name="pane"> /// A reference to the <see cref="GraphPane"/> object that is the parent or owner of this object. /// </param> /// <param name="curve"> /// A <see cref="CurveItem"/> object representing the /// <see cref="Bar"/>'s to be drawn. /// </param> /// <param name="baseAxis"> /// The <see cref="Axis"/> class instance that defines the base (independent) axis for the <see cref="Bar"/> /// </param> /// <param name="valueAxis"> /// The <see cref="Axis"/> class instance that defines the value (dependent) axis for the <see cref="Bar"/> /// </param> /// <param name="scaleFactor"> /// The scaling factor to be used for rendering objects. This is calculated and passed down by the parent <see cref="GraphPane"/> object using the /// <see cref="PaneBase.CalcScaleFactor"/> method, and is used to proportionally adjust font sizes, etc. according to the actual size of the graph. /// </param> public void Draw(Graphics g, GraphPane pane, ErrorBarItem curve, Axis baseAxis, Axis valueAxis, float scaleFactor) { ValueHandler valueHandler = new ValueHandler(pane, false); float pixBase, pixValue, pixLowValue; double scaleBase, scaleValue, scaleLowValue; if (curve.Points != null && this.IsVisible) { using (Pen pen = !curve.IsSelected ? new Pen(this._color, this._penWidth) : new Pen(Selection.Border.Color, Selection.Border.Width)) { // Loop over each defined point for (int i = 0; i < curve.Points.Count; i++) { valueHandler.GetValues(curve, i, out scaleBase, out scaleLowValue, out scaleValue); // Any value set to double max is invalid and should be skipped // This is used for calculated values that are out of range, divide // by zero, etc. // Also, any value <= zero on a log scale is invalid if (!curve.Points[i].IsInvalid3D && (scaleBase > 0 || !baseAxis._scale.IsLog) && ((scaleValue > 0 && scaleLowValue > 0) || !valueAxis._scale.IsLog)) { pixBase = baseAxis.Scale.Transform(curve.IsOverrideOrdinal, i, scaleBase); pixValue = valueAxis.Scale.Transform(curve.IsOverrideOrdinal, i, scaleValue); pixLowValue = valueAxis.Scale.Transform(curve.IsOverrideOrdinal, i, scaleLowValue); // if ( this.fill.IsGradientValueType ) // brush = fill.MakeBrush( _rect, _points[i] ); this.Draw( g, pane, baseAxis is XAxis || baseAxis is X2Axis, pixBase, pixValue, pixLowValue, scaleFactor, pen, curve.IsSelected, curve.Points[i]); } } } } }
/// <summary> /// Determine the coords for the rectangle associated with a specified point for this <see cref="CurveItem"/> /// </summary> /// <param name="pane"> /// The <see cref="GraphPane"/> to which this curve belongs /// </param> /// <param name="i"> /// The index of the point of interest /// </param> /// <param name="coords"> /// A list of coordinates that represents the "rect" for this point (used in an html AREA tag) /// </param> /// <returns> /// true if it's a valid point, false otherwise /// </returns> public override bool GetCoords(GraphPane pane, int i, out string coords) { coords = string.Empty; if (i < 0 || i >= this._points.Count) { return false; } Axis valueAxis = this.ValueAxis(pane); Axis baseAxis = this.BaseAxis(pane); float scaledSize = this._bar.Symbol.Size * pane.CalcScaleFactor(); // pixBase = pixel value for the bar center on the base axis // pixHiVal = pixel value for the bar top on the value axis // pixLowVal = pixel value for the bar bottom on the value axis float pixBase, pixHiVal, pixLowVal; float clusterWidth = pane.BarSettings.GetClusterWidth(); float barWidth = this.GetBarWidth(pane); float clusterGap = pane._barSettings.MinClusterGap * barWidth; float barGap = barWidth * pane._barSettings.MinBarGap; // curBase = the scale value on the base axis of the current bar // curHiVal = the scale value on the value axis of the current bar // curLowVal = the scale value of the bottom of the bar double curBase, curLowVal, curHiVal; ValueHandler valueHandler = new ValueHandler(pane, false); valueHandler.GetValues(this, i, out curBase, out curLowVal, out curHiVal); // Any value set to double max is invalid and should be skipped // This is used for calculated values that are out of range, divide // by zero, etc. // Also, any value <= zero on a log scale is invalid if (!this._points[i].IsInvalid3D) { // calculate a pixel value for the top of the bar on value axis pixLowVal = valueAxis.Scale.Transform(this._isOverrideOrdinal, i, curLowVal); pixHiVal = valueAxis.Scale.Transform(this._isOverrideOrdinal, i, curHiVal); // calculate a pixel value for the center of the bar on the base axis pixBase = baseAxis.Scale.Transform(this._isOverrideOrdinal, i, curBase); // Calculate the pixel location for the side of the bar (on the base axis) float pixSide = pixBase - scaledSize / 2.0F; // Draw the bar if (baseAxis is XAxis || baseAxis is X2Axis) { coords = string.Format("{0:f0},{1:f0},{2:f0},{3:f0}", pixSide, pixLowVal, pixSide + scaledSize, pixHiVal); } else { coords = string.Format("{0:f0},{1:f0},{2:f0},{3:f0}", pixLowVal, pixSide, pixHiVal, pixSide + scaledSize); } return true; } return false; }
/// <summary> /// Create a <see cref="TextObj"/> for each bar in the <see cref="GraphPane"/>. /// </summary> /// <remarks> /// This method will go through the bars, create a label that corresponds to the bar value, and place it on the graph depending on user preferences. /// This works for horizontal or vertical bars in clusters or stacks, but only for <see cref="BarItem"/> types. This method does not apply to /// <see cref="ErrorBarItem"/> or <see cref="HiLowBarItem"/> objects. Call this method only after calling <see cref="GraphPane.AxisChange()"/>. /// </remarks> /// <param name="pane"> /// The GraphPane in which to place the text labels. /// </param> /// <param name="isBarCenter"> /// true to center the labels inside the bars, false to place the labels just above the top of the bar. /// </param> /// <param name="valueFormat"> /// The double.ToString string format to use for creating the labels. /// </param> /// <param name="fontFamily"> /// The string name of the font family to use for the labels /// </param> /// <param name="fontSize"> /// The floating point size of the font, in scaled points /// </param> /// <param name="fontColor"> /// The color in which to draw the labels /// </param> /// <param name="isBold"> /// true for a bold font type, false otherwise /// </param> /// <param name="isItalic"> /// true for an italic font type, false otherwise /// </param> /// <param name="isUnderline"> /// true for an underline font type, false otherwise /// </param> public static void CreateBarLabels( GraphPane pane, bool isBarCenter, string valueFormat, string fontFamily, float fontSize, Color fontColor, bool isBold, bool isItalic, bool isUnderline) { bool isVertical = pane.BarSettings.Base == BarBase.X; // keep a count of the number of BarItems int curveIndex = 0; // Get a valuehandler to do some calculations for us ValueHandler valueHandler = new ValueHandler(pane, true); // Loop through each curve in the list foreach (CurveItem curve in pane.CurveList) { // work with BarItems only BarItem bar = curve as BarItem; if (bar != null) { IPointList points = curve.Points; // ADD JKB 9/21/07 // The labelOffset should depend on whether the curve is YAxis or Y2Axis. // JHC - Generalize to any value axis // Make the gap between the bars and the labels = 1.5% of the axis range float labelOffset; Scale scale = curve.ValueAxis(pane).Scale; labelOffset = (float)(scale._max - scale._min) * 0.015f; // Loop through each point in the BarItem for (int i = 0; i < points.Count; i++) { // Get the high, low and base values for the current bar // note that this method will automatically calculate the "effective" // values if the bar is stacked double baseVal, lowVal, hiVal; valueHandler.GetValues(curve, i, out baseVal, out lowVal, out hiVal); // Get the value that corresponds to the center of the bar base // This method figures out how the bars are positioned within a cluster float centerVal = (float)valueHandler.BarCenterValue(bar, bar.GetBarWidth(pane), i, baseVal, curveIndex); // Create a text label -- note that we have to go back to the original point // data for this, since hiVal and lowVal could be "effective" values from a bar stack string barLabelText = (isVertical ? points[i].Y : points[i].X).ToString(valueFormat); // Calculate the position of the label -- this is either the X or the Y coordinate // depending on whether they are horizontal or vertical bars, respectively float position; if (isBarCenter) { position = (float)(hiVal + lowVal) / 2.0f; } else if (hiVal >= 0) { position = (float)hiVal + labelOffset; } else { position = (float)hiVal - labelOffset; } // Create the new TextObj TextObj label; if (isVertical) { label = new TextObj(barLabelText, centerVal, position); } else { label = new TextObj(barLabelText, position, centerVal); } label.FontSpec.Family = fontFamily; // Configure the TextObj // CHANGE JKB 9/21/07 // CoordinateFrame should depend on whether curve is YAxis or Y2Axis. label.Location.CoordinateFrame = (isVertical && curve.IsY2Axis) ? CoordType.AxisXY2Scale : CoordType.AxisXYScale; label.FontSpec.Size = fontSize; label.FontSpec.FontColor = fontColor; label.FontSpec.IsItalic = isItalic; label.FontSpec.IsBold = isBold; label.FontSpec.IsUnderline = isUnderline; label.FontSpec.Angle = isVertical ? 90 : 0; label.Location.AlignH = isBarCenter ? AlignH.Center : (hiVal >= 0 ? AlignH.Left : AlignH.Right); label.Location.AlignV = AlignV.Center; label.FontSpec.Border.IsVisible = false; label.FontSpec.Fill.IsVisible = false; // Add the TextObj to the GraphPane pane.GraphObjList.Add(label); } curveIndex++; } } }