/// <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> /// 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++; } } }