d3js v7,当我用光标拖动圆圈时,圆圈在y坐标中向相反方向移动
d3js v7, the circle moves in the opposite direction in y coordinate when I drag the circle by the cursor
我想在 d3js 中通过拖动来移动一个圆,但是我无法让它在 y 轴方向上移动。
我使用的是d3js的v7,通过多次尝试发现拖动函数中的event.x
和event.y
是被拖动的光标在图形上的坐标。因此,我尝试用scale变换函数对这两个坐标进行变换,得到svg上显示的坐标。
这里有两个问题。
首先是event.y
得到的y坐标与实际垂直移动方向相反。例如,如果我实际上 运行 代码并将圆圈拖动到右上角,则圆圈将移动到右下角。这是在圆初始位置的 y 坐标中的线对称运动; y坐标移动的正负值取反。 event.dy
也是如此。 event.x
和 event.dx
中的 x 坐标工作正常。此外,d3.pointer(event)
未按预期工作。
第二个问题是圆圈在拖动的开始跳动。我通过查看拖动过程中执行的函数找到了原因。 event.x
和 event.y
的初始值是圆的坐标初始值,即使经过多次拖动,这导致圆在拖动时总是从圆的初始值开始移动。
我有a program that reproduces the problem.
如何解决这些问题?
谢谢。
这是代码。
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
函数引用数据中设置的值(cx
和cy
)表示新的光标坐标反映dy
和dx
来自这些值。所以在我的例子中,我将未缩放的数据值设置为 event.subject
,因此当我直接拖动到上面时,它将根据 (cx=250, cy=-50)
和 dy=-1
计算为 (cx=250, cy=-40)
。这随后反映在 event.y
中。这就是为什么我说的那个圆在拖动的时候y坐标是倒过来的原因。
正确的值设置为(cx=204, cy=150)
作为enter()
执行时数据的缩放值。当圆圈直接向上拖动时,event.y
在dy=-1
的基础上设置为cy=140
。在svg中的像素坐标中,y坐标值减小意味着向上移动,所以圆圈向上移动。此时我需要更新 d.x=event.y
以在下一次拖动时考虑到对 event.subject
值的引用。
这是给定您的代码的预期结果。
您最初使用 scale(d.y)
定位数据点,这会将您的数据值转换为像素值。当您拖动时,您获取像素值 (event.y) 并再次应用缩放 - 缩放像素值,就好像它们是数据一样。
d.y = 0 的数据值被刻度映射到height
。如果您拖动一个位于 SVG 顶部的圆圈 (y=0),那么比例尺将 return height
位于 SVG 的底部 - 因此您的反转。
解决方案是只使用 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>
我想在 d3js 中通过拖动来移动一个圆,但是我无法让它在 y 轴方向上移动。
我使用的是d3js的v7,通过多次尝试发现拖动函数中的event.x
和event.y
是被拖动的光标在图形上的坐标。因此,我尝试用scale变换函数对这两个坐标进行变换,得到svg上显示的坐标。
这里有两个问题。
首先是event.y
得到的y坐标与实际垂直移动方向相反。例如,如果我实际上 运行 代码并将圆圈拖动到右上角,则圆圈将移动到右下角。这是在圆初始位置的 y 坐标中的线对称运动; y坐标移动的正负值取反。 event.dy
也是如此。 event.x
和 event.dx
中的 x 坐标工作正常。此外,d3.pointer(event)
未按预期工作。
第二个问题是圆圈在拖动的开始跳动。我通过查看拖动过程中执行的函数找到了原因。 event.x
和 event.y
的初始值是圆的坐标初始值,即使经过多次拖动,这导致圆在拖动时总是从圆的初始值开始移动。
我有a program that reproduces the problem.
如何解决这些问题?
谢谢。
这是代码。
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
函数引用数据中设置的值(cx
和cy
)表示新的光标坐标反映dy
和dx
来自这些值。所以在我的例子中,我将未缩放的数据值设置为 event.subject
,因此当我直接拖动到上面时,它将根据 (cx=250, cy=-50)
和 dy=-1
计算为 (cx=250, cy=-40)
。这随后反映在 event.y
中。这就是为什么我说的那个圆在拖动的时候y坐标是倒过来的原因。
正确的值设置为(cx=204, cy=150)
作为enter()
执行时数据的缩放值。当圆圈直接向上拖动时,event.y
在dy=-1
的基础上设置为cy=140
。在svg中的像素坐标中,y坐标值减小意味着向上移动,所以圆圈向上移动。此时我需要更新 d.x=event.y
以在下一次拖动时考虑到对 event.subject
值的引用。
这是给定您的代码的预期结果。
您最初使用 scale(d.y)
定位数据点,这会将您的数据值转换为像素值。当您拖动时,您获取像素值 (event.y) 并再次应用缩放 - 缩放像素值,就好像它们是数据一样。
d.y = 0 的数据值被刻度映射到height
。如果您拖动一个位于 SVG 顶部的圆圈 (y=0),那么比例尺将 return height
位于 SVG 的底部 - 因此您的反转。
解决方案是只使用 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>