저는 Highmaps를 사용하여 데모 단순 비행 경로 ( jsfiddle )를 시작점으로 사용하여 비행 경로 차트를 만들고 있습니다 . 세계지도를 사용하도록 코드를 업데이트하면 위치 / 마커 사이의 선 경로가 과장된 곡선으로 왜곡됩니다.
데모에서 다음으로 만 수정 한 jsfiddle을 참조하십시오 .
HTML
<!-- line 5 -->
<script src="https://code.highcharts.com/mapdata/custom/world.js"></script>
자바 스크립트
// line 39
mapData: Highcharts.maps['custom/world'],
// line 47
data: Highcharts.geojson(Highcharts.maps['custom/world'], 'mapline'),
어디에 둘 사이의 차이를보기 전에 Highcharts 데모를하고 후 내 몇 가지 변화입니다 위에서 언급 한 바와 같이를 :
The paths are calculated through function pointsToPath
which uses quadratic Bézier curve Q
in SVG to curve the line drawn between markers.
// 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;
}
If I modify the function to always divide by 2
for arc points x
and y
then I get a straight line between markers:
var arcPointX = (from.x + to.x) / 2,
arcPointY = (from.y + to.y) / 2;
I'm not sure of the math to get a smaller, less exaggerated curve.
Ideally I would like for the line to be symmetrical as per an example from 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>
Using world map data, how do I calculate line paths between markers to appear with a smaller or symmetrical curve?
You just need to get your denominator closer to 2.0 as when it is 2.0 is a perfectly straight line: https://jsfiddle.net/my7bx50p/1/
So I chose 2.03 and 1.97 and that gives you much "softer" curves. Hope that helps.
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;
}
UPDATE:
I tried to focus in only on the math: https://jsfiddle.net/9gkvhfuL/1/
I think the math is now correct:
Which back to the real example: https://jsfiddle.net/my7bx50p/6/
Gives, I believe, the desired outcome :):
From the code (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;
}
UPDATE 2: (to try to explain the math and clean up the code)
Check out the Math Fiddle: https://jsfiddle.net/alexander_L/dcormfxy/53/
The solid black line between the two points is the straight line between them and it also has the corresponding slope (used in the code later). I also drew a centre point on each line. Then I drew the inverse slope as a dotted line (also used in the code) The inverse slope is by definition perpendicular to the slope and is related by invSlope = -1/slope
. From this, we are now set up to find the perpendicular points to the left or right of the centre point, that will become the centre of our symmetrical arcs. We do this by first figuring out if the slope is greater than the inverse slope or if the inverse slope is greater than the slope (absolute values). This is only necessary because when we have a perfectly horizontal or perfectly vertical line then the slope is zero and undefined respectively and then our math doesn't work. (remember slope = (y2 - y1)/(x2 - x1) so when the line is vertical y-changes but x-doesn't so x2 = x1 and then the denominator is zero and gives us undefined slope)
Let's think about line C from : {x: 40, y: 40}, to : {x: 220, y: 40}
slope = (y2 - y1)/(x2 - x1)
slope = (40 - 40)/(220 - 40)
slope = 0 / 180
slope = 0
invSlope = -1/slope
invSlope = undefined
This is why we need to have the two cases (the if else) in the code as whenever we get slope or invSlope as undefined the math is not going to work. So now, although slope is zero, it is greater than invSlope (undefined). (note SVGs are upside down compared to normal charts and how we think about them so it requires your brain to keep that in mind, otherwise, it's easy to get lost)
So now we can offset the centre point in the y-direction and then figure out how much we have to offset in the x-direction. If you had a line with slope 1, then you would offset the same in the x and the y-directions because the slope of the line is 1 (line makes a 45-degree angle with the x-axis) and so moving perpendicularly away from that line is achieved just by moving for example 5 in the x-direction and -5 in the y-direction.
Luckily, with this edge case (slope = 0), then we just move in the y-direction and the x-direction offset = 0. Look at line C in the math example and you can see what I mean, to move perpendicularly, we just move either positive or negative y-direction from the centre point. From the code:
offsetCenter = [centerPoint[0] + (offset * (1/slope)), centerPoint[1] + offset];
So as I said, we offset in the y-direction from the centerPoint and the term + (offset * (1/slope))
will be zero here because 1/slope is undefined. We can chose to offset "left" or "right" by the function's argument invertArc
which is used in this line: var offset = (invertArc ? -1 : 1) * 2 * Math.sqrt(distance);
which basically means move postive or negative direction away from the centerPoint in a magnitude equal to two times the square root of the distance between the points. I settled on two times the square root of the distance between the points because this gives us an offsetCenter of our arc that gives similarly soft curves for all lines both short and long.
Now, let's think about line A from : {x: 40, y: 40}, to : {x: 320, y: 360}
slope = (y2 - y1)/(x2 - x1)
slope = (360 - 40)/(320 - 40)
slope = 320 / 280
slope = 1.143
invSlope = -1/slope
invSlope = -0.875
Final cleaned up code and real example here 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;
}
UPDATE 3:
I figured out how to remove the need for the if else control flow / switch statement by using trigonometry instead. I hope my sketch helps explain the logic, you might also want to read some stuff (https://study.com/academy/lesson/sohcahtoa-definition-example-problems-quiz.html) etc. as I would struggle to explain briefly here (and I am already writing an essay here :) so will not explain SOH CAH TOA etc.)
This makes the core function code like this (Math only - https://jsfiddle.net/alexander_L/dcormfxy/107/) (full example - 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;
}
이 코드가 더 우아하고 깨끗하며 더 강력하고 수학적으로 유효하다고 생각합니다. :) Const & Let use와 var에 대한 팁에 대해 Ash도 감사드립니다.
이것은 또한 최종 결과를 제공합니다.
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다