[英]Draw arrow at the end of line drawn between two markers on the jvector map
[英]Draw small symmetrical arc line path between two markers on world map in Highmaps
我正在使用 Highmaps 创建一个飞行路径图,使用他们的演示简单飞行路线( jsfiddle ) 作为起点。 当我更新代码以使用世界地图时,位置/标记之间的线路径会因夸张的曲线而扭曲。
请参阅我的 jsfiddle ,其中我仅将演示修改为以下内容:
HTML
<!-- line 5 -->
<script src="https://code.highcharts.com/mapdata/custom/world.js"></script>
JavaScript
// line 39
mapData: Highcharts.maps['custom/world'],
// line 47
data: Highcharts.geojson(Highcharts.maps['custom/world'], 'mapline'),
查看两者之间的区别,其中之前是 Highcharts 演示,之后是我上面提到的一些更改:
路径是通过函数pointsToPath
计算的,该函数使用 SVG 中的二次贝塞尔曲线Q
来弯曲标记之间绘制的线。
// Function to return an SVG path between two points, with an arc
function pointsToPath(from, to, invertArc) {
var arcPointX = (from.x + to.x) / (invertArc ? 2.4 : 1.6),
arcPointY = (from.y + to.y) / (invertArc ? 2.4 : 1.6);
return 'M' + from.x + ',' + from.y + 'Q' + arcPointX + ' ' + arcPointY +
',' + to.x + ' ' + to.y;
}
如果我修改函数以始终将弧点x
和y
除以2
,那么我会在标记之间得到一条直线:
var arcPointX = (from.x + to.x) / 2,
arcPointY = (from.y + to.y) / 2;
我不确定获得更小、更不夸张的曲线的数学方法。
理想情况下,我希望根据MDN-Paths 中的示例,该线是对称的:
<svg width="190" height="160" xmlns="http://www.w3.org/2000/svg"> <path d="M 10 80 Q 95 10 180 80" stroke="black" fill="transparent"/> </svg>
使用世界地图数据,如何计算标记之间的线路径以显示更小或对称的曲线?
你只需要让你的分母更接近 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 未定义时,数学就不会起作用。 所以现在,虽然斜率为零,但它大于 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);
这基本上意味着从 centerPoint 向正或负方向移动,其幅度等于点之间距离的平方根的两倍。 我选择了点之间距离平方根的两倍,因为这为我们提供了弧的偏移中心,它为所有短线和长线提供了类似的软曲线。
现在,让我们考虑from : {x: 40, y: 40}, to : {x: 320, y: 360}
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 的提示。
这也给出了最终结果:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.