d3js v7,当我通过光标拖动圆时,圆在 y 坐标中以相反的方向移动



我试图通过在 d3js 中拖动圆来移动圆,但我无法让它沿 y 轴方向移动。

我使用的是 d7js 的 v3,通过几次尝试,我发现拖动函数中的event.xevent.y是被拖动光标图形上的坐标。因此,我尝试使用比例变换函数转换这两个坐标,以获得 svg 上显示的坐标。

这里有两个问题。
首先是event.y得到的y坐标与实际垂直运动相反。例如,如果我实际运行代码并将圆圈拖到右上角,圆圈将移动到右下角。这是圆初始位置的 y 坐标中的线对称运动;Y 坐标移动的正值和负值颠倒。对于event.dy也是如此。event.xevent.dx中的 x 坐标工作正常。此外,d3.pointer(event)无法按预期工作。

第二个问题是圆在阻力开始时跳跃。我通过查看在拖动期间执行的函数找到了原因。event.xevent.y的初始值是圆坐标的初始值,即使在多次拖动后也是如此,这会导致圆在拖动时始终从圆的初始值开始移动。

我有一个重现该问题的程序。
如何解决这些问题?

谢谢。
这是代码。

var dataset = [
{x: 5, y: 20, name: "one"},
{x: 100, y: 33, name: "two"},
{x: 250, y: 50, name: "three"},
{x: 480, y: 90, name: "four"},
];
const width = 400;
const height = 300;
const margin = { "top": 30, "bottom": 60, "right": 30, "left": 60 };
const svgWidth = width + margin.left + margin.right;
const svgHeight = height + margin.top + margin.bottom;
var svg = d3.select("#graph-area").append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("border-radius", "20px 20px 20px 20px")
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
var xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d.x; }) + 10])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d.y; }) + 10])
.range([height, 0]);
var axisx = d3.axisBottom(xScale).ticks(5);
var axisy = d3.axisLeft(yScale).ticks(5);
var x = svg.append("g")
.attr("transform", "translate(" + 0 + "," + height + ")")
.call(axisx)
.attr("class", "x_axis")
x.append("text")
.attr("x", (width - margin.left - margin.right) / 2 + margin.left)
.attr("y", 35)
.attr("text-anchor", "middle")
.attr("font-size", "10pt")
.attr("font-weight", "bold")
.text("X Label")
.attr("class", "x_axis")
var y = svg.append("g")
.call(axisy)
.attr("class", "y_axis")
y.append("text")
.attr("x", -(height - margin.top - margin.bottom) / 2 - margin.top)
.attr("y", -35)
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("font-size", "10pt")
.text("Y Label")
.attr("class", "y_axis")
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width )
.attr("height", height )
.attr("x", 0) 
.attr("y", 0);

var circle = svg.append("g")
.attr("id", "scatterplot")
.attr("clip-path", "url(#clip)");

circle.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("fill", "steelblue")
.attr("r", 20)
.on('mouseover',function(elem, data) {
//console.log("over data name: " + data.name);
})
.on('mouseout',function (elem, data) {
//console.log("out data name: " + data.name);
})
.on("mousedown", function(elem, data){
console.log("down data name: " + data.name);
})
.on("mouseup", function(elem, data){
console.log("up data name: " + data.name);
})
.call(d3.drag().on("drag", circleDragged).on("start", circleStartDrag))
function circleStartDrag(event){
console.log("start drag");
}
function circleDragged(event){
d3.select(this).attr('cx', xScale(event.x)).attr('cy', yScale(event.y));
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Scatter Plot</title>
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>

<div id="graph-area" stype="position: relative;"></div

</body>
</html>

编辑(拖动函数中对坐标计算的感知)

drag函数是指数据中设置的值(cxcy),并指示光标的新坐标,以反映这些值的dydx。所以就我而言,我将未缩放的数据值设置为event.subject,因此当我直接在上方拖动时,它将根据(cx=250, cy=-50)dy=-1计算为(cx=250, cy=-40)。然后反映在event.y中。这就是为什么我在拖曳过程中所说的圆的y坐标是颠倒的。

正确的值设置为(cx=204, cy=150)执行时数据的缩放值enter()。当圆圈直接向上拖动时,event.y设置为基于dy=-1cy=140。在 svg 中的像素坐标中,y 坐标值的减小意味着向上移动,因此圆向上移动。此时,我需要更新d.x=event.y以考虑在下一次拖动中对event.subject值的引用。

这是给定代码的预期结果。

您最初使用scale(d.y)定位数据点,这会将数据值转换为像素值。拖动时,您将获取像素值 (event.y) 并再次应用缩放 - 缩放像素值,就好像它们是数据一样。

d.y = 0 的数据值按刻度映射到height。如果您拖动位于 SVG 顶部的圆圈 (y=0),则比例将返回位于 SVG 底部height- 因此您的反转。

解决方案是仅使用 event.y/event.x 值,因为这些值已经表示像素值 - 无需缩放它们。如果要将像素值转换为数据值,则可以使用 scale.invert() - 但这仅在更新数据时有用,而不是每个圆圈的像素坐标。

从拖动中删除缩放功能揭示了最后一个问题 - 当拖动随鼠标移动时,您仍然可以在拖动开始时跳转:拖动主题(被拖动事物的参考坐标)默认情况下是基准的 x,y 属性。由于这表示基准面中的数据值,而不是缩放的像素值,因此您可以获得跳跃。最快的解决方案是将基准面的 x,y 属性重新定义为缩放的像素值,并在拖动时更新这些值。

var dataset = [
{x: 5, y: 20, name: "one"},
{x: 100, y: 33, name: "two"},
{x: 250, y: 50, name: "three"},
{x: 480, y: 90, name: "four"},
];
const width = 400;
const height = 300;
const margin = { "top": 30, "bottom": 60, "right": 30, "left": 60 };
const svgWidth = width + margin.left + margin.right;
const svgHeight = height + margin.top + margin.bottom;
var svg = d3.select("#graph-area").append("svg")
.attr("width", svgWidth)
.attr("height", svgHeight)
.style("border-radius", "20px 20px 20px 20px")
.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);
var xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d.x; }) + 10])
.range([0, width]);
var yScale = d3.scaleLinear()
.domain([0, d3.max(dataset, function(d) { return d.y; }) + 10])
.range([height, 0]);
var axisx = d3.axisBottom(xScale).ticks(5);
var axisy = d3.axisLeft(yScale).ticks(5);
var x = svg.append("g")
.attr("transform", "translate(" + 0 + "," + height + ")")
.call(axisx)
.attr("class", "x_axis")
x.append("text")
.attr("x", (width - margin.left - margin.right) / 2 + margin.left)
.attr("y", 35)
.attr("text-anchor", "middle")
.attr("font-size", "10pt")
.attr("font-weight", "bold")
.text("X Label")
.attr("class", "x_axis")
var y = svg.append("g")
.call(axisy)
.attr("class", "y_axis")
y.append("text")
.attr("x", -(height - margin.top - margin.bottom) / 2 - margin.top)
.attr("y", -35)
.attr("transform", "rotate(-90)")
.attr("text-anchor", "middle")
.attr("font-weight", "bold")
.attr("font-size", "10pt")
.text("Y Label")
.attr("class", "y_axis")
var clip = svg.append("defs").append("svg:clipPath")
.attr("id", "clip")
.append("svg:rect")
.attr("width", width )
.attr("height", height )
.attr("x", 0) 
.attr("y", 0);

var circle = svg.append("g")
.attr("id", "scatterplot")
.attr("clip-path", "url(#clip)");

circle.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) { return d.x = xScale(d.x); })
.attr("cy", function(d) { return d.y = yScale(d.y); })
.attr("fill", "steelblue")
.attr("r", 20)
.on('mouseover',function(elem, data) {
//console.log("over data name: " + data.name);
})
.on('mouseout',function (elem, data) {
//console.log("out data name: " + data.name);
})
.on("mousedown", function(elem, data){
console.log("down data name: " + data.name);
})
.on("mouseup", function(elem, data){
console.log("up data name: " + data.name);
})
.call(d3.drag().on("drag", circleDragged).on("start", circleStartDrag))
function circleStartDrag(event){
console.log("start drag");
}
function circleDragged(event){
d3.select(this).attr('cx', d=>d.x=event.x).attr('cy', d=>d.y=event.y);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>D3 Scatter Plot</title>
<script src="https://code.jquery.com/jquery-3.6.0.slim.min.js" integrity="sha256-u7e5khyithlIdTpu22PHhENmPcRdFiHRjhAuHcs05RI=" crossorigin="anonymous"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>

<div id="graph-area" stype="position: relative;"></div

</body>
</html>

最新更新