KonvaJS 连接正方形并正确放置线条?
KonvaJS connect squares and correct line placement?
所以我正在用 KonvaJS 和 KonvaReact 构建一个 UML 绘图工具,为此我需要用线条连接形状。我在网站上看到了有关连接对象的教程 https://konvajs.org/docs/sandbox/Connected_Objects.html。
他们使用函数 get_connecter_points
根据圆上的弧度计算直线的位置。
function getConnectorPoints(from, to) {
const dx = to.x - from.x;
const dy = to.y - from.y;
let angle = Math.atan2(-dy, dx);
const radius = 50;
return [
from.x + -radius * Math.cos(angle + Math.PI),
from.y + radius * Math.sin(angle + Math.PI),
to.x + -radius * Math.cos(angle),
to.y + radius * Math.sin(angle)
];
}
我正在尝试想出一个模拟函数,但无法想出一个好的解决方案或找到一个好的例子。正如您在图像中看到的那样,我刚刚在函数中返回了 from x 和 y 以及 to x 和 y,因此这些线将放置在每个正方形的左上角。
该函数的目标应该是将线放置在正方形一侧的中间位置,并位于正方形的正确一侧。所以当 to 正方形放在下面时,它应该出现在底部。
因此,如果有人有解决方案,我们将不胜感激。
对于矩形,数学运算比圆形复杂一些。
首先,您需要计算两个对象之间连接线的角度:
function getCenter(node) {
return {
x: node.x() + node.width() / 2,
y: node.y() + node.height() / 2
}
}
const c1 = getCenter(object1);
const c2 = getCenter(object2;
const dx = c1.x - c2.x;
const dy = c1.y - c2.y;
const angle = Math.atan2(-dy, dx);
其次,当您知道角度后,您需要一个函数,它可以找到矩形边框的一个点,您可以用它来连接另一个对象。
function getRectangleBorderPoint(radians, size, sideOffset = 0) {
const width = size.width + sideOffset * 2;
const height = size.height + sideOffset * 2;
radians %= 2 * Math.PI;
if (radians < 0) {
radians += Math.PI * 2;
}
const phi = Math.atan(height / width);
let x, y;
if (
(radians >= 2 * Math.PI - phi && radians <= 2 * Math.PI) ||
(radians >= 0 && radians <= phi)
) {
x = width / 2;
y = Math.tan(radians) * x;
} else if (radians >= phi && radians <= Math.PI - phi) {
y = height / 2;
x = y / Math.tan(radians);
} else if (radians >= Math.PI - phi && radians <= Math.PI + phi) {
x = -width / 2;
y = Math.tan(radians) * x;
} else if (radians >= Math.PI + phi && radians <= 2 * Math.PI - phi) {
y = -height / 2;
x = y / Math.tan(radians);
}
return {
x: -Math.round(x),
y: Math.round(y)
};
}
现在,您只需要为线形生成点:
function getPoints(r1, r2) {
const c1 = getCenter(r1);
const c2 = getCenter(r2);
const dx = c1.x - c2.x;
const dy = c1.y - c2.y;
const angle = Math.atan2(-dy, dx);
const startOffset = getRectangleBorderPoint(angle + Math.PI, r1.size());
const endOffset = getRectangleBorderPoint(angle, r2.size());
const start = {
x: c1.x - startOffset.x,
y: c1.y - startOffset.y
};
const end = {
x: c2.x - endOffset.x,
y: c2.y - endOffset.y
};
return [start.x, start.y, end.x, end.y]
}
function updateLine() {
const points = getPoints(rect1, rect2);
line.points(points);
}
所有这些作为演示:
function getRectangleBorderPoint(radians, size, sideOffset = 0) {
const width = size.width + sideOffset * 2;
const height = size.height + sideOffset * 2;
radians %= 2 * Math.PI;
if (radians < 0) {
radians += Math.PI * 2;
}
const phi = Math.atan(height / width);
let x, y;
if (
(radians >= 2 * Math.PI - phi && radians <= 2 * Math.PI) ||
(radians >= 0 && radians <= phi)
) {
x = width / 2;
y = Math.tan(radians) * x;
} else if (radians >= phi && radians <= Math.PI - phi) {
y = height / 2;
x = y / Math.tan(radians);
} else if (radians >= Math.PI - phi && radians <= Math.PI + phi) {
x = -width / 2;
y = Math.tan(radians) * x;
} else if (radians >= Math.PI + phi && radians <= 2 * Math.PI - phi) {
y = -height / 2;
x = y / Math.tan(radians);
}
return {
x: -Math.round(x),
y: Math.round(y)
};
}
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
const rect1 = new Konva.Rect({
x: 20,
y: 20,
width: 50,
height: 50,
fill: 'green',
draggable: true
});
layer.add(rect1);
const rect2 = new Konva.Rect({
x: 220,
y: 220,
width: 50,
height: 50,
fill: 'red',
draggable: true
});
layer.add(rect2);
const line = new Konva.Line({
stroke: 'black'
});
layer.add(line);
function getCenter(node) {
return {
x: node.x() + node.width() / 2,
y: node.y() + node.height() / 2
}
}
function getPoints(r1, r2) {
const c1 = getCenter(r1);
const c2 = getCenter(r2);
const dx = c1.x - c2.x;
const dy = c1.y - c2.y;
const angle = Math.atan2(-dy, dx);
const startOffset = getRectangleBorderPoint(angle + Math.PI, rect1.size());
const endOffset = getRectangleBorderPoint(angle, rect2.size());
const start = {
x: c1.x - startOffset.x,
y: c1.y - startOffset.y
};
const end = {
x: c2.x - endOffset.x,
y: c2.y - endOffset.y
};
return [start.x, start.y, end.x, end.y]
}
function updateLine() {
const points = getPoints(rect1, rect2);
line.points(points);
}
updateLine();
layer.on('dragmove', updateLine);
layer.draw();
<script src="https://unpkg.com/konva@^3/konva.min.js"></script>
<div id="container"></div>
最简单的方法可能就是拥有两层,一层用于连接矩形中心的线,另一层用于在线的顶部绘制矩形。
除此之外,我想到了另一种方法来解决“矩形上的点问题”,在我看来效果不错。
从这个矩形的隐式公式开始(我从here得到它,但我认为你也可以通过旋转
的坐标系并拉伸垂直和水平轴来推导它):

我们可以通过代入
和
来转换为极坐标。重写为半径的函数得到:

然后我们可以使用相同的替换将其转换回直角坐标并得到 θ 的参数函数:


那么这有什么用呢?除了我们不必担心角度落在哪个范围这一事实之外,在极坐标中工作使得旋转变得微不足道 - 它只是一个域偏移。
使用 r 的极坐标函数(为了便于表示)我们可以表示为(设 α 为我们逆时针旋转的角度):


您可以在此 desmos 可视化中使用它:https://www.desmos.com/calculator/zgi3jzb2eg
将其付诸实践(假设围绕中心旋转),我们得到:
function degreesToRadians(degrees) {
return Math.PI * degrees / 180;
}
function getPointOnRectangle(width, height, angle, rotation) {
const rot_angle = angle - rotation;
const radius = 1 / (
Math.abs(Math.cos(rot_angle) / width + Math.sin(rot_angle) / height) + Math.abs(Math.cos(rot_angle) / width - Math.sin(rot_angle) / height)
);
return {x: radius * Math.cos(angle), y: radius * Math.sin(angle)};
}
function connectRects(rectA, rectB, line, margin=0) {
const deltaX = rectB.x() - rectA.x();
const deltaY = rectB.y() - rectA.y();
const angleA = Math.atan2(deltaY, deltaX);
const angleB = Math.PI + angleA;
const rotA = Konva.angleDeg? degreesToRadians(rectA.rotation()) : rectA.rotation();
const rotB = Konva.angleDeg? degreesToRadians(rectB.rotation()) : rectB.rotation();
const relA = getPointOnRectangle(rectA.width() + margin, rectA.height() + margin, angleA, rotA);
const relB = getPointOnRectangle(rectB.width() + margin, rectB.height() + margin, angleB, rotB);
line.points([
rectA.x() + relA.x,
rectA.y() + relA.y,
rectB.x() + relB.x,
rectB.y() + relB.y,
]);
}
Codepen 演示(改编自 lavrton 的演示)在这里:https://codepen.io/creallfluharty/pen/WNEVjgW
所以我正在用 KonvaJS 和 KonvaReact 构建一个 UML 绘图工具,为此我需要用线条连接形状。我在网站上看到了有关连接对象的教程 https://konvajs.org/docs/sandbox/Connected_Objects.html。
他们使用函数 get_connecter_points
根据圆上的弧度计算直线的位置。
function getConnectorPoints(from, to) {
const dx = to.x - from.x;
const dy = to.y - from.y;
let angle = Math.atan2(-dy, dx);
const radius = 50;
return [
from.x + -radius * Math.cos(angle + Math.PI),
from.y + radius * Math.sin(angle + Math.PI),
to.x + -radius * Math.cos(angle),
to.y + radius * Math.sin(angle)
];
}
我正在尝试想出一个模拟函数,但无法想出一个好的解决方案或找到一个好的例子。正如您在图像中看到的那样,我刚刚在函数中返回了 from x 和 y 以及 to x 和 y,因此这些线将放置在每个正方形的左上角。
该函数的目标应该是将线放置在正方形一侧的中间位置,并位于正方形的正确一侧。所以当 to 正方形放在下面时,它应该出现在底部。
因此,如果有人有解决方案,我们将不胜感激。
对于矩形,数学运算比圆形复杂一些。
首先,您需要计算两个对象之间连接线的角度:
function getCenter(node) {
return {
x: node.x() + node.width() / 2,
y: node.y() + node.height() / 2
}
}
const c1 = getCenter(object1);
const c2 = getCenter(object2;
const dx = c1.x - c2.x;
const dy = c1.y - c2.y;
const angle = Math.atan2(-dy, dx);
其次,当您知道角度后,您需要一个函数,它可以找到矩形边框的一个点,您可以用它来连接另一个对象。
function getRectangleBorderPoint(radians, size, sideOffset = 0) {
const width = size.width + sideOffset * 2;
const height = size.height + sideOffset * 2;
radians %= 2 * Math.PI;
if (radians < 0) {
radians += Math.PI * 2;
}
const phi = Math.atan(height / width);
let x, y;
if (
(radians >= 2 * Math.PI - phi && radians <= 2 * Math.PI) ||
(radians >= 0 && radians <= phi)
) {
x = width / 2;
y = Math.tan(radians) * x;
} else if (radians >= phi && radians <= Math.PI - phi) {
y = height / 2;
x = y / Math.tan(radians);
} else if (radians >= Math.PI - phi && radians <= Math.PI + phi) {
x = -width / 2;
y = Math.tan(radians) * x;
} else if (radians >= Math.PI + phi && radians <= 2 * Math.PI - phi) {
y = -height / 2;
x = y / Math.tan(radians);
}
return {
x: -Math.round(x),
y: Math.round(y)
};
}
现在,您只需要为线形生成点:
function getPoints(r1, r2) {
const c1 = getCenter(r1);
const c2 = getCenter(r2);
const dx = c1.x - c2.x;
const dy = c1.y - c2.y;
const angle = Math.atan2(-dy, dx);
const startOffset = getRectangleBorderPoint(angle + Math.PI, r1.size());
const endOffset = getRectangleBorderPoint(angle, r2.size());
const start = {
x: c1.x - startOffset.x,
y: c1.y - startOffset.y
};
const end = {
x: c2.x - endOffset.x,
y: c2.y - endOffset.y
};
return [start.x, start.y, end.x, end.y]
}
function updateLine() {
const points = getPoints(rect1, rect2);
line.points(points);
}
所有这些作为演示:
function getRectangleBorderPoint(radians, size, sideOffset = 0) {
const width = size.width + sideOffset * 2;
const height = size.height + sideOffset * 2;
radians %= 2 * Math.PI;
if (radians < 0) {
radians += Math.PI * 2;
}
const phi = Math.atan(height / width);
let x, y;
if (
(radians >= 2 * Math.PI - phi && radians <= 2 * Math.PI) ||
(radians >= 0 && radians <= phi)
) {
x = width / 2;
y = Math.tan(radians) * x;
} else if (radians >= phi && radians <= Math.PI - phi) {
y = height / 2;
x = y / Math.tan(radians);
} else if (radians >= Math.PI - phi && radians <= Math.PI + phi) {
x = -width / 2;
y = Math.tan(radians) * x;
} else if (radians >= Math.PI + phi && radians <= 2 * Math.PI - phi) {
y = -height / 2;
x = y / Math.tan(radians);
}
return {
x: -Math.round(x),
y: Math.round(y)
};
}
const stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
const layer = new Konva.Layer();
stage.add(layer);
const rect1 = new Konva.Rect({
x: 20,
y: 20,
width: 50,
height: 50,
fill: 'green',
draggable: true
});
layer.add(rect1);
const rect2 = new Konva.Rect({
x: 220,
y: 220,
width: 50,
height: 50,
fill: 'red',
draggable: true
});
layer.add(rect2);
const line = new Konva.Line({
stroke: 'black'
});
layer.add(line);
function getCenter(node) {
return {
x: node.x() + node.width() / 2,
y: node.y() + node.height() / 2
}
}
function getPoints(r1, r2) {
const c1 = getCenter(r1);
const c2 = getCenter(r2);
const dx = c1.x - c2.x;
const dy = c1.y - c2.y;
const angle = Math.atan2(-dy, dx);
const startOffset = getRectangleBorderPoint(angle + Math.PI, rect1.size());
const endOffset = getRectangleBorderPoint(angle, rect2.size());
const start = {
x: c1.x - startOffset.x,
y: c1.y - startOffset.y
};
const end = {
x: c2.x - endOffset.x,
y: c2.y - endOffset.y
};
return [start.x, start.y, end.x, end.y]
}
function updateLine() {
const points = getPoints(rect1, rect2);
line.points(points);
}
updateLine();
layer.on('dragmove', updateLine);
layer.draw();
<script src="https://unpkg.com/konva@^3/konva.min.js"></script>
<div id="container"></div>
最简单的方法可能就是拥有两层,一层用于连接矩形中心的线,另一层用于在线的顶部绘制矩形。
除此之外,我想到了另一种方法来解决“矩形上的点问题”,在我看来效果不错。
从这个矩形的隐式公式开始(我从here得到它,但我认为你也可以通过旋转的坐标系并拉伸垂直和水平轴来推导它):
我们可以通过代入和
来转换为极坐标。重写为半径的函数得到:
然后我们可以使用相同的替换将其转换回直角坐标并得到 θ 的参数函数:
那么这有什么用呢?除了我们不必担心角度落在哪个范围这一事实之外,在极坐标中工作使得旋转变得微不足道 - 它只是一个域偏移。
使用 r 的极坐标函数(为了便于表示)我们可以表示为(设 α 为我们逆时针旋转的角度):
您可以在此 desmos 可视化中使用它:https://www.desmos.com/calculator/zgi3jzb2eg
将其付诸实践(假设围绕中心旋转),我们得到:
function degreesToRadians(degrees) {
return Math.PI * degrees / 180;
}
function getPointOnRectangle(width, height, angle, rotation) {
const rot_angle = angle - rotation;
const radius = 1 / (
Math.abs(Math.cos(rot_angle) / width + Math.sin(rot_angle) / height) + Math.abs(Math.cos(rot_angle) / width - Math.sin(rot_angle) / height)
);
return {x: radius * Math.cos(angle), y: radius * Math.sin(angle)};
}
function connectRects(rectA, rectB, line, margin=0) {
const deltaX = rectB.x() - rectA.x();
const deltaY = rectB.y() - rectA.y();
const angleA = Math.atan2(deltaY, deltaX);
const angleB = Math.PI + angleA;
const rotA = Konva.angleDeg? degreesToRadians(rectA.rotation()) : rectA.rotation();
const rotB = Konva.angleDeg? degreesToRadians(rectB.rotation()) : rectB.rotation();
const relA = getPointOnRectangle(rectA.width() + margin, rectA.height() + margin, angleA, rotA);
const relB = getPointOnRectangle(rectB.width() + margin, rectB.height() + margin, angleB, rotB);
line.points([
rectA.x() + relA.x,
rectA.y() + relA.y,
rectB.x() + relB.x,
rectB.y() + relB.y,
]);
}
Codepen 演示(改编自 lavrton 的演示)在这里:https://codepen.io/creallfluharty/pen/WNEVjgW