경로를 다시 추가 한 후 D3 클릭 이벤트가 작동하지 않습니다.
나는 다시 시도하고 이 D3를 사용하여 마이크 Bostock에서하지만 SVG 버전과 드래그 세계를. 드래그하는 동안 성능 문제로 인해 지구본을 다시 렌더링하고 있습니다. 여태까지는 그런대로 잘됐다. 이제 클릭 이벤트를 구현하고 싶지만 작동하지 않습니다. 여기서 문제는 다시 추가 될 수 있다고 언급됩니다. mousedown 이벤트가 잘 작동하지만 나중에 끌기를 방해합니다. mousedown 이벤트가 작동하고 클릭 이벤트가 작동하지 않는 이유는 무엇입니까? 이를 해결하기 위해 코드를 재구성하는 힌트는 높이 평가됩니다.
더 나은 이해를 위해 Fiddle을 만들었습니다 : Fiddle
추신. 나는 프로그래밍과 D3에 익숙하지 않으므로 너무 가혹하지 마십시오 :)
HTML :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>D3</title>
</head>
<body>
<div id="world"></div>
<script src="https://d3js.org/d3.v6.min.js"></script>
<script src="https://unpkg.com/topojson-client@2"></script>
</body>
</html>
JS :
let width, height
height = 150
width = 150
const projection = d3.geoOrthographic()
.scale((height - 10) / 2)
.translate([100, height / 2])
.precision(0);
let path = d3.geoPath().projection(projection)
const svg = d3.select("#world")
.append("svg")
const g = svg.append("g")
d3.json("https://cdn.jsdelivr.net/npm/world-atlas@2/countries-110m.json").then(data => {
let data1 = data
renderGlobe(data1);
})
function renderGlobe(world){
g.call(drag(projection)
.on("drag.render", ()=>render(world, true))
.on("end.render", ()=>render(world, false) ))
.call( () => render(world, false))
}
function render(world, x){
if(x){
variable = "land"
world = topojson.feature(world, world.objects.land).features;
}
else{
variable = "countries"
world = topojson.feature(world, world.objects.countries).features;
}
g.selectAll("path").remove()
g.selectAll(`${variable}`) .data(world) .enter().append("path") .attr("class", `${variable}`)
.attr("d", path)
// This click event doesn't work
.on("click",()=>console.log("Do something"))
// But mousedown event works
.on("mousedown",()=>console.log("Mousedown event works"))
}
function drag(projection){
var LonLatStart, eulerStart
function dragstarted(event){
LonLatStart = projection.invert(d3.pointer(event))
eulerStart = projection.rotate()
}
var LonLatEnd, eulerEnd
function dragged(event){
LonLatEnd = projection.rotate(eulerStart).invert(d3.pointer(event))
eulerEnd = getEulerAngles(LonLatStart, eulerStart, LonLatEnd)
projection.rotate(eulerEnd)
refresh()
}
return drag = d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
}
function refresh(){
svg.selectAll("path").attr("d", path)
}
// Dragging Math
let cos = Math.cos,
acos = Math.acos,
sin = Math.sin,
asin = Math.asin,
atan2 = Math.atan2,
sqrt = Math.sqrt,
min = Math.min,
max = Math.max,
PI = Math.PI,
radians = PI / 180,
degrees = 180 / PI;
// a: original vector, b: ending vector
function crossProduct(a, b){
return [
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0]
]
}
function dotProduct(a, b){
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
function LengthOfVector(c){
return sqrt(c[0] * c[0] + c[1] * c[1] + c[2] * c[2])
}
function quaternionEulerFormula(a, b){
let rotationAxis = crossProduct(a,b) , normalizationFactor = sqrt(dotProduct(rotationAxis,rotationAxis))
if (!normalizationFactor) return [1, 0, 0, 0]
let theta = acos(max(-1, min(1, dotProduct(a, b))))
return [
cos(theta / 2),
sin(theta / 2) * rotationAxis[2] / normalizationFactor,
- sin(theta / 2) * rotationAxis[1] / normalizationFactor,
sin(theta / 2) * rotationAxis[0] / normalizationFactor
]
}
// returns unit quaternion from euler angles [λ, φ, γ]
function unitQuaternion(d){
var lambda = d[0] / 2 * radians, cosLambda = cos(lambda), sinLambda = sin(lambda),
phi = d[1] / 2 * radians, cosPhi = cos(phi), sinPhi = sin(phi),
gamma = d[2] / 2 * radians, cosGamma = cos(gamma), sinGamma = sin(gamma)
return [
cosLambda * cosPhi * cosGamma + sinLambda * sinPhi * sinGamma,
sinLambda * cosPhi * cosGamma - cosLambda * sinPhi * sinGamma,
cosLambda * sinPhi * cosGamma + sinLambda * cosPhi * sinGamma,
cosLambda * cosPhi * sinGamma - sinLambda * sinPhi * cosGamma,
]
}
// quaternion multiplication, returns another quaternion which represents the rotation
function quaternionMultiplication(q0 , q1){
return [
q0[0] * q1[0] - q0[1] * q1[1] - q0[2] * q1[2] - q0[3] * q1[3],
q0[0] * q1[1] + q0[1] * q1[0] + q0[2] * q1[3] - q0[3] * q1[2],
q0[0] * q1[2] - q0[1] * q1[3] + q0[2] * q1[0] + q0[3] * q1[1],
q0[0] * q1[3] + q0[1] * q1[2] - q0[2] * q1[1] + q0[3] * q1[0]
]
}
// converts quaternion to euler angles
function quaternion2eulerAngles(q){
return [
atan2(2 * (q[0] * q[1] + q[2] * q[3]), 1 - 2 * (q[1] * q[1] + q[2] * q[2])) * degrees,
//asin(2 * (q[0] * q[2] - q[3] * q[1])) * degrees,
asin(max(-1, min(1, 2 * (q[0] * q[2] - q[3] * q[1])))) * degrees,
atan2(2 * (q[0] * q[3] + q[1] * q[2]), 1 - 2 * (q[2] * q[2] + q[3] * q[3])) * degrees
]
}
// converts long, lat to cartesian coordinates x,y,z
function lonlat2cartesian(e) {
let l = e[0] * radians, p = e[1] * radians, cp = cos(p);
return [cp * cos(l), cp * sin(l), sin(p)];
};
function getEulerAngles(positionLonLatStart, eulerAnglesStart, positionLonLatEnd){
let v0 = lonlat2cartesian(positionLonLatStart)
let v1 = lonlat2cartesian(positionLonLatEnd)
let quaternionEnd = quaternionMultiplication(unitQuaternion(eulerAnglesStart), quaternionEulerFormula(v0,v1))
return quaternion2eulerAngles(quaternionEnd)
}
답변
지금까지 본 그대로 클릭을 트리거하지 않습니다. 이것은 클릭이 마우스를 내리거나 올리는 것을 모두 포함하기 때문입니다. 이들은 드래그 동작과 상호 작용하며 각각 시작 및 종료 이벤트를 트리거합니다.
귀하의 경우에서 일어나는 일은 mousedown이 드래그를 트리거하고 경로에 추가 한 mousedown 리스너입니다. 그런 다음 마우스를 올리면 드래그 이벤트 리스너가 먼저 실행되어 모든 경로 및 관련 리스너를 제거합니다. render 함수는 이벤트를 등록하기에는 너무 늦게 사실 후에 새로운 경로를 추가합니다.
여러 가지 해결책이 있지만 가장 쉬운 방법은 드래그 엔드 리스너를 제거하고 실제로 드래그가 발생할 때만 교체하는 것입니다 (시작 이벤트가 아닌 드래그 이벤트에서).
function renderGlobe(world){
g.call(drag(projection)
.on("drag.render", function(event) {
render(world, true)
event.on("end.render",()=>render(world,false))
}))
.call( () => render(world, false))
}
이 event.on()
메서드를 사용하면 리스너가 현재 제스처에만 적용될 수 있습니다. 드래그 리스너는 mousedown과 mouseup 사이에 mousemove가있는 경우에만 트리거되므로 간단한 클릭 만있는 경우이 최종 리스너는 사용되지 않습니다.
여기 갈래의 바이올린이 있습니다.
이 문제에 대한 많은 대체 솔루션이 있습니다. 여기에있는 솔루션은 간단하지만 클릭하는 동안 움직임에 약간 민감 할 수 있습니다. 다음은 mouseup 관련 이벤트 (클릭 및 드래그 종료)를 차별화하는 다른 접근 방식에 대한 가능한 근거입니다.