您只需让分母更接近 2.0,因为 2.0 是一条完美的直线:https://jsfiddle.net/my7bx50p/1/
所以我选择了 2.03 和 1.97,这会为您提供更“柔和”的曲线。希望对您有所帮助。
function pointsToPath(from, to, invertArc) {
var arcPointX = (from.x + to.x) / (invertArc ? 2.03 : 1.97),
arcPointY = (from.y + to.y) / (invertArc ? 2.03 : 1.97);
return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY + ' ' + to.x + ' ' + to.y;
}
更新:
我试图只专注于数学:https://jsfiddle.net/9gkvhfuL/1/
我认为现在数学是正确的:
回到真实的例子:https://jsfiddle.net/my7bx50p/6/
我相信,会产生预期的结果 :):
来自代码(https://jsfiddle.net/my7bx50p/6/):
function pointsToPath(from, to, invertArc) {
var centerPoint = [ (from.x + to.x) / 2, (from.y + to.y) / 2];
var slope = (to.x - from.x) / (to.y - from.y);
var invSlope = -1 / slope;
var distance = Math.sqrt( Math.pow((to.x - from.x), 2) + Math.pow((to.y - from.y), 2) );
if (Math.abs(slope) > Math.abs(invSlope) ){
//then we should offset in the y direction
var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
var min_slope = Math.min( Math.abs(slope), Math.abs(invSlope) );
var final_slope = Math.max(min_slope, 1);
var offsetCenter = [centerPoint[0] + (offset * (1/slope)), centerPoint[1] + offset];
//console.log(centerPoint, slope, invSlope, distance);
var arcPointX = offsetCenter[0], //(from.x + to.x) / (invertArc ? 2.03 : 1.97),
arcPointY = offsetCenter[1] //(from.y + to.y) / (invertArc ? 2.03 : 1.97);
} else{ //invSlope <= slope
//then we should offset in the x direction
var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
var min_slope = Math.min( Math.abs(slope), Math.abs(invSlope) );
var final_slope = Math.max(min_slope, 1);
var offsetCenter = [centerPoint[0] + offset, centerPoint[1] + (offset * (1/invSlope))];
//console.log(centerPoint, slope, invSlope, distance);
var arcPointX = offsetCenter[0], //(from.x + to.x) / (invertArc ? 2.03 : 1.97),
arcPointY = offsetCenter[1] //(from.y + to.y) / (invertArc ? 2.03 : 1.97);
}
return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
' ' + to.x + ' ' + to.y;
}
更新 2:(尝试解释数学并清理代码)
查看数学小提琴:https://jsfiddle.net/alexander_L/dcormfxy/53/
两点之间的黑色实线是它们之间的直线,它也有相应的斜率(在后面的代码中使用)。我还在每条线上画了一个中心点。然后我将反斜率绘制为虚线(也用于代码中) 反斜率根据定义垂直于斜率并与invSlope = -1/slope 相关。由此,我们现在设置找到中心点左侧或右侧的垂直点,这将成为我们对称弧的中心。为此,我们首先确定斜率是否大于反斜率,或者反斜率是否大于斜率(绝对值)。这只是必要的,因为当我们有一条完全水平或完全垂直的线时,斜率分别为零和未定义,然后我们的数学不起作用。 (请记住斜率 = (y2 - y1)/(x2 - x1),所以当直线垂直时,y 会发生变化,但 x 不会因此 x2 = x1,然后分母为零并给出未定义的斜率)
让我们想想C行from : {x: 40, y: 40}, to : {x: 220, y: 40}
斜率 = (y2 - y1)/(x2 - x1)
斜率 = (40 - 40)/(220 - 40)
斜率 = 0 / 180
斜率 = 0
invSlope = -1/斜率
invSlope = 未定义
这就是为什么我们需要在代码中包含两种情况(if else),因为每当我们将斜率或 invSlope 设置为 undefined 时,数学就不起作用了。所以现在,虽然斜率为零,但它大于 invSlope(未定义)。 (注意 SVG 与普通图表以及我们对它们的看法是颠倒的,因此需要您的大脑牢记这一点,否则很容易迷路)
所以现在我们可以在 y 方向上偏移中心点,然后计算我们必须在 x 方向上偏移多少。如果您有一条斜率为 1 的线,那么您将在 x 和 y 方向上偏移相同,因为线的斜率为 1(线与 x 轴成 45 度角),因此垂直移动只需通过在 x 方向上移动 5 和在 y 方向上移动 -5 即可实现从该线开始。
幸运的是,在这种极端情况下(斜率 = 0),我们只需沿 y 方向移动,x 方向偏移量 = 0。看看数学示例中的 C 行,你就会明白我的意思,垂直移动,我们只是从中心点移动正或负 y 方向。来自代码:
offsetCenter = [centerPoint[0] + (offset * (1/slope)), centerPoint[1] + offset];
正如我所说,我们在 y 方向上从 centerPoint 偏移,术语 + (offset * (1/slope)) 将在此处为零,因为 1/slope 未定义。我们可以选择通过函数的参数invertArc 来偏移“左”或“右”,该参数在这一行中使用:var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance); 这基本上意味着将正向或负向远离中心点,幅度等于平方的两倍点之间距离的根。我确定了点之间距离的平方根的两倍,因为这为我们提供了弧的 offsetCenter,它为所有短线和长线提供了类似的柔和曲线。
现在,让我们考虑一下 A 行 from : {x: 40, y: 40}, to : {x: 320, y: 360}
斜率 = (y2 - y1)/(x2 - x1)
斜率 = (360 - 40)/(320 - 40)
坡度 = 320 / 280
斜率 = 1.143
invSlope = -1/斜率
invSlope = -0.875
最终清理代码和真实示例在这里https://jsfiddle.net/alexander_L/o43ka9u5/4/:
function pointsToPath(from, to, invertArc) {
var centerPoint = [ (from.x + to.x) / 2, (from.y + to.y) / 2];
var slope = (to.x - from.x) / (to.y - from.y);
var invSlope = -1 / slope;
var distance = Math.sqrt( Math.pow((to.x - from.x), 2) + Math.pow((to.y - from.y), 2) );
var arcPointX = 0;
var arcPointY = 0;
var offset = 0;
var offsetCenter = 0;
if (Math.abs(slope) > Math.abs(invSlope) ){
//then we should offset in the y direction (then calc. x-offset)
offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
offsetCenter = [centerPoint[0] + (offset * (1/slope)), centerPoint[1] + offset];
arcPointX = offsetCenter[0]
arcPointY = offsetCenter[1]
} else{ //invSlope >= slope
//then we should offset in the x direction (then calc. y-offset)
offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
offsetCenter = [centerPoint[0] + offset, centerPoint[1] + (offset * (1/invSlope))];
arcPointX = offsetCenter[0]
arcPointY = offsetCenter[1]
}
return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
' ' + to.x + ' ' + to.y;
}
更新 3:
我想出了如何通过使用三角函数来消除对 if else 控制流/switch 语句的需求。我希望我的草图有助于解释逻辑,您可能还想阅读一些东西(https://study.com/academy/lesson/sohcahtoa-definition-example-problems-quiz.html)等,因为我很难在这里简要解释(我已经在这里写了一篇文章:)所以不会解释 SOH CAH TOA等等)
这使得核心函数代码如下(仅限数学 - https://jsfiddle.net/alexander_L/dcormfxy/107/)(完整示例 - https://jsfiddle.net/alexander_L/o43ka9u5/6/):
function pointsToPath(from, to, invertArc) {
const centerPoint = [ (from.x + to.x) / 2, (from.y + to.y) / 2];
const slope = (to.y - from.y) / (to.x - from.x);
const invSlope = -1 / slope;
const distance = Math.sqrt( Math.pow((to.x - from.x), 2) + Math.pow((to.y - from.y), 2) );
const offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
const angle = Math.atan(slope);
//Math.cos(angle) = offsetY/offset;
//Math.sin(angle) = offsetX/offset;
const offsetY = Math.cos(angle)*offset;
const offsetX = Math.sin(angle)*offset;
//if slope = 0 then effectively only offset y-direction
const offsetCenter = [centerPoint[0] - offsetX, centerPoint[1] + offsetY];
const arcPointX = offsetCenter[0]
const arcPointY = offsetCenter[1]
return 'M' + from.x + ' ' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
' ' + to.x + ' ' + to.y;
}
我相信这段代码更优雅、更简洁、更健壮且在数学上有效:) 也感谢 Ash 提供的关于 Const & Let use 与 var 的提示。
这也给出了最终结果: