Contrasting actions observed when employing drag functionality with arrays of numbers versus arrays of objects

Being a newcomer to D3 and JavaScript, I'm hoping someone can help me clarify this simple point.

I am creating a scatter graph with draggable points using code that closely resembles the solution provided in this Stack Overflow question. When I construct a dataset as an array of [x, y] pairs and reference them as d[0] and d[1], everything functions as expected. However, if I use an array of objects with attributes x and y, referring to them as d.x and d.y, the plot looks the same but the dragging behavior does not work properly – when a point is clicked, it moves below the x-axis.

The following code performs correctly:

<!DOCTYPE html>
<svg width="500" height="350"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

    let svg = d3.select("svg"),
        margin = {top: 20, right: 20, bottom: 30, left: 50},
        width = +svg.attr("width") - margin.left - margin.right,
        height = +svg.attr("height") - margin.top - margin.bottom;
    //Create fake data as an array of [x, y] pairs
    let m=3.0, c=15.0;
    let points = d3.range(1, 10).map(function(i) {
        let x=i * width / 10;
        let noise=Math.random()*500;
        let y=m*x+c + noise;
        return [x, y];
    });





    let x = d3.scaleLinear()
        .rangeRound([0, width]);

    let y = d3.scaleLinear()
        .rangeRound([height, 0]);

    let xAxis = d3.axisBottom(x),
        yAxis = d3.axisLeft(y);

    let line = d3.line()
        .x(function(d) { return x(d[0]); })
        .y(function(d) { return y(d[1]); });

    let drag = d3.drag()
        .on('start', dragstarted)
        .on('drag', dragged)
        .on('end', dragended);

    svg.append('rect')
        .attr('class', 'zoom')
        .attr('cursor', 'move')
        .attr('fill', 'none')
        .attr('pointer-events', 'all')
        .attr('width', width)
        .attr('height', height)
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')

    let focus = svg.append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    x.domain(d3.extent(points, function(d) { return d[0]; }));
    y.domain(d3.extent(points, function(d) { return d[1]; }));

    focus.append("path")
        .datum(points)
        .attr("fill", "none")
        .attr("stroke", "steelblue")
        .attr("stroke-linejoin", "round")
        .attr("stroke-linecap", "round")
        .attr("stroke-width", 1.5)
        .attr("d", line);

    focus.selectAll('circle')
        .data(points)
        .enter()
        .append('circle')
        .attr('r', 5.0)
        .attr('cx', function(d) { return x(d[0]);  })
        .attr('cy', function(d) { return y(d[1]); })
        .style('cursor', 'pointer')
        .style('fill', 'steelblue');

    focus.selectAll('circle')
        .call(drag);

    focus.append('g')
        .attr('class', 'axis axis--x')
        .attr('transform', 'translate(0,' + height + ')')
        .call(xAxis);

    focus.append('g')
        .attr('class', 'axis axis--y')
        .call(yAxis);

    function dragstarted(d) {
        d3.select(this).raise().classed('active', true);
    }

    function dragged(d) {
        d[0] = x.invert(d3.event.x);
        d[1] = y.invert(d3.event.y);
        d3.select(this)
            .attr('cx', x(d[0]))
            .attr('cy', y(d[1]))
        focus.select('path').attr('d', line);
    }

    function dragended(d) {
        d3.select(this).classed('active', false);
    }

</script>

However, this code exhibits strange behavior when dragging the points:

<!DOCTYPE html>
<svg width="500" height="350"></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script>

    let svg = d3.select("svg"),
        margin = {top: 20, right: 20, bottom: 30, left: 50},
        width = +svg.attr("width") - margin.left - margin.right,
        height = +svg.attr("height") - margin.top - margin.bottom;
    //Generate data as an array of objects, each with attributes x and y
    let m=3.0, c=15.0;
    let points = d3.range(1, 10).map(function(i) {
        let x_val=i * width / 10;
        let noise=Math.random()*500;
        let y_val=m*x_val+c + noise;
        return {
            x:x_val,
            y:y_val

        };
    });





    let x = d3.scaleLinear()
        .rangeRound([0, width]);

    let y = d3.scaleLinear()
        .rangeRound([height, 0]);

    let xAxis = d3.axisBottom(x),
        yAxis = d3.axisLeft(y);

    let line = d3.line()
        .x(function(d) { return x(d.x); })
        .y(function(d) { return y(d.y); });

    let drag = d3.drag()
        .on('start', dragstarted)
        .on('drag', dragged)
        .on('end', dragended);

    svg.append('rect')
        .attr('class', 'zoom')
        .attr('cursor', 'move')
        .attr('fill', 'none')
        .attr('pointer-events', 'all')
        .attr('width', width)
        .attr('height', height)
        .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');

    let focus = svg.append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    x.domain(d3.extent(points, function(d) { return d.x; }));
    y.domain(d3.extent(points, function(d) { return d.y; }));

    focus.append("path")
        .datum(points)
        .attr("fill", "none")
        .attr("stroke", "steelblue")
        .attr("stroke-linejoin", "round")
        .attr("stroke-linecap", "round")
        .attr("stroke-width", 1.5)
        .attr("d", line);

    focus.selectAll('circle')
        .data(points)
        .enter()
        .append('circle')
        .attr('r', 5.0)
        .attr('cx', function(d) { return x(d.x);  })
        .attr('cy', function(d) { return y(d.y); })
        .style('cursor', 'pointer')
        .style('fill', 'steelblue');

    focus.selectAll('circle')
        .call(drag);

    focus.append('g')
        .attr('class', 'axis axis--x')
        .attr('transform', 'translate(0,' + height + ')')
        .call(xAxis);

    focus.append('g')
        .attr('class', 'axis axis--y')
        .call(yAxis);

    function dragstarted(d) {
        d3.select(this).raise().classed('active', true);
    }

    function dragged(d) {
        d.x = x.invert(d3.event.x);
        d.y = y.invert(d3.event.y);
        d3.select(this)
            .attr('cx', x(d.x))
            .attr('cy', y(d.y));
        focus.select('path').attr('d', line);
    }

    function dragended(d) {
        d3.select(this).classed('active', false);
    }

</script>

If anyone could shed light on why these two versions produce different outcomes, despite only changing the data structure, I would appreciate your explanation! Thank you!

Answer №1

The x and y properties within the data seem to be causing confusion with the default subject accessor. To resolve this issue, you can explicitly define it as follows:

    let drag = d3.drag()
        .on('start', dragstarted)
        .on('drag', dragged)
        .subject(function(d){ return {x: x(d.x), y: y(d.y)} })
        .on('end', dragended);

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

What is the best method to retrieve a nested JSON property that is deeply embedded within

I am facing an issue with storing a HEX color code obtained from an API in a Vue.js app. The object app stores the color code, for example: const app = {"theme":"{\"color\":\"#186DFFF0\"}"}. However, when I try to access the color prope ...

Tips for setting up listeners across multiple modules using socket.io

Last year, I created a multiplayer game using node.js and socket.io. Now, as part of my efforts to enhance the game, I am working on breaking down the code into modules. Currently, I am utilizing expressjs 4.4 along with socket.io 1.0. One challenge I enco ...

Is it advisable to use type="text/plain" for JavaScript?

I recently came across instructions on how to implement a particular feature out of curiosity. I found it interesting but was puzzled when they mentioned that in order for it to function properly, certain steps needed to be followed: You must identify any ...

Continuous loop of Vue countdown timer

Hey there! I'm currently working on implementing a countdown timer, but I keep encountering a console error that says 'You may have an infinite update loop in a component render function.' It seems like something needs to be adjusted. expor ...

Tips for effectively utilizing MeteorPad: (Note: ensure to use at home instead of at work due to potential firewall or proxy issues)

UPDATE: It's possible that the issue is related to a firewall or proxy. MeteorPad doesn't work at my workplace, but it works fine at home. I've been attempting to use MeteorPad () but I'm encountering some difficulties. The bottom are ...

Converting a string array to an array object in JavaScript: A step-by-step guide

My task involves extracting an array from a string and manipulating its objects. var arrayString = "[{'name': 'Ruwaida Abdo'}, {'name': 'Najlaa Saadi'}]"; This scenario arises when working with a JSON file where ce ...

Unexpected token error occurs when making cross-domain AJAX requests to the server and receiving a JSON object

I have set up an express server to handle get requests to specific url endpoints. When responding to these requests, I am sending back data in JSON format to enable making Ajax calls and retrieving data from the page. To allow for cross-domain requests, I ...

Using API calls to update component state in React-Redux

I am currently working on setting up a React application where clicking on a map marker in one component triggers the re-rendering of another component on the page. This new component should display data retrieved from a database and update the URL accordi ...

Start up a server using Angular along with Node.js and Express framework

I am encountering an issue with configuring Express as a server in my Angular application. The app loads without any issues when accessing the HOME route, but when trying to access another route, I receive an error message: Cannot GET / This is how I hav ...

Leveraging web workers for asynchronous API calls

Trying to find an efficient method for utilizing web workers to handle api calls, my current approach involves the following on the client side: - worker-client.js export const workerFetch = (method = "get", url = "/", data = {}) => new Promise((res ...

How to leverage onpopstate in Vuejs without relying on vue-router

I am currently utilizing vue.js in conjunction with laravel, specifically incorporating a vue component within a laravel blade file. My aim is to trigger a page reload upon pressing the back navigation button to return to the previous page. The code I have ...

Create an Ajax request function to execute a PHP function, for those just starting out

I came across a JS-Jquery file that almost meets my needs. It currently calls a PHP function when a checkbox is clicked, and now I want to add another checkbox that will call a different PHP function. My initial attempt was to copy the existing function a ...

What impact does incorporating a new test case have on code coverage in Jest?

I recently encountered an unexpected situation while working on a Jest test suite. Despite not making any changes to the codebase, I simply added a new test. Originally: mylibrary.js | 95.65 | 89.66 | 100 | 95.65 | 54-56,84 ------------ ...

Currently in the process of developing an electron application, I encountered an Uncaught Exception error stating: "TypeError: $.ajax is not

I'm currently working on an electron app that interacts with an API endpoint and displays the response on the page. Due to electron's separation of main process and renderer process, I'm using a preload script to facilitate communication bet ...

Upon initiating npm start in my React application, an error was encountered: internal/modules/cjs/loader.js:834

Upon downloading my React course project, I proceeded to install dependencies and run npm start. To my dismay, I encountered the following error: PS C:\Users\Marcin & Joanna\Desktop\react-frontend-01-starting-setup> npm start &g ...

Showing error messages in Angular when a form is submitted and found to be invalid

My form currently displays an error message under each field if left empty or invalid. However, I want to customize the behavior of the submit button when the form is invalid. <form #projectForm="ngForm" (ngSubmit)="onSubmit()"> ...

The attribute 'checked' is not a valid property for the 'TElement' type

Learning about typescript is new to me. I have a functional prototype in fiddle, where there are no errors if I use this code. http://jsfiddle.net/61ufvtpj/2/ But in typescript, when I utilize this line - if(this.checked){ it presents an error [ts] Pro ...

Error: The reset function cannot be executed on $(...)[0]

Purpose My aim is to clear a form once it has been successfully submitted. Problem Upon submitting the form, I encounter the error message in my console: Uncaught TypeError: $(...)[0].reset is not a function When examining the content before resetting, ...

The text entered in the textbox vanishes after I press the submit button

When a user selects a value in a textbox and clicks the submit button, the selected value disappears. <div class="panel-body" ng-repeat="patient in $ctrl.patient | filter:$ctrl.mrd"> <form> <div class="form-group"> ...

Managing the state in NextJS applications

I've scoured the depths of the internet in search of a solution for this issue, but unfortunately I have yet to come across one that effectively resolves it. I've experimented with various state management tools including: useContext Redux Zusta ...