根据角度在圆周围画一条线

Draw A line around the circle based on angle

我希望它看起来像什么:

圆圈随着箭头围绕中心圆移动。

目前的情况:

我想在两个圆之间画两条线。然而,这些圆圈在屏幕上四处移动,我不知道如何在它们之间画线。例如,我总是在两个圆圈的左上角之间画一条线,但仅此而已。我需要帮助在 java 中绘制一条线,该线将根据其位置进行调整,以便随着圆圈的移动使线围绕边缘移动

      for (int z = 0; z < lines.size(); z++) {
                    if (lines.get(z).getfState().equals(states.get(a).getText()) && !lines.get(z).getfState().equals(lines.get(z).getnState())) {
                        transition.get(z).setIcon(null);
                        for (int x = 0; x < states.size(); x++) {
                            if (states.get(x).getText().equals(lines.get(z).getnState()) && states.get(a).getText().equals(lines.get(z).getfState())) {
                                int xbegin = (int) states.get(a).getBounds().getX();
                                int ybegin = (int) states.get(a).getBounds().getY();
                                int xend = (int) states.get(x).getBounds().getX();
                                int yend = (int) states.get(x).getBounds().getY();
                                if (xbegin > xend) {
                                    Path2D.Double rect = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend, yend, 10, 7));
                                    OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend + 30, yend, 10, 7), Color.BLACK);
                                    transition.get(z).setIcon(transit);
                                    transition.get(z).setBounds(rect.getBounds().x, rect.getBounds().y, rect.getBounds().width + 20, rect.getBounds().height + 20);
                                    jPanel2.revalidate();
                                    jPanel2.repaint();
                                } else {
                                    if (xend - xbegin < 75) {
                                        xbegin = xbegin - 20;
                                        xend = xend - 20;
                                    }
                                    xbegin = xbegin + 5;
                                    ybegin = ybegin + 25;
                                    xend = xend + 5;
                                    yend = yend + 25;
                                    Path2D.Double rect = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7));
                                    OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7), Color.BLACK);
                                    transition.get(z).setIcon(transit);
                                    transition.get(z).setBounds(rect.getBounds().x, rect.getBounds().y, rect.getBounds().width + 20, rect.getBounds().height + 20);
                                    jPanel2.revalidate();
                                    jPanel2.repaint();
                                }

                            }
                        }
                    } else if (lines.get(z).getnState().equals(states.get(a).getText()) && !lines.get(z).getfState().equals(lines.get(z).getnState())) {
                        transition.get(z).setIcon(null);
                        for (int x = 0; x < states.size(); x++) {
                            if (states.get(x).getText().equals(lines.get(z).getfState()) && states.get(a).getText().equals(lines.get(z).getnState())) {
                                int xend = (int) states.get(a).getBounds().getX();
                                int yend = (int) states.get(a).getBounds().getY();
                                int xbegin = (int) states.get(x).getBounds().getX();
                                int ybegin = (int) states.get(x).getBounds().getY();
                                if (xbegin > xend) {
                                    Path2D.Double rect2 = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend, yend, 10, 7));

                                    OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend + 30, yend, 10, 7), Color.BLACK);
                                    transition.get(z).setIcon(transit);
                                    transition.get(z).setBounds(rect2.getBounds().x, rect2.getBounds().y, rect2.getBounds().width + 20, rect2.getBounds().height + 20);
                                    jPanel2.revalidate();
                                    jPanel2.repaint();
                                } else {
                                    if (xend - xbegin < 75) {
                                        xbegin = xbegin + 20;
                                        xend = xend + 20;
                                    }
                                    xbegin = xbegin + 5;
                                    ybegin = ybegin + 25;
                                    xend = xend + 5;
                                    yend = yend + 25;
                                    Path2D.Double rect2 = new Path2D.Double(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7));
                                    OutlineIcon transit = new OutlineIcon(drawArrowLine(xbegin, ybegin, xend - 10, yend, 10, 7), Color.BLACK);
                                    transition.get(z).setIcon(transit);
                                    transition.get(z).setBounds(rect2.getBounds().x, rect2.getBounds().y, rect2.getBounds().width + 20, rect2.getBounds().height + 20);
                                    jPanel2.revalidate();
                                    jPanel2.repaint();
                                }
                            }
                        }
          public static Path2D.Double createArrowForLine(
        int fromPointx,
        int fromPointy,
        double rotationDeg,
        double length,
        double wingsAngleDeg) {

    double ax = fromPointx;
    double ay = fromPointy;

    double radB = Math.toRadians(-rotationDeg + wingsAngleDeg);
    double radC = Math.toRadians(-rotationDeg - wingsAngleDeg);

    Path2D resultPath = new Path2D.Double();
    resultPath.moveTo(length * Math.cos(radB) + ax, length * Math.sin(radB) + ay);
    resultPath.lineTo(ax, ay);
    resultPath.lineTo(length * Math.cos(radC) + ax, length * Math.sin(radC) + ay);
    return (Path2D.Double) resultPath;
}

虽然问题中出现了一些问题,而且目前提供的代码看起来有问题,但现在的问题核心非常有趣...

解决这个问题有不同的选择。从您目前提供的图像来看,圆圈似乎始终具有相同的大小,这使事情变得简单得多。对于不同大小的圆,你真的必须计算圆的 切线 在所需的方向上,同时考虑另一个圆的半径。当然,这是可能的,但有点不那么琐碎。

如果你有同样大小的圆圈,你可以

  • 计算两个圆心的差值
  • 将其除以距离,得到(标准化的)方向
  • 将这个方向旋转90°
  • 按半径缩放旋转方向矢量
  • 将缩放和旋转后的矢量添加到圆心

这将产生这样一条直线的一个端点。可以顺时针旋转一次,逆时针旋转一次约90°,分别得到直线的"upper"和"lower"端点。

图像已通过编辑更新,见下文

实际的计算是在下面MCVEcomputeLine方法中完成的。请注意,此示例使用 "simple" 方法, 尽管 它使用的圆圈大小略有不同。效果是,当两个圆的大小差异太大时(基本上与圆之间的距离相比),那么线可能会稍微 与圆相交 。但解决方案应该是简单性和普遍适用性之间的合理权衡。特别是,对于同样大小的圆,根本不会有交集。

代码已通过编辑更新,见下文

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class LinesAtCirclesTest
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {

            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        JPanel linesAtCirclesTestPanel = new LinesAtCirclesTestPanel(); 
        f.getContentPane().add(linesAtCirclesTestPanel);
        f.setSize(400,400);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

class LinesAtCirclesTestPanel extends JPanel 
    implements MouseListener, MouseMotionListener
{
    private Point2D draggedCenter;
    private List<Point2D> centers = new ArrayList<Point2D>();
    private List<Double> radii = new ArrayList<Double>();

    public LinesAtCirclesTestPanel()
    {
        addMouseListener(this);
        addMouseMotionListener(this);

        addCircle(100, 100, 30);
        addCircle(200, 300, 50);
        addCircle(300, 200, 40);
    }

    private void addCircle(double x, double y, double radius)
    {
        centers.add(new Point2D.Double(x,y));
        radii.add(radius);
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING, 
            RenderingHints.VALUE_ANTIALIAS_ON);

        for (int i=0; i<centers.size(); i++)
        {
            Point2D center0 = centers.get(i);
            double radius0 = radii.get(i);

            Shape ellipse = new Ellipse2D.Double(
                center0.getX() - radius0, center0.getY() - radius0, 
                radius0 + radius0, radius0 + radius0);

            g.setColor(Color.LIGHT_GRAY);
            g.fill(ellipse);
            g.setColor(Color.BLACK);
            g.draw(ellipse);
        }

        g.setColor(Color.RED);
        for (int i=0; i<centers.size() - 1; i++)
        {
            Point2D center0 = centers.get(i);
            double radius0 = radii.get(i);
            Point2D center1 = centers.get(i+1);
            double radius1 = radii.get(i+1);

            g.draw(createArrow(computeLine(center0, radius0, center1, radius1, true)));
            g.draw(createArrow(computeLine(center0, radius0, center1, radius1, false)));
        }
    }

    private static Shape createArrow(Line2D line)
    {
        double dx = line.getX2() - line.getX1();
        double dy = line.getY2() - line.getY1();
        double angleToX = Math.atan2(dy, dx);
        final double angleRad = Math.toRadians(30);
        final double headLength = 20.0f;
        double dxL = Math.cos(Math.PI + angleToX + angleRad) * headLength;
        double dyL = Math.sin(Math.PI + angleToX + angleRad) * headLength;
        double dxR = Math.cos(Math.PI + angleToX - angleRad) * headLength;
        double dyR = Math.sin(Math.PI + angleToX - angleRad) * headLength;
        Path2D arrow = new Path2D.Double();
        arrow.moveTo(line.getX1(), line.getY1());
        arrow.lineTo(line.getX2(), line.getY2());
        arrow.lineTo(line.getX2() + dxL, line.getY2() + dyL);
        arrow.moveTo(line.getX2(), line.getY2());
        arrow.lineTo(line.getX2() + dxR, line.getY2() + dyR);
        return arrow;
    }

    private static Line2D computeLine(
        Point2D center0, double radius0,
        Point2D center1, double radius1, 
        boolean upper)
    {
        double dx = center1.getX() - center0.getX();
        double dy = center1.getY() - center0.getY();
        double invLength = 1.0 / Math.hypot(dx, dy);
        double dirX = dx * invLength;
        double dirY = dy * invLength;

        double rotDirX = dirY;
        double rotDirY = -dirX;
        if (upper)
        {
            rotDirX = -dirY;
            rotDirY = dirX;
        }

        double x0 = center0.getX() + rotDirX * radius0;
        double y0 = center0.getY() + rotDirY * radius0;

        double x1 = center1.getX() + rotDirX * radius1;
        double y1 = center1.getY() + rotDirY * radius1;

        if (upper)
        {
            return new Line2D.Double(x1, y1, x0, y0);
        }
        return new Line2D.Double(x0, y0, x1, y1);
    }

    @Override
    public void mousePressed(MouseEvent e)
    {
        draggedCenter = null;
        for (int i=0; i<centers.size(); i++)
        {
            Point2D center = centers.get(i);
            double radius = radii.get(i);
            if (e.getPoint().distance(center) < radius)
            {
                draggedCenter = center;
            }
        }
    }

    @Override
    public void mouseReleased(MouseEvent e)
    {
        draggedCenter = null;
    }

    @Override
    public void mouseDragged(MouseEvent e)
    {
        if (draggedCenter == null)
        {
            return;
        }
        draggedCenter.setLocation(e.getPoint());
        repaint();
    }



    @Override
    public void mouseMoved(MouseEvent e)
    {
        // Not used
    }

    @Override
    public void mouseClicked(MouseEvent e)
    {
        // Not used
    }

    @Override
    public void mouseEntered(MouseEvent e)
    {
        // Not used
    }

    @Override
    public void mouseExited(MouseEvent e)
    {
        // Not used
    }
}

EDIT in response to the comment:

原始代码计算了 Line2D 个对象。从一条线创建一个箭头,在最简单的情况下,基本上是用一点三角学来完成的,网络上有很多这方面的资源。

为了回应评论,我扩展了示例以显示简单的箭头,如上图所示。


然而,当仔细观察时,可能会注意到这种箭头有几个自由度:

  • 头部长度应该是绝对长度还是相对于箭头?
  • 头部宽度应该是绝对的还是相对于箭头的?
  • (或者:箭头的角度应该是多少?)
  • 箭头是填充还是由线条组成?
  • 箭头的"trunk"应该是单线,还是轮廓形状?
  • 后备箱的宽度应该是多少?
  • ...

为了涵盖其中的一些自由度,我创建了一个 ArrowCreator class a while ago, and there's also a sample 展示如何使用它。