用一条线连接两个圆 [英] Connect two circles with a line

查看:41
本文介绍了用一条线连接两个圆的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在 JPanel 中绘制了两个形状(圆圈),我需要用一条线将它们连接起来.我只是通过获得圆的中点并相互连接来做到这一点,很简单.

I'm drawing two shapes (circles) in a JPanel and I need to connect them with a line. I was doing this by just getting the middle point of the circle and connecting each other, easy.

问题是现在我需要制作单向线,它的末尾有一个箭头",以指出线的走向.所以现在我不能使用圆的中点,因为我需要从边框到边框相互连接,这样箭头"才能正确出现.

The problem is that now I need to make single-direction lines, which has an "arrow" at the end, to point out which direction the line goes. So now I can't use the middle point of the circle because I need to connect each other from border to border, so the "arrow' can appear correctly.

我最后一次尝试的结果是,没有什么好处:

On my last try that was the result, nothing good:

PS:在屏幕截图中,我不是为了看到线条的确切位置而填充圆圈,但通常我会填充它.

PS: In the screenshot I'm not filling the circles just to see the exact position of the line, but normally I would fill it.

我无法计算开始/结束我的行所需的边界的确切位置.任何人都知道如何做到这一点?

I'm having trouble to calculate the exact position of the border I need to start/end my line. Anyone has any idea on how to do this?

圆圈是可移动的,它们可以在任何位置,所以这条线在任何情况下都应该起作用.

The circles are movable, they could be in any position, so the line should work in any case.

推荐答案

好了,基本上,我们可以将问题分解为基本问题:

Okay, so basically, we can break down the problem to basic issues:

  1. 获取两个圆之间的角度
  2. 沿着这个角度从一个圆的圆周到另一个圆画一条线

这两个问题都不难解决(只要花时间在互联网上搜索就可以找到解决方案——因为我就是从那里得到它们的;))

Both these issues aren't hard to solve (and any time spent searching the internet would provide solutions - because that's where I got them from ;))

所以,两点之间的角度可以使用类似的东西来计算......

So, the angle between two points could be calculated using something like...

protected double angleBetween(Point2D from, Point2D to) {
    double x = from.getX();
    double y = from.getY();

    // This is the difference between the anchor point
    // and the mouse.  Its important that this is done
    // within the local coordinate space of the component,
    // this means either the MouseMotionListener needs to
    // be registered to the component itself (preferably)
    // or the mouse coordinates need to be converted into
    // local coordinate space
    double deltaX = to.getX() - x;
    double deltaY = to.getY() - y;

    // Calculate the angle...
    // This is our "0" or start angle..
    double rotation = -Math.atan2(deltaX, deltaY);
    rotation = Math.toRadians(Math.toDegrees(rotation) + 180);

    return rotation;
}

圆上的点可以使用类似的东西来计算......

And the point on a circle can be calculated using something like...

protected Point2D getPointOnCircle(Point2D center, double radians, double radius) {

    double x = center.getX();
    double y = center.getY();

    radians = radians - Math.toRadians(90.0); // 0 becomes the top
    // Calculate the outter point of the line
    double xPosy = Math.round((float) (x + Math.cos(radians) * radius));
    double yPosy = Math.round((float) (y + Math.sin(radians) * radius));

    return new Point2D.Double(xPosy, yPosy);

}

请注意,结果有一些内部修改,以考虑数学解决方案与Graphics API 绘制圆圈的方式之间的差异

Just beware, there's some internal modifications of the results to allow for the difference between the mathematical solution and the way that the Graphics API draws circles

好吧,你说了这么多,这对我有什么帮助?嗯,其实我很喜欢.

Okay, so big deal you say, how does that help me? Well, I great deal actually.

您将计算 to 圆之间的角度(to 和 from,您可能可以简单地反转一个角度,但我有可用的计算,所以我使用了它).由此,您可以计算每个圆上该线将相交的点,然后您只需绘制它,例如...

You'd calculate the angle between the to circles (both to and from, you might be able to simple inverse one angle, but I have the calculation available so I used it). From that, you can calculate the point on each circle where the line will intersect and then you simply need to draw it, something like...

double from = angleBetween(circle1, circle2);
double to = angleBetween(circle2, circle1);

Point2D pointFrom = getPointOnCircle(circle1, from);
Point2D pointTo = getPointOnCircle(circle2, to);

Line2D line = new Line2D.Double(pointFrom, pointTo);
g2d.draw(line);

可运行示例

因为我已经将大部分计算提炼为公共属性,所以我提供了我的测试代码作为可运行的示例.所有的计算都基于动态值,没有什么是真正的硬编码.例如,您可以更改圆圈的大小和位置,计算应该会继续进行...

Runnable Example

Because I've distilled much of the calculations down to communalised properties, I've provided my test code as a runnable example. All the calculations are based on dynamic values, nothing is really hard coded. For example, you can change the size and positions of the circles and the calculations should continue to work...

import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class Test {

    public static void main(String[] args) {
        new Test();
    }

    public Test() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Ellipse2D circle1;
        private Ellipse2D circle2;

        private Point2D drawTo;

        public TestPane() {
            circle1 = new Ellipse2D.Double(10, 10, 40, 40);
            circle2 = new Ellipse2D.Double(100, 150, 40, 40);

            //addMouseMotionListener(new MouseAdapter() {
            //  @Override
            //  public void mouseMoved(MouseEvent e) {
            //      drawTo = new Point2D.Double(e.getPoint().x, e.getPoint().y);
            //      repaint();
            //  }
            //});
        }

        protected Point2D center(Rectangle2D bounds) {
            return new Point2D.Double(bounds.getCenterX(), bounds.getCenterY());
        }

        protected double angleBetween(Shape from, Shape to) {
            return angleBetween(center(from.getBounds2D()), center(to.getBounds2D()));
        }

        protected double angleBetween(Point2D from, Point2D to) {
            double x = from.getX();
            double y = from.getY();

            // This is the difference between the anchor point
            // and the mouse.  Its important that this is done
            // within the local coordinate space of the component,
            // this means either the MouseMotionListener needs to
            // be registered to the component itself (preferably)
            // or the mouse coordinates need to be converted into
            // local coordinate space
            double deltaX = to.getX() - x;
            double deltaY = to.getY() - y;

            // Calculate the angle...
            // This is our "0" or start angle..
            double rotation = -Math.atan2(deltaX, deltaY);
            rotation = Math.toRadians(Math.toDegrees(rotation) + 180);

            return rotation;
        }

        protected Point2D getPointOnCircle(Shape shape, double radians) {
            Rectangle2D bounds = shape.getBounds();
//          Point2D point = new Point2D.Double(bounds.getX(), bounds.getY());
            Point2D point = center(bounds);
            return getPointOnCircle(point, radians, Math.max(bounds.getWidth(), bounds.getHeight()) / 2d);
        }

        protected Point2D getPointOnCircle(Point2D center, double radians, double radius) {

            double x = center.getX();
            double y = center.getY();

            radians = radians - Math.toRadians(90.0); // 0 becomes th?e top
            // Calculate the outter point of the line
            double xPosy = Math.round((float) (x + Math.cos(radians) * radius));
            double yPosy = Math.round((float) (y + Math.sin(radians) * radius));

            return new Point2D.Double(xPosy, yPosy);

        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();
            g2d.draw(circle1);
            g2d.draw(circle2);

            // This was used for testing, it will draw a line from circle1 to the
            // drawTo point, which, if enabled, is the last known position of the
            // mouse
            //if (drawTo != null) {
            //  Point2D pointFrom = center(circle1.getBounds2D());
            //  g2d.setColor(Color.RED);
            //  g2d.draw(new Line2D.Double(drawTo, pointFrom));
            //
            //  double from = angleBetween(pointFrom, drawTo);
            //  System.out.println(NumberFormat.getNumberInstance().format(Math.toDegrees(from)));
            //
            //  Point2D poc = getPointOnCircle(circle1, from);
            //  g2d.setColor(Color.BLUE);
            //  g2d.draw(new Line2D.Double(poc, drawTo));
            //}

            double from = angleBetween(circle1, circle2);
            double to = angleBetween(circle2, circle1);

            Point2D pointFrom = getPointOnCircle(circle1, from);
            Point2D pointTo = getPointOnCircle(circle2, to);

            g2d.setColor(Color.RED);
            Line2D line = new Line2D.Double(pointFrom, pointTo);
            g2d.draw(line);
            g2d.dispose();
        }

    }

}

箭头头

目的是将箭头视为一个单独的实体.原因是因为这样更简单,无论对象之间的距离如何,您都可以获得更一致的结果.

Arrow head

The intention is to treat the arrow head as a separate entity. The reason is because it's just simpler that way, you also get a more consistent result regardless of the distance between the objects.

所以,首先,我定义了一个新的Shape...

So, to start with, I define a new Shape...

public class ArrowHead extends Path2D.Double {

    public ArrowHead() {
        int size = 10;
        moveTo(0, size);
        lineTo(size / 2, 0);
        lineTo(size, size);
    }
    
}

其实很简单.它只是创建了两条向上的线,在可用空间的中间相遇.

Pretty simple really. It just creates two lines, which point up, meeting in the middle of the available space.

然后在 paintComponent 方法中,我们使用我们已有的可用信息执行一些 AffineTransform 魔术,即

Then in the paintComponent method, we perform some AffineTransform magic using the available information we already have, namely

  • 目标圆周上的点
  • 与目标圆的角度

并转换ArrowHead 形状...

g2d.setColor(Color.MAGENTA);
ArrowHead arrowHead = new ArrowHead();
AffineTransform at = AffineTransform.getTranslateInstance(
                pointTo.getX() - (arrowHead.getBounds2D().getWidth() / 2d), 
                pointTo.getY());
at.rotate(from, arrowHead.getBounds2D().getCenterX(), 0);
arrowHead.transform(at);
g2d.draw(arrowHead);

现在,因为我疯了,我还通过画一个指向我们源圈的箭头来测试代码,只是为了证明计算是可行的...

Now, because I'm crazy, I also tested the code by drawing an arrow pointing at our source circle, just to prove that the calculations would work...

// This just proofs that the previous calculations weren't a fluke
// and that the arrow can be painted pointing to the source object as well
g2d.setColor(Color.GREEN);
arrowHead = new ArrowHead();
at = AffineTransform.getTranslateInstance(
                pointFrom.getX() - (arrowHead.getBounds2D().getWidth() / 2d), 
                pointFrom.getY());
at.rotate(to, arrowHead.getBounds2D().getCenterX(), 0);
arrowHead.transform(at);
g2d.draw(arrowHead);

这篇关于用一条线连接两个圆的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆