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

The option value in mat-autocomplete is not displaying correctly on IOS devices

When I click on the first option in the dropdown menu, it does not display the selected option in the field. However, when I select the second option, then the value of the first option appears, and when I choose the third option, the value of the second o ...

Customize the width of a DataTable cell in VuetifyJS

I am currently utilizing the VuetifyJS Data Table and I'm looking for a way to reduce the spacing between the entries in each header cell. Adding a width to each header did not achieve the desired result, as it seems there is a minimum predefined widt ...

Having trouble with jQuery toggle fade: Unable to make the div fade in/out when toggling

Is there a way to modify the functionality of my black button so that when clicked, the red div fades out while the blue div fades in? Right now, clicking the button switches between the two divs without any fading effect. Fiddle: http://jsfiddle.net/ddac ...

Is there a way to sort a table using a dropdown menu selection as a filter?

I am currently working on a project that involves a table with three columns, which are typically populated from a SQL database. My goal is to filter the third column based on the value selected from a dropdown menu. I came across a helpful tutorial on W3 ...

Can we trust the accuracy of Function.prototype.toString for our needs?

Can I trust Function.prototype.toString to provide a valid javascript function string for user-defined functions? Do any popular javascript engines differ in how they represent function objects as strings? I came across this question, but it doesn't ...

What is causing the malfunction in communication between my React app and Express server via fetch requests?

I am currently facing an issue while trying to connect my react js frontend (hosted on localhost for testing purposes, and also on my S3 bucket) to my node.js/express server deployed on an AWS Elastic Beanstalk environment. To resolve a CORS error, I recen ...

Double Calling of Angular Subscription

I am currently working with a series of observables that operate in the following sequence: getStyles() --> getPrices() Whenever a config.id is present in the configs array, getStyles() retrieves a style Object for it. This style Object is then passed ...

How about: "Using Node.js and Express to declaratively define a route for

I am facing an issue managing my routes in declarative objects and initializing/registering the endpoint handlers using one or more of these objects. The problem arises when I attempt to register the handlers in a loop of the declarative routes, methods, ...

Conflicting Joomla Modules

I'm currently working on a project at the following link: www.eltotaldesign.com/planeteco Within this project, I have integrated the modules DJ Image Slider and Altra Switcher. Despite attempting to install Easy JQuery and other methods, I have been ...

What is the best way to update a CSS href using window.open with JavaScript?

I am trying to dynamically change the CSS href by using window.open in JavaScript. <a class="cssButton" id="Launch" target="_blank" data-baseref="Example/index.html?" href="Example/index.html?" >Launch Example</a> I want to transform it into: ...

What is the best way to enable the delete function exclusively for users who are logged in?

I am currently working on implementing a delete function in JavaScript that will remove a MySQL row if and only if it was created by the user who is currently logged in. This means that users cannot delete rows they did not create. Below is the progress I ...

The standard date format used in Javascript/Jquery programs

I have a kendo date picker set to display dates in the format "MM/dd/yyyy". I need to use jquery or javascript to ensure that the selected date is not in the future and is greater than '01/01/1900'. The problem I'm encountering is handling ...

Retrieve the dynamically generated element ID produced by a for loop and refresh the corresponding div

I am facing a challenge with my HTML page where I generate div IDs using a for loop. I want to be able to reload a specific div based on its generated ID without having to refresh the entire page. Here is a snippet of my HTML code: {% for list in metrics ...

What's the best way to iterate through multiple objects within <td> tags using Vue.js?

I have an Array filled with multiple Objects, and now I am interested in iterating through each object as a <tr> within a <table>. I have successfully achieved this. However, some of these objects might contain nested objects. In such cases, I ...

Display the JSON information within a text input field using React JS

Below is the code I have written in the componentDidMount function: componentDidMount: function() { var url = document.URL; var currentId = url.substring(url.lastIndexOf('?') + 4); // Extracting the current ...

a guide to incorporating Google Maps into your website using latitude and longitude coordinates

After retrieving a list of latitudes and longitudes from my API for an AngularJS application, I want to display a Google map indicating the positions of these coordinates. I attempted using an iFrame but it only displayed a blank page since the latitudes ...

Let the numerical tally commence once we arrive at said juncture

Our script is designed to count numbers, but the issue arises when the page is refreshed and the number count starts over. We want the count to start only when a user reaches that specific number or point. Css:- .office{padding-right: 5px; padding-left: ...

The table row disappears but the value persists

I have designed a table where users can perform calculations. However, when a row is deleted, the values from that row appear in the row below it and subsequent rows as well. What I want is for the user to be able to completely remove a row with its values ...

Automated tool for generating random JSON objects

Looking for a tool that can generate random JSON objects? I'm in need of one to test my HTTP POST requests and incorporate the random JSON object into them. Any recommendations? ...

The JSON response is being overridden by a catch-all URL and ends up being displayed as a 404 error page

I'm dealing with a React/Express setup that looks something like this (simplified for clarity): const path = require('path') const express = require('express') const CLIENT_BUILD_PATH = path.join(__dirname, '../build') ...