I need to draw an arc using GraphicsPath and having initial, median and final points. The arc has to pass on them.
I tried .DrawCurve and .DrawBezier but the result isn't exactly an arc.
What can I do?
SOLUTION:
After a couple of hours of code writing I managed to draw what I wanted with this algorithm (give 3 Point a,b,c and a GraphicsPath path):
double d = 2 * (a.X - c.X) * (c.Y - b.Y) + 2 * (b.X - c.X) * (a.Y - c.Y);
double m1 = (Math.Pow(a.X, 2) - Math.Pow(c.X, 2) + Math.Pow(a.Y, 2) - Math.Pow(c.Y, 2));
double m2 = (Math.Pow(c.X, 2) - Math.Pow(b.X, 2) + Math.Pow(c.Y, 2) - Math.Pow(b.Y, 2));
double nx = m1 * (c.Y - b.Y) + m2 * (c.Y - a.Y);
double ny = m1 * (b.X - c.X) + m2 * (a.X - c.X);
double cx = nx / d;
double cy = ny / d;
double dx = cx - a.X;
double dy = cy - a.Y;
double distance = Math.Sqrt(dx * dx + dy * dy);
Vector va = new Vector(a.X - cx, a.Y - cy);
Vector vb = new Vector(b.X - cx, b.Y - cy);
Vector vc = new Vector(c.X - cx, c.Y - cy);
Vector xaxis = new Vector(1, 0);
float startAngle = (float)Vector.AngleBetween(xaxis, va);
float sweepAngle = (float)(Vector.AngleBetween(va, vb) + Vector.AngleBetween(vb, vc));
path.AddArc(
(float)(cx - distance), (float)(cy - distance),
(float)(distance * 2), (float)(distance * 2),
startAngle, sweepAngle);
I would use DrawArc() as suggested by ANC_Michael. To find an arc that passes through 3 points you want to calculate the circumcircle of the triangle formed by the points.
Once you have the circumcircle calculate a bounding box for the circle to use with DrawArc using the min/max dimensions (center +/- radius). Now calculate your start and stop angles by translating the points so that the circumcircle is centered on the origin (translate by -circumcenter) and take the dot-product of the normalized start and end vectors with the X-axis:
double startAngle = Math.Acos(VectorToLeftPoint.Dot(XAxis));
double stopAngle = Math.Acos(VectorToRightPoint.Dot(XAxis));
Note that DrawArc expects angles clockwise from the X-axis so you should add Math.PI if the calculated vector is above the x-axis. That should be enough information to call DrawArc().
Edit: This method will find a circular arc and not necessarily the 'best fit' arc depending on your expected endpoint behavior.
Have you tried the DrawArc method and seeing if u can manipulate your 3 points somehow?
maybe
Pen blackPen= new Pen(Color.Black, 3);
// Create rectangle to bound ellipse.
Rectangle rect = new Rectangle(initial x, initial y, final x, median y);
// Create start and sweep angles on ellipse.
float startAngle = 0F;
float sweepAngle = 270.0F;
// Draw arc to screen.
e.Graphics.DrawArc(blackPen, rect, startAngle, sweepAngle);
http://msdn.microsoft.com/en-us/library/system.drawing.graphics.drawarc%28VS.71%29.aspx
Related
I have locations of three points along the circle. pt1(x1, y1,z1), pt2(x2, y2, z2), pt3(x3, y3,z3). and want to find the radius of the circle. Already I have a function to compute radius in 2d space, which I am copying it here
public static double ComputeRadius(Location a, Location b, Location c)
{
double x1 = a.x;
double y1 = a.y;
double x2 = b.x;
double y2 = b.y;
double x3 = c.x;
double y3 = c.y;
double mr = (double)((y2 - y1) / (x2 - x1));
double mt = (double)((y3 - y2) / (x3 - x2));
double xc = (double)((mr * mt * (y3 - y1) + mr * (x2 + x3) - mt * (x1 + x2)) / (2 * (mr - mt)));
double yc = (double)((-1 / mr) * (xc - (x1 + x2) / 2) + (y1 + y2) / 2);
double d = (xc - x1) * (xc - x1) + (yc - y1) * (yc - y1);
return Math.Sqrt(d);
}
If you know the order of points pt1,pt2,pt3 along the circle then you can use graphical method:
cast normal axises from middle of each line segment in the plane of circle
your circle plane is defined by your 3 points. so the normal vector is
n = (pt2-pt1) x (pt3-pt2)
where the x is cross product so you have 2 lines (pt1,pt2) and (pt2,pt3) in black. The mid points are easy
p0=0.5*(pt1+pt2)
p1=0.5*(pt2+pt3)
the axis directions can be obtained also by cross product
dp0=(pt2-pt1) x n
dp1=(pt3-pt2) x n
so you got 2 axises:
pnt0(t)=p0+dp0*t
pnt1(u)=p1+dp1*u
Where t,u are scalar parameters t,u=(-inf,+inf) it is just position in axis from the starting mid point ...
the intersection is center of circle
So find the intersection of 2 axises and call it pt0
compute distance between center and any of your points
r=|pt1-pt0|
Sorry the image is for any curve (too lazy to repaint for circle as it is almost the same). If you do not know the order of points then the 2 points that are most distant to each other are the outer points ... In case they are equidistant the order does not matter any is OK
In my application user points two points PointA and PointB on the same row (could be at any angle). So I have the following information
PointA coordinates
pointB coordinates
Distance between PointA and Point B
An Across distance (taken from user as input to draw other points)
Angle (calculated from pointA and pointB).
Based on this information, another application draws four points (vertices of rectangle).
What I have to do is, I have to find the centre point of those four points (rectangle) to be able to properly draw the rectangle bounded between those 4 points.
Right now I am able to draw the rectangle taking the centre as the pointA which obviously is incorrect. What formula should I use to calculate the centre of Rectangle so that I can draw a bounded rectangle?
Image 1:
Image 2:
Image 3:
Image 4:
Image 5:
As seen in the attached images, in every case rectangle is getting drawn with pointA as centroid. While I want the centroid to the centre of the FOUR points.
P.S: All angles are measured 0 degrees North.
I think:
Let P0 = {x0,y0} and P1 = {x1,y1}
Let the vector V01 = P1 - P0 == {V01x = P1x - P0x, V01y = P1y - P0y}
Let the vector V03 = {V01x * Cos(PI/2) - V01y * Sin(PI/2) , V01x* Sin(PI/2) + V01y * Cos(PI/2)
Width = Sqrt(V03x * V03x + V03y * V03y)
VN = V03 / Width == {V03x / Width, V03y / Width}
P3 = P0 + VN * Height
P4 = P1 + VN * Height
PC = (P0 + P1 + P2 + P3) / 4
In case you have two points p1, p2 and you need to draw a rectange (get other 2 points a1, a2) from these:
a1.x = p1.x;
a1.y = p2.y;
a2.x = p2.x;
a2.y = p1.y;
There you go, four points p1, a2, p2, a1 (clockwise order) describe your rectangle.
Update:
var width = p2.x - p1.x;
var height = p2.y - p1.y;
var angle = 0;
var center = new Point(p1.x + width / 2, p1.y + height / 2);
Update:
var center = new Point();
var angle = //you have it. Radians.
var height = // you have this as well.
var halfSegment = new Point((p2.x - p1.x) / 2, (p2.y - p1.y) / 2);
center.x = halfSegment.x + Math.Cos(angle - Math.PI / 2) * height / 2;
center.y = halfSegment.y + Math.Sin(angle - Math.PI / 2) * height / 2;
I have a ellipse with center point is at origin(0,0)
double dHalfwidthEllipse = 10;
double dHalfheightEllipse = 20;
double dAngle = 30;//Its in degree
PointF ptfPoint = new PointF();//To be found
PointF ptfOrigin = new PointF(0, 0);//Origin
Angle of point with respect to origin = 30 degree;
How to get the point now given the above values using C#?
See http://www.mathopenref.com/coordparamellipse.html
The parametric equation for an ellipse with center point at the origin, half width a and half height b is
x(t) = a cos t,
y(t) = b sin t
If you simply wish to draw an ellipse, given
double dHalfwidthEllipse = 10; // a
double dHalfheightEllipse = 20; // b
PointF ptfOrigin = new PointF(0, 0); // Origin
all you need is
PointF ptfPoint =
new PointF(ptfOrigin.X + dHalfwidthEllipse * Math.Cos(t * Math.Pi/180.0),
ptfOrigin.Y + dHalfheightEllipse * Math.Sin(t * Math.Pi/180.0) );
with t varying between -180 and 180 degrees.
However, as #Sebastian points out, if you wish to compute the exact intersection with a line through the center with angle theta, it gets a bit more complicated, since we need to find a t that corresponds to theta:
y(t)/x(t) = tan θ
b sin t / (a cos t) = tan θ
b/a tan t = tan θ
t= arctan(a tan θ / b) + n * π
So if we add
double dAngle = 30; // theta, between -90 and 90 degrees
We can compute t and ptfPoint:
double t = Math.Atan( dHalfwidthEllipse * Math.Tan( dAngle * Math.Pi/180.0 )
/ dHalfheightEllipse);
PointF ptfPoint =
new PointF(ptfOrigin.X + dHalfwidthEllipse * Math.Cos(t),
ptfOrigin.Y + dHalfheightEllipse * Math.Sin(t) );
This works fine for the area around the positive x axis.
For theta between 90 and 180 degrees, add π:
double t = Math.Atan( dHalfwidthEllipse * Math.Tan( dAngle * Math.Pi/180.0 )
/ dHalfheightEllipse) + Math.Pi;
For theta between -180 and -90 degrees, subtract π:
double t = Math.Atan( dHalfwidthEllipse * Math.Tan( dAngle * Math.Pi/180.0 )
/ dHalfheightEllipse) - Math.Pi;
As you get close to the y axis, x(t) approaches zero and the above calculation divides by zero, but there you can use the opposite:
x(t)/y(t) = tan (90 - θ)
a cos t / (b sin t) = tan (90 - θ)
a/b tan t = tan (90 - θ)
t = arctan ( b tan (90 - θ) / a ) + n * π
I'm drawing a custom diagram of business objects using .NET GDI+. Among other things, the diagram consists of several lines that are connecting the objects.
In a particular scenario, I need to shorten a line by a specific number of pixels, let's say 10 pixels, i.e. find the point on the line that lies 10 pixels before the end point of the line.
Imagine a circle with radius r = 10 pixels, and a line with start point (x1, y1) and end point (x2, y2). The circle is centered at the end point of the line, as in the following illustration.
How do I calculate the point marked with a red circle, i.e. the intersection between circle and line? This would give me the new end point of the line, shortening it by 10 pixels.
Solution
Thank you for your answers from which I was able to put together the following procedure. I named it LengthenLine, since I find it more natural to pass a negative number of pixels if I want the line shortened.
Specifically, I was trying to put together a function that could draw a line with rounded corners, which can be found here.
public void LengthenLine(PointF startPoint, ref PointF endPoint, float pixelCount)
{
if (startPoint.Equals(endPoint))
return; // not a line
double dx = endPoint.X - startPoint.X;
double dy = endPoint.Y - startPoint.Y;
if (dx == 0)
{
// vertical line:
if (endPoint.Y < startPoint.Y)
endPoint.Y -= pixelCount;
else
endPoint.Y += pixelCount;
}
else if (dy == 0)
{
// horizontal line:
if (endPoint.X < startPoint.X)
endPoint.X -= pixelCount;
else
endPoint.X += pixelCount;
}
else
{
// non-horizontal, non-vertical line:
double length = Math.Sqrt(dx * dx + dy * dy);
double scale = (length + pixelCount) / length;
dx *= scale;
dy *= scale;
endPoint.X = startPoint.X + Convert.ToSingle(dx);
endPoint.Y = startPoint.Y + Convert.ToSingle(dy);
}
}
Find the direction vector, i.e. let the position vectors be (using floats) B = (x2, y2) and A = (x1, y1), then AB = B - A. Normalize that vector by dividing by its length ( Math.Sqrt(xx + yy) ). Then multiply the direction vector AB by the original length minus the circle's radius, and add back to the lines starting position:
double dx = x2 - x1;
double dy = y2 - y1;
double length = Math.Sqrt(dx * dx + dy * dy);
if (length > 0)
{
dx /= length;
dy /= length;
}
dx *= length - radius;
dy *= length - radius;
int x3 = (int)(x1 + dx);
int y3 = (int)(y1 + dy);
Edit: Fixed the code, aaand fixed the initial explanation (thought you wanted the line to go out from the circle's center to its perimeter :P)
I'm not sure why you even had to introduce the circle. For a line stretching from (x2,y2) to (x1,y1), you can calculate any point on that line as:
(x2+p*(x1-x2),y2+p*(y1-y2))
where p is the percentage along the line you wish to go.
To calculate the percentage, you just need:
p = r/L
So in your case, (x3,y3) can be calculated as:
(x2+(10/L)*(x1-x2),y2+(10/L)*(y1-y2))
For example, if you have the two points (x2=1,y2=5) and (x1=-6,y1=22), they have a length of sqrt(72 + 172 or 18.38477631 and 10 divided by that is 0.543928293. Putting all those figures into the equation above:
(x2 + (10/l) * (x1-x2) , y2 + (10/l) * (y1-y2))
= (1 + 0.543928293 * (-6- 1) , 5 + 0.543928293 * (22- 5))
= (1 + 0.543928293 * -7 , 5 + 0.543928293 * 17 )
= (x3=-2.807498053,y3=14.24678098)
The distance between (x3,y3) and (x1,y1) is sqrt(3.1925019472 + 7.7532190152) or 8.384776311, a difference of 10 to within one part in a thousand million, and that's only because of rounding errors on my calculator.
You can use similar triangles. For the main triangle, d is the hypotenuses and the extension of r is the vertical line that meets the right angle. Inside the circle you will have a smaller triangle with a hypotenuses of length r.
r/d = (x2-a0)/(x2-x1) = (y2-b0)/(y2-y1)
a0 = x2 + (x2-x1)r/d
b0 = y2 + (y2-y1)r/d
I am trying to use System.Drawing.Drawing2D.GraphicsPath.AddArc to draw an arc of an ellipse starting at 0 degrees and sweeping to 135 degrees.
The issue I am running in to is that for an ellipse, the arc drawn does not match up with what I would expect.
For example, the following code generates the image below. The green circles are where I would expect the end points of the arc to be using the formula for a point along an ellipse. My formula works for circles but not for ellipses.
Does this have something to do with polar versus Cartesian coordinates?
private PointF GetPointOnEllipse(RectangleF bounds, float angleInDegrees)
{
float a = bounds.Width / 2.0F;
float b = bounds.Height / 2.0F;
float angleInRadians = (float)(Math.PI * angleInDegrees / 180.0F);
float x = (float)(( bounds.X + a ) + a * Math.Cos(angleInRadians));
float y = (float)(( bounds.Y + b ) + b * Math.Sin(angleInRadians));
return new PointF(x, y);
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
Rectangle circleBounds = new Rectangle(250, 100, 500, 500);
e.Graphics.DrawRectangle(Pens.Red, circleBounds);
System.Drawing.Drawing2D.GraphicsPath circularPath = new System.Drawing.Drawing2D.GraphicsPath();
circularPath.AddArc(circleBounds, 0.0F, 135.0F);
e.Graphics.DrawPath(Pens.Red, circularPath);
PointF circlePoint = GetPointOnEllipse(circleBounds, 135.0F);
e.Graphics.DrawEllipse(Pens.Green, new RectangleF(circlePoint.X - 5, circlePoint.Y - 5, 10, 10));
Rectangle ellipseBounds = new Rectangle(50, 100, 900, 500);
e.Graphics.DrawRectangle(Pens.Blue, ellipseBounds);
System.Drawing.Drawing2D.GraphicsPath ellipticalPath = new System.Drawing.Drawing2D.GraphicsPath();
ellipticalPath.AddArc(ellipseBounds, 0.0F, 135.0F);
e.Graphics.DrawPath(Pens.Blue, ellipticalPath);
PointF ellipsePoint = GetPointOnEllipse(ellipseBounds, 135.0F);
e.Graphics.DrawEllipse(Pens.Green, new RectangleF(ellipsePoint.X - 5, ellipsePoint.Y - 5, 10, 10));
}
I was getting confused about how GraphicsPath.AddArc worked & I couldn't find any decent diagrams, so I drew one. Just in case anyone else has been suffering similarly! http://imgur.com/lNBewKZ
GraphicsPath.AddArc does exactly what you ask it to do -- it the arc up to a line projecting from the ellipse center, at an exact angle of 135 degrees clockwise from the x axis.
Unfortunately, this doesn't help when you're using the angle as a direct proportion of a pie chart slice you want to draw. To find out the angle B you need to use with AddArc, given an angle A that works on a circle, in radians, use:
B = Math.Atan2(sin(A) * height / width, cos(A))
Where width and height are those of the ellipse.
In your sample code, try adding the following at the end of Form1_Paint:
ellipticalPath = new System.Drawing.Drawing2D.GraphicsPath();
ellipticalPath.AddArc(
ellipseBounds,
0.0F,
(float) (180.0 / Math.PI * Math.Atan2(
Math.Sin(135.0 * Math.PI / 180.0) * ellipseBounds.Height / ellipseBounds.Width,
Math.Cos(135.0 * Math.PI / 180.0))));
e.Graphics.DrawPath(Pens.Black, ellipticalPath);
The result should look as follows:
alt text http://img216.imageshack.us/img216/1905/arcs.png