/// <summary> /// Draw Axis Label /// </summary> /// <param name="g">The GDI+ drawing surface on which to draw</param> /// <param name="offset">offset calculated by derived class that makes sure axis label /// misses tick labels.</param> /// <param name="axisPhysMin">The minimum physical extent of the axis</param> /// <param name="axisPhysMax">The maximum physical extent of the axis</param> /// <returns>boxed RectangleF indicating bounding box of label. null if no label printed.</returns> public object DrawLabel(Graphics g, PointF offset, PointF axisPhysMin, PointF axisPhysMax) { if (label_ != "") { // determine angle of axis. double theta = (double)System.Math.Atan2( axisPhysMax.Y - axisPhysMin.Y, axisPhysMax.X - axisPhysMin.X); double perpTheta = theta - Math.PI / 2.0f; // want to move text this way to center on axis. double y = Math.Sin(perpTheta); double x = Math.Cos(perpTheta); PointF average = new PointF( (axisPhysMax.X + axisPhysMin.X) / 2.0f, (axisPhysMax.Y + axisPhysMin.Y) / 2.0f); g.TranslateTransform(offset.X, offset.Y); // this is done last. g.TranslateTransform(average.X, average.Y); theta = theta * 180.0f / Math.PI; // convert to degrees. g.RotateTransform((float)theta); // this is done first. double lHt = g.MeasureString(label_, FontScaler.scaleFont(labelFont_, FontScale)).Height; double lWd = g.MeasureString(label_, FontScaler.scaleFont(labelFont_, FontScale)).Width; // bb centered around zero. RectangleF drawRect = new RectangleF((float)(-lWd / 2.0f), (float)(-lHt / 2.0f), (float)lWd, (float)lHt); StringFormat drawFormat = new StringFormat(); drawFormat.Alignment = StringAlignment.Center; g.DrawString(label_, FontScaler.scaleFont(this.LabelFont, this.FontScale), this.labelBrush_, drawRect, drawFormat); // now work out physical bounds of label. and return. Matrix m = g.Transform; Point[] recPoints = new Point[2]; recPoints[0] = new Point((int)(-lWd / 2.0f), (int)(-lHt / 2.0f)); recPoints[1] = new Point((int)(lWd / 2.0f), (int)(lHt / 2.0f)); m.TransformPoints(recPoints); double x1 = Math.Min(recPoints[0].X, recPoints[1].X); double x2 = Math.Max(recPoints[0].X, recPoints[1].X); double y1 = Math.Min(recPoints[0].Y, recPoints[1].Y); double y2 = Math.Max(recPoints[0].Y, recPoints[1].Y); RectangleF bounds = new RectangleF((float)x1, (float)y1, (float)(x2 - x1), (float)(y2 - y1)); g.ResetTransform(); // reset transform before we return. return(bounds); } return(null); }
public RectangleF Draw(Graphics g, int xPos, int yPos, ArrayList plots, float scale) { // determine max width and max height of label strings. float maxHt = 0.0f; float maxWd = 0.0f; for (int i = 0; i < plots.Count; ++i) { IPlot p = (IPlot)plots[i]; float lHt = g.MeasureString(p.Label, FontScaler.scaleFont(font_, scale)).Height; float lWd = g.MeasureString(p.Label, FontScaler.scaleFont(font_, scale)).Width; if (lHt > maxHt) { maxHt = lHt; } if (lWd > maxWd) { maxWd = lWd; } } float lineLength = 20.0f; float lineHeight = maxHt; float hSpacing = 5.0f * scale; float vSpacing = 3.0f * scale; float boxWidth = hSpacing * 3.0f + lineLength + maxWd; float boxHeight = vSpacing * (float)(plots.Count + 1) + maxHt * (float)plots.Count; float totalWidth = boxWidth; float totalHeight = boxHeight; // draw box.. if (BorderStyle == BorderType.Line) { g.FillRectangle(new SolidBrush(Color.White), xPos, yPos, boxWidth, boxHeight); g.DrawRectangle(new Pen(Color.Black), xPos, yPos, boxWidth, boxHeight); } else if (BorderStyle == BorderType.Shadow) { float offset = 4.0f * (float)scale; g.FillRectangle(new SolidBrush(Color.LightGray), xPos + offset, yPos + offset, boxWidth, boxHeight); g.FillRectangle(new SolidBrush(Color.White), xPos, yPos, boxWidth, boxHeight); g.DrawRectangle(new Pen(Color.Black), xPos, yPos, boxWidth, boxHeight); totalWidth += offset; totalHeight += offset; } /* * else if ( this.BorderStyle == BorderType.Curved ) * { * // TODO. make this nice. * } */ else { // do nothing. } // now draw entries in box.. int unnamedCount = 0; for (int i = 0; i < plots.Count; ++i) { IPlot p = (IPlot)plots[i]; float lineXPos = xPos + hSpacing; float lineYPos = yPos + vSpacing + (float)i * (vSpacing + maxHt); p.DrawLegendLine(g, new RectangleF(lineXPos, lineYPos, lineLength, lineHeight)); float textXPos = lineXPos + hSpacing + lineLength; float textYPos = lineYPos; string label = p.Label; if (label == "") { unnamedCount += 1; label = "Series " + unnamedCount.ToString(); } g.DrawString(label, FontScaler.scaleFont(Font, scale), new SolidBrush(Color.Black), textXPos, textYPos); } return(new RectangleF(xPos, yPos, totalWidth, totalHeight)); }
/// <summary> /// Draw the plot on the drawing surface /// </summary> /// <param name="g">The GDI+ drawing surface on which to render.</param> /// <param name="bounds">The bounding rectangle on the drawing surface to be considered the plot area.</param> public void Draw(Graphics g, Rectangle bounds) { if (plots_.Count == 0) { return; // TODO: better output in this case. } float scale = (float)DetermineScaleFactor(bounds.Width, bounds.Height); if (xAxis1_ == null) { xAxis1_ = (Axis)xAxis2_.Clone(); xAxis1_.HideTickText = true; xAxis1_.TicksAngle = -Math.PI / 2.0f; } if (xAxis2_ == null) { xAxis2_ = (Axis)xAxis1_.Clone(); xAxis2_.HideTickText = true; xAxis2_.TicksAngle = Math.PI / 2.0f; } if (yAxis1_ == null) { yAxis1_ = (Axis)yAxis2_.Clone(); yAxis1_.HideTickText = true; yAxis1_.TicksAngle = Math.PI / 2.0f; } if (yAxis2_ == null) { yAxis2_ = (Axis)yAxis1_.Clone(); yAxis2_.HideTickText = true; yAxis2_.TicksAngle = -Math.PI / 2.0f; } // TODO: fix this so these not automatically overwritten. xAxis1_.TickScale = scale; xAxis1_.FontScale = scale; yAxis1_.TickScale = scale; yAxis1_.FontScale = scale; xAxis2_.TickScale = scale; xAxis2_.FontScale = scale; yAxis2_.TickScale = scale; yAxis2_.FontScale = scale; // now have axes world info. set physical limits. // first guess axes positions, then find bounding box, then change // to align nicely with side of control. System.Drawing.Rectangle cb = bounds; RectangleF bb; // guess physical x axis (bottom). Put it at the bottom of the plot PhysicalAxis pXAxis1 = new PhysicalAxis(xAxis1_, new Point(cb.Left, cb.Bottom), new Point(cb.Right, cb.Bottom)); int bottomIndent = (int)(padding_); if (!pXAxis1.Axis.Hidden) { // evaluate its bounding box bb = pXAxis1.GetBoundingBox(); // finally determine its indentation from the bottom bottomIndent = (int)(bottomIndent + bb.Bottom - cb.Bottom); } // guess physical y axis (left). Put it at the left side. PhysicalAxis pYAxis1 = new PhysicalAxis(yAxis1_, new Point(cb.Left, cb.Bottom), new Point(cb.Left, cb.Top)); int leftIndent = (int)(padding_); if (!pYAxis1.Axis.Hidden) { // evaluate its bounding box bb = pYAxis1.GetBoundingBox(); // finally determine its indentation from the left leftIndent = (int)(leftIndent - bb.Left + cb.Left); } // guess secondary x axis (top). PhysicalAxis pXAxis2 = new PhysicalAxis(xAxis2_, new Point(cb.Left, cb.Top), new Point(cb.Right, cb.Top)); int topIndent = (int)(padding_); double titleHeight = FontScaler.scaleFont(titleFont_, scale).Height; if (!pXAxis2.Axis.Hidden) { // evaluate its bounding box bb = pXAxis2.GetBoundingBox(); topIndent = (int)(topIndent - bb.Top + cb.Top); // finally determine its indentation from the top // correct top indendation to take into account plot title if (title_ != "") { topIndent += (int)((double)titleHeight * 1.3f); } } // guess secondary y axis (right). Put it at the right side. PhysicalAxis pYAxis2 = new PhysicalAxis(yAxis2_, new Point(cb.Right, cb.Bottom), new Point(cb.Right, cb.Top)); int rightIndent = (int)(padding_); if (!pYAxis2.Axis.Hidden) { // evaluate its bounding box bb = pYAxis2.GetBoundingBox(); // finally determine its indentation from the right rightIndent = (int)(rightIndent + bb.Right - cb.Right); } // now determine if legend should change any of these (legend should be fully // visible at all times), and draw legend. Legend legend = null; float lXPos = 0.0f; float lYPos = 0.0f; if (showLegend_) { legend = new Legend(); legend.BorderStyle = LegendBorderStyle; RectangleF legendWidthHeight = legend.GetBoundingBox(0, 0, plots_, scale); // calculate legend position. lYPos = legendOffsetY_; if (legendOffsetXAxis_ == XAxisPosition.Bottom) { lYPos += cb.Bottom - bottomIndent; if (horizontalEdgeLegendPlacement_ == Legend.Placement.Inside) { lYPos -= legendWidthHeight.Height; } } else { lYPos += cb.Top + topIndent; if (horizontalEdgeLegendPlacement_ == Legend.Placement.Outside) { lYPos -= legendWidthHeight.Height; } } lXPos = legendOffsetX_; if (legendOffsetYAxis_ == YAxisPosition.Left) { if (verticalEdgeLegendPlacement_ == Legend.Placement.Outside) { lXPos -= legendWidthHeight.Width; } lXPos += cb.Left + leftIndent; } else { if (verticalEdgeLegendPlacement_ == Legend.Placement.Inside) { lXPos -= legendWidthHeight.Width; } lXPos += cb.Right - rightIndent; } // update axes positions if need to for legend position. if (lXPos < padding_) { int changeAmount = -(int)lXPos + padding_; // only allow axes to move away from bounds. if (changeAmount > 0) { leftIndent += changeAmount; } lXPos += changeAmount; } if (lXPos + legendWidthHeight.Width > bounds.Right - padding_) { int changeAmount = ((int)lXPos - bounds.Right + (int)legendWidthHeight.Width + padding_); // only allow axes to move away from bounds. if (changeAmount > 0) { rightIndent += changeAmount; } lXPos -= changeAmount; } if (lYPos < padding_) { int changeAmount = -(int)lYPos + padding_; // only allow axes to move away from bounds. if (changeAmount > 0) { topIndent += changeAmount; } lYPos += changeAmount; } if (lYPos + legendWidthHeight.Height > bounds.Bottom - padding_) { int changeAmount = ((int)lYPos - bounds.Bottom + (int)legendWidthHeight.Height + padding_); // only allow axes to move away from bounds. if (changeAmount > 0) { bottomIndent += changeAmount; } lYPos -= changeAmount; } } // now we have all the positions and we can proceed to "move" the axes to their // right places // primary axes (bottom, left) pXAxis1.PhysicalMin = new Point(cb.Left + leftIndent, cb.Bottom - bottomIndent); pXAxis1.PhysicalMax = new Point(cb.Right - rightIndent, cb.Bottom - bottomIndent); pYAxis1.PhysicalMin = new Point(cb.Left + leftIndent, cb.Bottom - bottomIndent); pYAxis1.PhysicalMax = new Point(cb.Left + leftIndent, cb.Top + topIndent); // secondary axes (top, right) pXAxis2.PhysicalMin = new Point(cb.Left + leftIndent, cb.Top + topIndent); pXAxis2.PhysicalMax = new Point(cb.Right - rightIndent, cb.Top + topIndent); pYAxis2.PhysicalMin = new Point(cb.Right - rightIndent, cb.Bottom - bottomIndent); pYAxis2.PhysicalMax = new Point(cb.Right - rightIndent, cb.Top + topIndent); // now we are ready to define the bounding box for the plot area (to use in clipping // operations. plotAreaBoundingBoxCache_ = new Rectangle(cb.Left + leftIndent, cb.Top + topIndent, cb.Width - leftIndent - rightIndent, cb.Height - topIndent - bottomIndent); #if _IWANNASEE_ bb = pXAxis1.GetBoundingBox(); g.DrawRectangle(new Pen(Color.Orange), bb.X, bb.Y, bb.Width, bb.Height); bb = pXAxis2.GetBoundingBox(); g.DrawRectangle(new Pen(Color.Orange), bb.X, bb.Y, bb.Width, bb.Height); bb = pYAxis1.GetBoundingBox(); g.DrawRectangle(new Pen(Color.Orange), bb.X, bb.Y, bb.Width, bb.Height); bb = pYAxis2.GetBoundingBox(); g.DrawRectangle(new Pen(Color.Orange), bb.X, bb.Y, bb.Width, bb.Height); g.DrawRectangle(new Pen(Color.Red, 5.0F), plotAreaBoundingBox_); #endif // Fill in the background. if (plotBackColor_ != null) { g.FillRectangle(new System.Drawing.SolidBrush((Color)plotBackColor_), pYAxis1.PhysicalMin.X, pXAxis2.PhysicalMax.Y, pXAxis1.PhysicalMax.X - pXAxis1.PhysicalMin.X, pYAxis1.PhysicalMin.Y - pYAxis1.PhysicalMax.Y); } // draw title StringFormat drawFormat = new StringFormat(); drawFormat.Alignment = StringAlignment.Center; g.DrawString(title_, FontScaler.scaleFont(titleFont_, scale), titleBrush_, new PointF((pXAxis2.PhysicalMax.X + pXAxis2.PhysicalMin.X) / 2.0f, cb.Top + padding_), drawFormat); // now draw grid. DrawGrid(g, pXAxis1, pYAxis1, pXAxis2, pYAxis2); // now draw axes. pXAxis1.Draw(g); pXAxis2.Draw(g); pYAxis1.Draw(g); pYAxis2.Draw(g); // draw plots. for (int i = 0; i < plots_.Count; ++i) { IPlot plot = (IPlot)plots_[i]; XAxisPosition xap = (XAxisPosition)xAxisPositions_[i]; YAxisPosition yap = (YAxisPosition)yAxisPositions_[i]; PhysicalAxis xAxis; PhysicalAxis yAxis; if (xap == XAxisPosition.Bottom) { xAxis = pXAxis1; } else { xAxis = pXAxis2; } if (yap == YAxisPosition.Left) { yAxis = pYAxis1; } else { yAxis = pYAxis2; } // set the clipping region.. (necessary for zoom) g.Clip = new Region((Rectangle)plotAreaBoundingBoxCache_); // plot.. plot.Draw(g, xAxis, yAxis); // reset it.. g.ResetClip(); // cache the physical axes we used on this draw; pXAxis1Cache_ = pXAxis1; pYAxis1Cache_ = pYAxis1; pXAxis2Cache_ = pXAxis2; pYAxis2Cache_ = pYAxis2; } if (legend != null) { legend.Draw(g, (int)lXPos, (int)lYPos, plots_, scale); } }
/// <summary> /// Draws a tick on the axis /// </summary> /// <param name="g">The drawing surface</param> /// <param name="w">The tick position in world coordinates</param> /// <param name="size">The size of the tick (in pixels)</param> /// <param name="text">The text associated with the tick</param> /// <param name="textOffset">The Offset to draw from the auto calculated position</param> /// <param name="axisPhysMin">The minimum physical extent of the axis</param> /// <param name="axisPhysMax">The maximum physical extent of the axis</param> /// <returns> An ArrayList containing the offset from the axis required for an axis label /// to miss this tick, followed by a bounding rectangle for the tick and tickLabel drawn </returns> public virtual ArrayList DrawTick(Graphics g, double w, double size, string text, Point textOffset, PointF axisPhysMin, PointF axisPhysMax) { // determine start point. PointF s = WorldToPhysical(w, axisPhysMin, axisPhysMax, true); // determine offset from start point. PointF dir = this.AxisNormVector(axisPhysMin, axisPhysMax); // rotate clockwise by angle radians. double x1 = Math.Cos(-this.TicksAngle) * dir.X + Math.Sin(-this.TicksAngle) * dir.Y; double y1 = -Math.Sin(-this.TicksAngle) * dir.X + Math.Cos(-this.TicksAngle) * dir.Y; // scaling tick. dir = new PointF((float)(this.TickScale * size * x1), (float)(this.TickScale * size * y1)); // draw it! g.DrawLine(this.linePen_, s.X, s.Y, s.X + dir.X, s.Y + dir.Y); // calculate bounds. double minx = Math.Min(s.X, s.X + dir.X); double miny = Math.Min(s.Y, s.Y + dir.Y); double maxx = Math.Max(s.X, s.X + dir.X); double maxy = Math.Max(s.Y, s.Y + dir.Y); RectangleF bounds = new RectangleF((float)minx, (float)miny, (float)(maxx - minx), (float)(maxy - miny)); PointF labelOffset = new PointF(0.0f, 0.0f); // now draw associated text. if (text != "" && !HideTickText) { double lHt = g.MeasureString(text, FontScaler.scaleFont(font_, this.FontScale)).Height; double lWd = g.MeasureString(text, FontScaler.scaleFont(font_, this.FontScale)).Width; double textCenterX; double textCenterY; // if text is at pointy end of tick. if (!this.TickTextNextToAxis) { // offset due to tick. textCenterX = s.X + dir.X * 1.2f; textCenterY = s.Y + dir.Y * 1.2f; // offset due to text box size. textCenterX += 0.5f * x1 * lWd; textCenterY += 0.5f * y1 * lHt; } // else it's next to the axis. else { // start location. textCenterX = s.X; textCenterY = s.Y; // offset due to text box size. textCenterX -= 0.5f * x1 * lWd; textCenterY -= 0.5f * y1 * lHt; // bring text away from the axis a little bit. textCenterX -= x1 * (2.0f + FontScale); textCenterY -= y1 * (2.0f + FontScale); } double bx1 = textCenterX - lWd / 2.0f; double by1 = textCenterY - lHt / 2.0f; double bx2 = lWd; double by2 = lHt; RectangleF drawRect = new RectangleF((float)bx1, (float)by1, (float)bx2, (float)by2); // g.DrawRectangle( new Pen(Color.Green),bx1, by1, bx2, by2 ); bounds = RectangleF.Union(bounds, drawRect); // g.DrawRectangle( new Pen(Color.Purple), bounds.X, bounds.Y, bounds.Width, bounds.Height ); StringFormat drawFormat = new StringFormat(); drawFormat.Alignment = StringAlignment.Center; g.DrawString(text, FontScaler.scaleFont(this.TickTextFont, this.FontScale), this.tickTextBrush_, drawRect, drawFormat); textCenterX -= s.X; textCenterY -= s.Y; textCenterX *= 2.3f; textCenterY *= 2.3f; labelOffset = new PointF((float)textCenterX, (float)textCenterY); } // if (text != "" && !HideTickText ) ArrayList toReturn = new ArrayList(); toReturn.Add(labelOffset); toReturn.Add(bounds); return(toReturn); }