用一条线连接两个圆
Connect two circles with a line
我在 JPanel 中绘制两个形状(圆形),我需要用一条线将它们连接起来。我只是通过获取圆的中点并相互连接来做到这一点,很简单。
问题是现在我需要制作单向线,最后有一个"arrow",以指出线的走向。所以现在我不能使用圆的中点,因为我需要从一个边界到另一个边界相互连接,这样“箭头”才能正确出现。
我最后一次尝试的结果是,没什么好:
PS: 在屏幕截图中,我没有填充圆圈只是为了查看线条的确切位置,但通常我会填充它。
我无法计算 start/end 我的线条所需的边界的确切位置。有人知道如何做到这一点吗?
编辑:圆圈是可移动的,它们可以在任何位置,所以这条线在任何情况下都应该有效。
看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);
}
设第一个圆心坐标为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
好的,基本上,我们可以将问题分解为基本问题:
- 得到两个圆的夹角
- 沿着这个角度从一个圆的圆周到另一个圆的圆周画一条线
这两个问题都不难解决(花在互联网上的任何时间搜索都会提供解决方案 - 因为我就是从那里得到它们的;))
所以,两点之间的角度可以用类似...
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 绘制圆圈的方式之间的差异
好吧,你说的那么重要,这对我有什么帮助?嗯,其实我很厉害。
你会计算到圆圈之间的角度(到圆圈和从圆圈之间,你也许可以简单地反转一个角度,但我有可用的计算,所以我使用了它)。由此,您可以计算出每个圆上直线相交的点,然后您只需绘制它,例如...
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);
}
}
真的很简单。它只是创建两条线,指向上方,在可用 space.
的中间相遇
然后在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);
我的绝招:
设两个中心为C0
和C1
。使用复数,通过变换
将这两个点映射到从原点开始的水平线段
P' = (P - C0) (C1 - C0)* / L
其中 *
表示共轭,L = |C1 - C0|
。 (如果你不喜欢复数表示法,你也可以用矩阵表示。)
现在段的可见部分从 (R0, 0)
变为 (L - R1, 0)
。对于高度 H
和宽度 2W
.
的箭头,箭头的另外两个顶点位于 (L - R1 - H, W)
和 (L - R1 - H, -W)
通过应用逆变换,您可以获得原始坐标,
P = C0 + L P' / (C1 - C0)*.
我在 JPanel 中绘制两个形状(圆形),我需要用一条线将它们连接起来。我只是通过获取圆的中点并相互连接来做到这一点,很简单。
问题是现在我需要制作单向线,最后有一个"arrow",以指出线的走向。所以现在我不能使用圆的中点,因为我需要从一个边界到另一个边界相互连接,这样“箭头”才能正确出现。
我最后一次尝试的结果是,没什么好:
PS: 在屏幕截图中,我没有填充圆圈只是为了查看线条的确切位置,但通常我会填充它。
我无法计算 start/end 我的线条所需的边界的确切位置。有人知道如何做到这一点吗?
编辑:圆圈是可移动的,它们可以在任何位置,所以这条线在任何情况下都应该有效。
看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);
}
设第一个圆心坐标为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
好的,基本上,我们可以将问题分解为基本问题:
- 得到两个圆的夹角
- 沿着这个角度从一个圆的圆周到另一个圆的圆周画一条线
这两个问题都不难解决(花在互联网上的任何时间搜索都会提供解决方案 - 因为我就是从那里得到它们的;))
所以,两点之间的角度可以用类似...
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 绘制圆圈的方式之间的差异
好吧,你说的那么重要,这对我有什么帮助?嗯,其实我很厉害。
你会计算到圆圈之间的角度(到圆圈和从圆圈之间,你也许可以简单地反转一个角度,但我有可用的计算,所以我使用了它)。由此,您可以计算出每个圆上直线相交的点,然后您只需绘制它,例如...
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);
}
}
真的很简单。它只是创建两条线,指向上方,在可用 space.
的中间相遇然后在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);
我的绝招:
设两个中心为C0
和C1
。使用复数,通过变换
P' = (P - C0) (C1 - C0)* / L
其中 *
表示共轭,L = |C1 - C0|
。 (如果你不喜欢复数表示法,你也可以用矩阵表示。)
现在段的可见部分从 (R0, 0)
变为 (L - R1, 0)
。对于高度 H
和宽度 2W
.
(L - R1 - H, W)
和 (L - R1 - H, -W)
通过应用逆变换,您可以获得原始坐标,
P = C0 + L P' / (C1 - C0)*.