We know 2 circle's x and y center position, and the radius is the same. I want to visually connect the circles without looping the draw ellipse for each point on the line what connects the 2 circle's center.
From this:
To this:
Code:
int radius = 75;
int x1 = 100;
int y1 = 200;
int x2 = 300;
int y2 = 100;
g.FillEllipse(Brushes.Blue, new Rectangle(x1 - radius / 2, y1 - radius / 2, radius, radius));
g.FillEllipse(Brushes.Blue, new Rectangle(x2 - radius / 2, y2 - radius / 2, radius, radius));
A solution for when the Circles don't have the same Diameter.
The first information needed is the distance between the Centers of two Circles.
To calculate it, we use the Euclidean distance applied to a Cartesian plane:
Where (x1, y1) and (x2, y2) are the coordinates of the Centers of two Circles.
We also need to know the Direction (expressed as a positive or negative value): the calculated [Distance] will always be positive.
in C# it, it can be coded as:
float Direction = (Circle1Center.X > Circle2Center.X) ? -1 : 1;
float Distance = (float)Math.Sqrt(Math.Pow(Circle1Center.X - Circle2Center.X, 2) +
Math.Pow(Circle1Center.Y - Circle2Center.Y, 2));
Distance *= Direction;
Now, we have the Distance between the Centers of two Circles, which also expresses a direction.
We also need to know how this virtual line - connecting the two Centers - is rotated in relation to our drawing plane. In the figure below, the Distance can be viewed as the hypotenuse of a right triangle h = (A, B). The C angle is determined by the intersection of the straight lines, parallel to the axis, that cross the Centers of the Circles.
We need to calculate the angle Theta (θ).
Using the Pythagorean theorem, we can derive that the Sine of the angle Theta is Sinθ = b/h (as in the figure)
Using the Circles' Centers coordinates, this can be coded in C# as:
(Distance is the triangle's hypotenuse)
float SinTheta = (Math.Max(Circle1Center.Y, Circle2Center.Y) -
Math.Min(Circle1Center.Y, Circle2Center.Y)) / Distance;
SinTheta expresses an angle in Radians. We need the angle expressed in Degrees: the Graphics object uses this measure for its world transformation functions.
float RotationAngle = (float)(Math.Asin(SinTheta) * (180 / Math.PI));
Now, we need to build a Connector, a shape that links the 2 Circles. We need a Polygon; a Rectangle can't have different pairs of sides (we are considering Circles with different Diameters).
This Polygon will have the longer sides = to the Distance between the Circles Centers, the shorter sides = to the Circles Diameters.
To build a Polygon, we can use both Graphics.DrawPolygon and GraphicsPath.AddPolygon. I'm choosing the GraphicsPath method, because a GraphicsPath can hold more that one shape and these shapes can interact, in a way.
To connect the 2 considered Circles with a Polygon, we need to rotate the Polygon using the RotationAngle previously calculated.
A simple way to perform the rotation, is to move the world coordinates to the Center of one of the Circles, using the Graphics.TranslateTransform method, then rotate the new coordinates, using Graphics.RotateTransform.
We need to draw our Polygon positioning one of the short sides - corresponding to the Diameter of the Circle which is the center of the coordinates transformation - in the center of the Cirle. Hence, when the rotation will be applied, it's short side it will be in the middle of this transformation, anchored to the Center.
Here, figure 3 shows the positioning of the Polygon (yellow shape) (ok, it looks like a rectangle, never mind);in figure 4 the same Polygon after the rotation.
Notes:
As TaW pointed out, this drawing needs to be performed using a SolidBrush with a non-transparent Color, which is kind of disappointing.
Well, a semi-transparent Brush is not forbidden, but the overlapping shapes will have a different color, the sum of the transparent colors of the intersections.
It is however possible to draw the shapes using a semi-transparent Brush without a Color change, using the GraphicsPath ability to fill its shapes using a color that is applied to all the overlapping parts. We just need to change the default FillMode (see the example in the Docs), setting it to FillMode.Winding.
Sample code:
In this example, two couples of Circles are drawn on a Graphics context. They are then connected with a Polygon shape, created using GraphicsPath.AddPolygon().
(Of course, we need to use the Paint event of a drawable Control, a Form here)
The overloaded helper function accepts both the Circles' centers position, expressed as a PointF and a RectangleF structure, representing the Circles bounds.
This is the visual result, with full Colors and using a semi-transparent brush:
using System.Drawing;
using System.Drawing.Drawing2D;
private float Radius1 = 30f;
private float Radius2 = 50f;
private PointF Circle1Center = new PointF(220, 47);
private PointF Circle2Center = new PointF(72, 254);
private PointF Circle3Center = new PointF(52, 58);
private PointF Circle4Center = new PointF(217, 232);
private void form1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.CompositingQuality = CompositingQuality.GammaCorrected;
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;
DrawLinkedCircles(Circle1Center, Circle2Center, Radius1, Radius2, Color.FromArgb(200, Color.YellowGreen), e.Graphics);
DrawLinkedCircles(Circle3Center, Circle4Center, Radius1, Radius2, Color.FromArgb(200, Color.SteelBlue), e.Graphics);
//OR, passing a RectangleF structure
//RectangleF Circle1 = new RectangleF(Circle1Center.X - Radius1, Circle1Center.Y - Radius1, Radius1 * 2, Radius1 * 2);
//RectangleF Circle2 = new RectangleF(Circle2Center.X - Radius2, Circle2Center.Y - Radius2, Radius2 * 2, Radius2 * 2);
//DrawLinkedCircles(Circle1, Circle2, Color.FromArgb(200, Color.YellowGreen), e.Graphics);
}
Helper function:
public void DrawLinkedCircles(RectangleF Circle1, RectangleF Circle2, Color FillColor, Graphics g)
{
PointF Circle1Center = new PointF(Circle1.X + (Circle1.Width / 2), Circle1.Y + (Circle1.Height / 2));
PointF Circle2Center = new PointF(Circle2.X + (Circle2.Width / 2), Circle2.Y + (Circle2.Height / 2));
DrawLinkedCircles(Circle1Center, Circle2Center, Circle1.Width / 2, Circle2.Width / 2, FillColor, g);
}
public void DrawLinkedCircles(PointF Circle1Center, PointF Circle2Center, float Circle1Radius, float Circle2Radius, Color FillColor, Graphics g)
{
float Direction = (Circle1Center.X > Circle2Center.X) ? -1 : 1;
float Distance = (float)Math.Sqrt(Math.Pow(Circle1Center.X - Circle2Center.X, 2) +
Math.Pow(Circle1Center.Y - Circle2Center.Y, 2));
Distance *= Direction;
float SinTheta = (Math.Max(Circle1Center.Y, Circle2Center.Y) -
Math.Min(Circle1Center.Y, Circle2Center.Y)) / Distance;
float RotationDirection = (Circle1Center.Y > Circle2Center.Y) ? -1 : 1;
float RotationAngle = (float)(Math.Asin(SinTheta) * (180 / Math.PI)) * RotationDirection;
using (GraphicsPath path = new GraphicsPath(FillMode.Winding))
{
path.AddEllipse(new RectangleF(-Circle1Radius, -Circle1Radius, 2 * Circle1Radius, 2 * Circle1Radius));
path.AddEllipse(new RectangleF(-Circle2Radius + (Math.Abs(Distance) * Direction),
-Circle2Radius, 2 * Circle2Radius, 2 * Circle2Radius));
path.AddPolygon(new[] {
new PointF(0, -Circle1Radius),
new PointF(0, Circle1Radius),
new PointF(Distance, Circle2Radius),
new PointF(Distance, -Circle2Radius),
});
path.AddEllipse(new RectangleF(-Circle1Radius, -Circle1Radius, 2 * Circle1Radius, 2 * Circle1Radius));
path.AddEllipse(new RectangleF(-Circle2Radius + (Math.Abs(Distance) * Direction),
-Circle2Radius, 2 * Circle2Radius, 2 * Circle2Radius));
path.CloseAllFigures();
g.TranslateTransform(Circle1Center.X, Circle1Center.Y);
g.RotateTransform(RotationAngle);
using (SolidBrush FillBrush = new SolidBrush(FillColor)) {
g.FillPath(FillBrush, path);
}
g.ResetTransform();
}
}
As the other answers so far slightly miss the correct solution, here is one that connects two circles of equal size:
using (Pen pen = new Pen(Color.Blue, radius)
{ EndCap = LineCap.Round, StartCap = LineCap.Round } )
g.DrawLine(pen, x1, y1, x2, y2);
Notes:
Usually is is good idea to set the smoothing mode of the graphics object to anti-alias..
To connect two circles of different sizes will take some math to calculate the four outer tangent points. From these one can get a polygon to fill or, if necessary one could create a GraphicsPath to fill, in case the color has an alpha < 1.
Jimi's comments point to a different solution that make use of GDI+ transformation capabilities.
Some of the answers or comments refer to the desired shape as an oval. While this ok in common speech, here, especially when geometry books are mentioned, this is wrong, as an oval will not have any straight lines.
As Jimi noted, what you call radius is really the diameter of the circles. I left the wrong term in the code but you should not!
Pseudo style:
circle1x;
circle1y;
circle2x;
circle2y;
midx=circle1x-circle2x;
midy=circle2x-circle2x;
draw circle at midx midy;
repeat for midx midy, in both directions. add another circle. honestly man, this isnt worth it,in order to make it smooth, you will need several circles. you need to draw an oval using the center of both circles as the two centers of your oval
I am currently working on a WinForms app, which at some point has to draw some stuff. Basically, it has to draw an unknown number of circles (f.e 3, 5, 10) in organized in a shape of a circle. Something like this:
I know it looks horrible. So I thought about defining a center of a circle and a radius. Then I just have to go f.e from the top of the big circle and draw a small circle every x-degrees (like for 3 circles it would be 120 degrees, for 4 circles 90 degrees etc.).
My question here is: Is there an algorithm, which would give me the center point of a circle to draw? Like I define my big circle with f.e center X = 50, Y = 50 and a radius R = 10. And then I draw a circle at the top, decide that I want to draw the next one 120 degrees far from the first one and I just need a point (X, Y) which is on the big circle?
Basically, you just need some math to figure out the coordinate of where the angle lands at the end of perimeter of the circle (a distance of the radius of the circle from the center of the circle). Here's psuedocode for this situation.
var center = new Point(0,0);
var radius = 5;
var degrees = 83;
var angle = Math.PI * degrees / 180;
var xPos = center.X + (radius * Math.cos(angle));
var yPos = center.Y + (radius * Math.sin(angle));
var newPosition = new Point(xPos,yPos);
Here, newPosition becomes the center point for the circled you'll be drawing along your imaginary circle. As for gathering the angles, simply use 360 / count * index.
I'm trying to build a mini overview/orientation map that is synchronized with a full MapControl view, like this: (Full Screenshot)
I'm getting trouble trying to calculate the width, height and location of the little red rectangle that is inside the mini map according to the MapControl's size, location and zoom.
It should be synchronized with the MapControl's view, and a click on the small map should also change the CenterPoint of the MapControl.
The full map is a MapControl in UWP and a the mini-map is just a Border UIElement over a static image.
I'm using the following formulas. They works but not accurately. The error margin is pretty noticeable, specially for big zooms.
For calculating the location and the size of the red rectangle:
var positions = MapControl.GetVisibleRegion(MapVisibleRegionKind.Near).Positions.ToArray();
var topLeft = positions[0];
var bottomLeft = positions[1];
var topRigt = positions[2];
//Transfering the Longitude system from [-180, 180] to [0, 360]
var centerX = (MapControl.Center.Position.Longitude + 180) * (SmallMapWidth / 360);
//Transfering the Latitude system from [-90, 90] to [0, 180]
var centerY = (-MapControl.Center.Position.Latitude + 90) * (SmallMapHeight / 180);
var topLeftX = topLeft.Longitude + 180;
var topRightX = topRigt.Longitude + 180;
//MapControl wraparound by default. In that case, the topRightX might be smaller than topLeftX, as it will start from the 'beginning'.
var deltaX = Math.Abs(topLeftX - (topLeftX < topRightX ? topRightX : 360 - topRightX));
//The width of the red rectangle
SmallMapViewPortWidth = Math.Abs(deltaX) * (SmallMapWidth / 360);
//The height of the red rectangle
SmallMapViewPortHeight = Math.Abs(topLeft.Latitude - bottomLeft.Latitude) * (SmallMapHeight / 180);
//The center point of the red rectangle.
RedRectangleCenterPoint = (centerX - SmallMapViewPortWidth / 2, centerY - SmallMapViewPortHeight / 2);
The following is for navigating the MapControl to a point that was clicked on the overview map. X and Y are the point that was clicked relatively to the overview map.
var lon = 360 * x / SmallMapWidth - 180;
var lat = 90 - 180 * y / SmallMapHeight;
What is wrong with my calculations? Why there is a pretty noticeable error margin?
The issue was actually the image that I was using for the orientation map. It was resolved once I started using proper Bing Maps images.
It seems there is an official API for rendering images in different sizes based on Bing Maps' projection
https://learn.microsoft.com/en-us/bingmaps/rest-services/imagery/get-a-static-map
i want to render nice radial tree layout and a bit stumbled with curved edges. The problem is that with different angles between source and target points the edges are drawn differently. Provided pics are from the single graph so you can see how they're differ for different edge directions. I think the point is in beizer curve control points generation and i just can't understand how to fix them.
I want them to be drawn the same way no matter what's the direction of the edge.
How can i achieve this as in Pic1?
How can i achieve this as in Pic2?
Like here: https://bl.ocks.org/mbostock/4063550
Thank you!
Code:
//draw using DrawingContext of the DrawingVisual
//gen 2 control points
double dx = target.X - source.X, dy = target.Y - source.Y;
var pts = new[]
{
new Point(source.X + 2*dx/3, source.Y),
new Point(target.X - dx/8, target.Y - dy/8)
};
//get geometry
var geometry = new StreamGeometry { FillRule = FillRule.EvenOdd };
using (var ctx = geometry.Open())
{
ctx.BeginFigure(START_POINT, false /* is filled */, false /* is closed */);
ctx.BezierTo(pts[0], pts[1], END_POINT, true, false);
}
geometry.Freeze();
//draw it
dc.DrawGeometry(DrawingBrush, DrawingPen, geometry);
UPDATE 1:
I've got the angle between previous vertex and source in radians using the following formula: Math.Atan2(prev.Y - source.Y, source.X - prev.X);
But still i get the edges like in Pic.4.
UPDATE 2
The prev vertex pos for branchAngle calculation is inaccurate so i decided to take an average angle between all edges in a branch as the branchAngle. This approach fails when edges from one brach are around the 180 deg mark and branch can have edge angles like 175, 176.. -176!! I use this code to make them all positive:
var angle = Math.Atan2(point1.Y - point2.Y, point1.X - point2.X);
while (angle < 0d)
angle += Math.PI*2;
But now the angles can be 350, 359.. 2!!! Quite difficult to calc an average :) Can you please advice me how i can work this around?
Pic1
Pic2
Pic3
Pic4
Looking at the graph from the link you provided each branch in the tree has it's own angle, which is used to declare the control points of the branch. This branchAngle is the same as the one of the vector going from the first node to the previous one (every branch can spawn several branches in turn). The angle of the first branch (first node = previous node = center) seems to be around -60°.
Setting the type of curve can be done by compensating this branch angle (0°, -90°, -180°,...) for all branches in the tree. Resulting in the controlAngle used for laying out the control points.
Generating the control points while taking into account the angles:
//gen per branch
double branchAngle = 30 * Math.PI / 180; //e.g., calc vector angle here
double cosB = Math.Cos(branchAngle);
double sinB = Math.Sin(branchAngle);
//depending on the desired curve compensate -90°, -180°,...
double controlAngle = branchAngle - (90 * Math.PI / 180);
double cosA = Math.Cos(controlAngle);
double sinA = Math.Sin(controlAngle);
//gen 2 control points
//calculate dx dy after rotation with branchAngle
double dxbase = target.X - source.X, dybase = target.Y - source.Y;
double dx = dxbase*sinB - dybase*cosB
double dy = dxbase*cosB + dybase*sinB
//control points based on controlAngle
var pts = new[]
{
new Point(source.X + (2*dx/3)*cosA , source.Y + (2*dx/3)*sinA),
new Point(target.X - (dx/8)*cosA + (dy/8)*sinA, target.Y - (dx/8)*sinA - (dy/8)*cosA)
};
Quick check
branchAngle = 30° &
compensation = -90° ->
controlAngle = -60°
I want to know the vertices of the arc where I have its StartPoint, Center and the EndPoint as well as the radius of the arc. I am aware that the arc which is drawn is by creating a small lines with the precision which is specified in the parameter. What I am trying to achieve is calculate the area of a polygon which may have an arc in it which can look like the image I have attached with this question.
SP = StartPoint of the Arc.
EP = EndPoint of the Arc.
CP = Center of the Arc.
Knowing StartPoint, Center and the EndPoint of arc is not enough to define it uniquely. You have to knew some another parameter(s).
When arc is well defined, it is possible to calculate an area of circular segment geometrically
Edit: Because you also know radius R, we can calculate an area:
Theta = 2 * ArcSin(Distance_SPtoEP/(2*R))
Area = 1/2 * R * R * (Theta - Sin(Theta))
Quick check:
R = 1, semicircle.
Theta = 2 * ArcSin(2/2) = 2 * Pi/2 = Pi
Area = 1/2 * (Pi - 0) = Pi/2 - true
Edit2: It is simpler to connect SP and EP to get remaining polygon, than build polyline approximation of arc and calc area of hundred-vertice polygon.
Polyline approximation:
We want that arc-line distance doesn't excess some limit d. So we will calculate small arc angle A
d = R * (1-Cos(A/2))
A = 2 * ArcCos(1-d/R)
Now divide large arc to small pieces with angle A, and generate new vertices
Your drawing seems to indicate a half ellipse.
The long axis (A) would be SP-EP and the short axis (B) should be given. The area is Pi.A.B/8.