Peristiwa D3 Klik tidak berfungsi setelah jalur penambahan ulang
Saya mencoba membuat ulang globe yang dapat diseret ini dari Mike Bostock menggunakan D3 tetapi sebagai versi svg. Karena masalah kinerja saat menyeret saya merender ulang globe. Sejauh ini bagus. Sekarang saya ingin menerapkan acara klik tetapi tidak berhasil. Di sini disebutkan bahwa masalahnya mungkin pada penambahan kembali. Acara mousedown berfungsi dengan baik tetapi nantinya akan mengganggu penarikan. Mengapa acara mousedown berfungsi dan acara klik tidak? Petunjuk untuk merestrukturisasi kode untuk menyelesaikan masalah ini sangat kami hargai.
Saya membuat Fiddle untuk pemahaman yang lebih baik: Fiddle
PS. Saya baru mengenal pemrograman dan D3, jadi tolong jangan terlalu kasar :)
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)
}
Jawaban
Anda tidak akan pernah memicu klik seperti sekarang, yang telah Anda lihat. Ini karena klik melibatkan mouse ke bawah dan mouse ke atas. Ini berinteraksi dengan perilaku tarik, dengan masing-masing memicu kejadian awal dan akhir.
Apa yang terjadi dalam kasus Anda adalah mousedown memicu tarikan dan pendengar mousedown yang telah Anda tambahkan ke jalur. Kemudian pada mouse ke atas, pemroses peristiwa tarik akan diaktifkan terlebih dahulu, menghapus semua jalur dan pemroses terkait. Fungsi render menambahkan jalur baru setelah fakta, terlambat untuk mendaftarkan acara.
Ada sejumlah solusi, tetapi mungkin yang paling mudah adalah menghapus listener drag end yang Anda miliki dan menggantinya hanya jika drag benar-benar terjadi (bukan di event start, di event drag):
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))
}
The event.on()
Metode memungkinkan pendengar untuk diterapkan hanya untuk gerakan saat ini. Pemroses seret hanya terpicu jika ada gerakan mouse antara mousedown dan mouseup, jadi pemroses akhir ini tidak akan digunakan jika hanya ada satu klik.
Ini biola bercabang dua .
Ada cukup banyak solusi alternatif untuk masalah ini, yang di sini, walaupun sederhana, mungkin sedikit sensitif terhadap pergerakan selama klik. Berikut adalah kemungkinan dasar untuk pendekatan alternatif untuk membedakan peristiwa terkait mouseup (klik dan seret akhir).