D3 Click olayı yolu yeniden ekledikten sonra çalışmıyor
Bu sürüklenebilir dünyayı Mike Bostock'tan D3 kullanarak ama bir svg versiyonu olarak yeniden yaratmaya çalışıyorum . Sürüklerken ortaya çıkan performans sorunları nedeniyle yerküreyi yeniden oluşturuyorum. Çok uzak çok iyi. Şimdi bir tıklama etkinliği uygulamak istiyorum ama işe yaramıyor. Burada sorunun yeniden ekleme olabileceğinden bahsediliyor. Bir fare indirme olayı iyi çalışıyor, ancak daha sonra sürüklemeyi engelliyor. Fare indirme olayı neden çalışıyor ve tıklama olayı neden çalışmıyor? Bunu çözmek için kodu yeniden yapılandırmak için ipuçları çok takdir edilmektedir.
Daha iyi anlamak için bir Fiddle yarattım: Fiddle
PS. Programlama ve D3 konusunda yeniyim, bu yüzden lütfen çok sert olmayın :)
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)
}
Yanıtlar
Tıklamayı asla şu anda olduğu gibi tetiklemeyeceksiniz. Bunun nedeni, bir tıklamanın hem fareyi aşağı hem de yukarı kaldırmayı içermesidir. Bunlar, sürükleme davranışıyla etkileşime girer ve her biri sırasıyla başlangıç ve bitiş olaylarını tetikler.
Sizin durumunuzda olan şey, fare indirme işleminin yollara eklediğiniz sürüklemeyi ve fare indirme dinleyicisini tetiklemesidir. Daha sonra fareyi basılı tuttuğunuzda, sürükleme olayı dinleyicisi ilk olarak tetiklenir ve tüm yolları ve ilişkili dinleyicileri kaldırır. Render işlevi olayın ardından yeni yollar ekler, olayı kaydetmek için çok geç.
Birkaç çözüm vardır, ancak belki de en kolayı, sahip olduğunuz sürükleme bitiş dinleyicisini kaldırmak ve onu yalnızca bir sürükleme gerçekten gerçekleştiğinde değiştirmektir (başlangıç olayında, sürükleme olayında değil):
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()
Yöntem, dinleyici yalnızca geçerli hareket için uygulanmasına olanak tanır. Sürükleme dinleyicisi yalnızca fare aşağı ve fareyi yukarı çekme arasında fare hareketi varsa tetiklenir, bu nedenle yalnızca basit bir tıklama varsa bu son dinleyici kullanılmaz.
İşte çatallı bir keman .
Bu soruna çok sayıda alternatif çözüm vardır, buradaki çözüm basit olsa da, tıklama sırasında harekete biraz duyarlı olabilir. İşte 'mouseup ilgili olayları ayırt alternatif bir yaklaşım olası bir dayanağı s (tıkla ve sürükle sonu).