【问题标题】:Connect two circles with a line用一条线连接两个圆
【发布时间】:2018-05-02 08:19:49
【问题描述】:

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

问题是现在我需要制作单向线,它的末端有一个“箭头”,以指出线的方向。所以现在我不能使用圆的中点,因为我需要从边框到边框相互连接,所以“箭头”才能正确显示。

在我最后一次尝试时,结果并不好:

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

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

编辑:圆圈是可移动的,它们可以在任何位置,所以这条线在任何情况下都应该有效。

【问题讨论】:

  • “边框到边框”是指您希望线段与每个圆相切吗?如果是这样,则(通常)有两个这样的线段,圆圈位于线的同一侧,另外两个圆圈位于线的相对侧。你想要哪些线段?另外,这两个圆圈的大小是否与您的屏幕截图中相同,还是更通用?
  • 所以,你知道圆的中心位置,你知道它们的半径。由此您可以确定它们的偏移位置(左上角的 x/y)。你需要做的是计算两个圆之间的角度,然后你可以计算出圆的圆周上一条线相交的点

标签: java swing math graphics shapes


【解决方案1】:

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

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

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

所以,两点之间的角度可以使用类似...

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(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 绘制圆圈的方式之间的差异

好吧,你说这么重要,这对我有什么帮助?嗯,其实我很喜欢。

您将计算 to 圆之间的角度(往返,您可能可以简单地反转一个角度,但我有可用的计算,所以我使用了它)。由此,您可以计算每个圆上直线相交的点,然后您只需要绘制它,就像...

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

可运行示例

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

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

    }

}

箭头

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

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

public class ArrowHead extends Path2D.Double {

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

真的很简单。它只是创建了两条线,它们指向上方,在可用空间的中间汇合。

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

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

然后变换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);

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

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

【讨论】:

  • 不需要使用角度和三角函数。
  • 一个问题,我的圆圈是在扩展一个 Ellipse2D.Float,它的坐标是基于形状的左上角。你的方法考虑形状的中心还是左上角?
  • 好的,我自己发现的,直到我开始发送圆的中心位置,它才工作,然后它工作正常。我现在尝试实现箭头。
  • 完美解决方案,感谢您的时间和精力!
  • 是的,我也使用椭圆并使用中心位置进行许多计算 - 该示例有许多帮助方法来计算数据的各个部分
【解决方案2】:

我的把戏:

让两个中心分别为C0C1。使用复数,通过变换将这两个点映射到从原点开始的水平线段

P' = (P - C0) (C1 - C0)* / L

其中* 表示共轭,L = |C1 - C0|。 (如果你不喜欢复数符号,你也可以用矩阵来表达。)

现在片段的可见部分从(R0, 0) 变为(L - R1, 0)。箭头的另外两个顶点位于(L - R1 - H, W)(L - R1 - H, -W),箭头的高度为H,宽度为2W

通过应用逆变换得到原始坐标,

P = C0 + L P' / (C1 - C0)*.

【讨论】:

    【解决方案3】:

    令第一个圆的中心坐标为AX,AY,半径AR,第二个圆为BX,BY,BR。

    差分向量

    D = (DX, DY)  = (BX - AX, BY - AY)
    

    标准化

    d = (dx, dy) = (DX / Length(D), DY / Length(D))
    

    箭头的起点

    S = (sx, sy) = (AX + dx * AR, AY + dy * AR)  
    

    终点

    E = (ex, ey) = (BX - dx * BR, BY - dy * BR)  
    

    例子:

    AX = 0     AY = 0     AR = 1
    BX = 4     BY = 3     BR = 2
    D = (4, 3)
    Length(D) = 5
    dx = 4/5
    dy = 3/5
    sx = 0.8  sy = 0.6
    ex = 4 - 2 * 4/5 = 12/5 = 2.4
    ey = 3 - 2 * 3/5 = 9/5 = 1.8
    

    【讨论】:

    • 现在角度完全正确,但线超出了圆圈,请参阅:i.stack.imgur.com/zdqZB.png
    • 您的计算可能有误。看看我的例子。
    【解决方案4】:

    Screenshot,我想你需要找到圆A的右上角,然后将到底部的总距离的一半加上y。接下来,找到圆 B 的右上角,并将到左上角的距离的一半加到 x 上。最后,画一条连接两者的线,并在其末端绘制一个箭头。
    像这样:

    private int x1, y1, x2, y2 width = 20, height = 20;
    
    private void example(Graphics g) {
        // Set x1, x2, y1, and y2 to something
        g.drawOval(x1, y1, width, height);
        g.drawOval(x2, y2, width, height);
        g.drawLine(x1, y1 + (height/2), x2 + (width/2), y2);
        g.drawImage(/*Image of an arrow*/, (x2 + width/2)-2, y2);
    }
    

    【讨论】:

    • 对不起,我忘了说圆圈是可移动的,所以这应该是动态的,它应该可以在任何位置工作。
    • @Deeh 好些了吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-06
    • 2012-08-15
    • 1970-01-01
    相关资源
    最近更新 更多