I am trying to move a circle by dragging it in d3js, but I can't get it to move in the y-axis direction.
I am using v7 of d3js and from several attempts I have found that the event.x and event.y in the drag function are the coordinates on the graph of the cursor being dragged. Therefore, I tried to transform these two coordinates with the scale transformation function to obtain the coordinates of the display on the svg.
There are two problems here.
The first is that the y-coordinates obtained by event.y are the opposite of the actual vertical movement. For example, if I actually run the code and drag the circle to the upper right, the circle will move to the lower right. This is a line symmetry-like movement in the y-coordinate of the circle's initial position; the positive and negative values of the y-coordinate movement are reversed. This is also true for event.dy. The x-coordinates in event.x and event.dx are working fine. Also, d3.pointer(event) does not work as intended.
The second problem is that the circle jumps at the beginning of the drag. I found the cause of this by looking within the function that is executed during the drag. The initial values of event.x and event.y are the initial values of the circle's coordinates, even after multiple drags, which causes the circle to always start moving from the initial value of the circle when it is dragged.
I have a program that reproduces the problem.
How can these problems be resolved?
Thanks.
Here is the code.
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>
Edit (Perception of coordinate calculation in the drag function)
The drag function refers to the values set in the data (cx and cy) and indicates the new coordinates of the cursor reflecting dy and dx from those values. So in my case I have the unscaled data values set as event.subject, so when I drag directly above, it will be calculated as (cx=250, cy=-40) based on (cx=250, cy=-50) and dy=-1. This is then reflected in event.y. This is the reason why the y-coordinates of the circle I was talking about during the drag were upside down.
The correct value is set as (cx=204, cy=150) as a scaled value of the data at the time of enter() execution. When the circle is dragged directly up, event.y is set to cy=140 based on dy=-1. In pixel coordinates in svg, a decrease in the y coordinate value means an upward movement, so the circle moves up. At this point I need to update d.x=event.y to take into account the reference to the value of event.subject in the next drag.