Reputation: 25
I am trying to put a label(string) at the center of a user created polygon. I checked the available methods on the Graphics class but I only found the DrawString method which takes a string, Font, and Rectangle. Problem is i am using a polygon. And there is no DrawPolygon overload which takes a string as a parameter.
// Get the highest and lowest of both axis to
// determine the width and height of the polygon
int _lowestX = _slot.Min(o => o.X);
int _highestX = _slot.Max(o => o.X);
int _lowestY = _slot.Min(o => o.Y);
int _highestY = _slot.Max(o => o.Y);
// Draw the polygon
e.Graphics.FillPolygon(Brushes.White, _slot.ToArray()); // _slot is a list of points
e.Graphics.DrawPolygon(Pens.Blue, _slot.ToArray());
Font _font = new Font(FontFamily.GenericSansSerif, _highestX - _lowestX, FontStyle.Regular);
SizeF _textSize = e.Graphics.MeasureString("Slot 1", _font);
// My attempt at drawing the text using the DrawString method
// by trying to mock a rectangle using the height and width of the polygon
e.Graphics.DrawString("Slot 1", _font, new SolidBrush(Color.Black), (_highestX - _lowestX - _textSize.Width) / 2, 0);
Any suggestions on what to do?
Thanks in advance.
Upvotes: 0
Views: 1551
Reputation: 54433
Here is how you can do it, but note that with really tricky shapes there will not be a space to contain the text, or even if there is it may be rather hard to find..
You already know how to detemine a size in which the text be fit.
What we need next is a test to see if our bounding rectangle is contained in the polygon.
Here is one, which makes use of GraphicsPaths
and Regions
..:
static bool PathContainsRect(GraphicsPath gp, RectangleF rect, Graphics g)
{
Region rPath0 = new Region(gp);
Region rPath1 = new Region(gp);
Region rRect = new Region(rect);
rPath1.Union(rRect);
rPath1.Exclude(rPath0);
return rPath1.IsEmpty(g);
}
You need to feed in the Graphics
object you want to use for drawing.
Next you need some algorithm to find points to test. The best choices will depend on your polygons.
Here is a simple one: It starts at the center and the moves in steps in 4 directions: top-left, top, bottom-right, bottom. If you want to add the other 4 directions or leave out some it should be simple to adapt..:
static Point NearestCenterLocation(GraphicsPath gp, Size sz, Graphics g, int step)
{
RectangleF rB = gp.GetBounds();
Point center = new Point((int)(rB.Left + rB.Width / 2f - sz.Width / 2),
(int)(rB.Top + rB.Height /2f - sz.Height/ 2));
Point ncTL = center; Point ncBR = center;
Point ncT = center; Point ncB = center;
RectangleF nTLRect = new RectangleF(center, sz);
RectangleF nBRRect = new RectangleF(center, sz);
RectangleF nTRect = new RectangleF(center, sz);
RectangleF nBRect = new RectangleF(center, sz);
Point hit = Point.Empty;
do
{
ncTL.Offset(-step, -step);
ncBR.Offset(step, step);
ncT.Offset(-step, 0);
ncB.Offset(step, 0);
nTLRect = new RectangleF(ncTL, sz);
nBRRect = new RectangleF(ncBR, sz);
nTRect = new RectangleF(ncT, sz);
nBRect = new RectangleF(ncB, sz);
hit = (PathContainsRect(gp, nTLRect, g) && ncTL.X > 0) ? ncTL : hit;
hit = (PathContainsRect(gp, nBRRect, g) ) ? ncBR : hit;
hit = (PathContainsRect(gp, nTRect, g)) ? ncT : hit;
hit = (PathContainsRect(gp, nBRect, g) ) ? ncB : hit;
g.DrawRectangle(Pens.Green, Rectangle.Round(nTLRect)); // test only
g.DrawRectangle(Pens.Blue, Rectangle.Round(nBRRect)); // test only
g.DrawRectangle(Pens.Cyan, Rectangle.Round(nTRect)); // test only
g.DrawRectangle(Pens.Khaki, Rectangle.Round(nBRect)); // test only
} while (hit == Point.Empty);
g.DrawRectangle(Pens.Tomato, new Rectangle(center, sz)); // test only
return hit;
}
It contains drawing calls to show how it searches in the four direction until it finds the first hit. Center and result Rectangles
are in red.
This is the code that created the test bed:
Size sz = new Size(70, 35);
GraphicsPath gp1 = new GraphicsPath();
gp1.FillMode = FillMode.Winding;
gp1.AddRectangle(new Rectangle(0, 0, 350, 120));
gp1.AddRectangle(new Rectangle(0, 0, 120, 300));
gp1.AddRectangle(new Rectangle(250, 0, 100, 300));
Point px = NearestCenterLocation(gp1, sz, g , 10);
using (SolidBrush brush = new SolidBrush(Color.FromArgb(66, Color.Gold)))
g.FillPath(brush, gp1);
g.DrawRectangle(Pens.Tomato, new Rectangle(px, sz));
Of course you may want to search down only at first, then up, then left, then right and then or never diagonally etc..
Upvotes: 0
Reputation: 25
I tried to find the average of all the Xs and Ys of the polygon. Then draw the string on the average minus the width or height of the text divided by two. So far, It is now displaying the text at the middle.
Here is my updated code
int _avgX = _slot.Sum(o => o.X) / _slot.Count;
int _avgY = _slot.Sum(o => o.Y) / _slot.Count;
e.Graphics.FillPolygon(Brushes.White, _slot.ToArray());
e.Graphics.DrawPolygon(Pens.Blue, _slot.ToArray());
Font _font = new Font(FontFamily.GenericSansSerif, 8, FontStyle.Regular);
SizeF _textSize = e.Graphics.MeasureString("Slot 1", _font);
e.Graphics.DrawString("Slot 1", _font, new SolidBrush(Color.Black), _avgX - (_textSize.Width / 2), _avgY - (_textSize.Height / 2));
This only works on convex polygons. On concave polygons (L shaped for example), the text will be drawn outside the polygon like this.
__________
| |
| |
| |
| | Slot 1
| |_____________
| |
| |
|______________________|
Any ideas on how to move it inside?
Upvotes: 2