如何在视觉上连接 2 个圆圈?

How to visually connect 2 circles?

我们知道2个圆的x和y圆心位置,半径相同。我想在视觉上连接这些圆圈,而不是为连接 2 个圆圈中心的线上的每个点循环绘制椭圆。

来自这里:

为此:

代码:

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));

伪风格:

  circle1x;
    circle1y;

circle2x;
circle2y;


midx=circle1x-circle2x;
midy=circle2x-circle2x;

draw circle at midx midy;

在两个方向重复 midx midy。添加另一个圆圈。老实说,这不值得,为了让它顺利,你需要几个圈子。你需要用两个圆的中心作为椭圆的两个中心来画一个椭圆

到目前为止的其他答案稍微错过了正确的解决方案,这是连接两个大小相等的圆的解决方案:

using (Pen pen = new Pen(Color.Blue, radius)
 { EndCap = LineCap.Round, StartCap = LineCap.Round }  )
     g.DrawLine(pen, x1, y1, x2, y2);

备注:

  • 通常将图形对象的平滑模式设置为抗锯齿是个好主意..

  • 要连接两个不同大小的圆需要一些数学运算才能计算出四个外部 tangent points。从这些可以得到一个多边形来填充,或者如果需要,可以创建一个 GraphicsPath 来填充,以防颜色的 alpha < 1。

  • Jimi 的评论指出了一个利用 GDI+ 转换功能的不同解决方案。

  • 一些答案或评论将所需的形状称为 oval。虽然这在普通话中是可以的,但在这里,尤其是当提到几何书籍时,这是错误的,因为椭圆不会有任何直线。

  • 正如 Jimi 指出的那样,您所谓的 半径 实际上是圆的 直径 。我在代码中遗漏了错误的术语,但 你不应该!

当圆的直径不同时的解决方案。

第一个需要的信息是两个圆心之间的距离。
为了计算它,我们使用应用于笛卡尔平面的 Euclidean distance

其中(x1, y1)(x2, y2)是两个圆心的坐标。
我们还需要知道方向(表示为正值或负值):计算出的 [Distance] 将始终为正值。

C#中,可以编码为:

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;

现在,我们有了两个圆心之间的距离,它也表达了一个方向。
我们还需要知道这条连接两个中心的虚拟线是如何相对于我们的绘图平面旋转的。在下图中,Distance可以看作是right triangleh = (A, B)的斜边。 C 角由与轴平行并穿过圆心的直线的交点确定。

我们需要计算角度 Theta (θ)
利用Pythagorean theorem,我们可以推导出角度Theta的正弦为Sinθ = b/h(如图)

使用圆的中心坐标,这可以在 C# 中编码为:
(Distance是三角形的斜边)

float SinTheta = (Math.Max(Circle1Center.Y, Circle2Center.Y) - 
                  Math.Min(Circle1Center.Y, Circle2Center.Y)) / Distance;

SinTheta表示Radians中的一个角。我们需要用 Degrees 表示的角度:Graphics 对象将此度量用于其世界变换函数。

float RotationAngle = (float)(Math.Asin(SinTheta) * (180 / Math.PI));

现在,我们需要构建一个 连接器,一个连接 2 个圆圈的形状。我们需要一个多边形;矩形不能有不同的边对(我们正在考虑具有不同直径的圆)。
此多边形的长边 = 圆心之间的距离,短边 = 圆直径。

要构建多边形,我们可以同时使用两者 Graphics.DrawPolygon and GraphicsPath.AddPolygon。我选择 GraphicsPath 方法,因为 GraphicsPath 可以容纳多个形状,并且这些形状可以 相互作用 ,以某种方式。

连接 2 个考虑的圆和多边形,我们需要使用之前计算的RotationAngle 旋转多边形。
执行旋转的一种简单方法是将世界坐标移动到其中一个圆的中心,使用 Graphics.TranslateTransform method, then rotate the new coordinates, using Graphics.RotateTransform.

我们需要绘制我们的多边形定位短边之一 - 对应于作为坐标变换中心的圆的直径 - 在圆的中心。因此,当应用旋转时,它的短边将位于该变换的中间,锚定到中心。

这里,figure 3显示了Polygon(黄色形状)的定位(ok,看起来像个矩形,没关系);
figure 4之后的同一个Polygon旋转。

备注:
由于,此图需要用non-transparent颜色的SolidBrush来绘制,有点令人失望。
好吧,semi-transparent画笔不是禁止的,但是重叠的形状会有不同的颜色,交叉点透明颜色的sum

然而,可以使用 semi-transparent 画笔绘制形状而不更改颜色,使用 GraphicsPath 功能使用应用于所有重叠部分的颜色填充其形状。我们只需要更改默认值 FillMode(参见文档中的示例),将其设置为 FillMode.Winding.

示例代码:
在此示例中,在 Graphics 上下文中绘制了两对 Circles。然后将它们连接到使用 GraphicsPath.AddPolygon().
创建的多边形形状 (当然,我们需要用到一个drawable Control的Paint事件,这里是一个Form)

重载的辅助函数接受圆的中心位置,表示为 PointFRectangleF 结构,表示圆的边界。

这是视觉结果,使用全色并使用 semi-transparent 画笔:

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);
}

辅助函数:

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();
    }
}