A Sutton
A Sutton

Reputation: 83

How to drag a path in d3.js

I cannot work out how to drag a path around the svg object using d3.js

Specifically, I have a normal distribution shape rendered as a path to the svg and I want to be able to click on it and drag it around the svg space (but there is nothing unique about this particular shape etc) .

I have seen examples for points, straight lines and shapes but not a path.

My simplified code is below. Unless I am way off the mark, I suspect the error is with the dragged function right at the bottom.

Javascript:

// width and height for svg object

var w = 500;
var h = 500;

/// setting up svg object 

var svg = d3.select("body")
            .append("svg")
            .attr("width", w)
            .attr("height", h)


// Values for calculating pdf of normal distribution 
var sigma = 4;
var mu = 0;
var N = 10;
var step = 0.1;
var dataset = [];
var x;

// creating the pdf of the normal distribution and plotting it for -N to N
var C = 1/(sigma*Math.sqrt(2*Math.PI));
for (x=-N; x < N; x += step) {
    var E = (x-mu)/sigma;
    E = -(E*E)/2;
    var d = C*Math.exp(E);
    dataset.push(d);
}


// Scales slightly over fancy, required for features stripped out
var overlap = w*0.1;
var xscale1 = d3.scale.linear().range([0, w/2+overlap]).domain([0, dataset.length-1]).clamp(true);
var xscale2 = d3.scale.linear().range([w/2-overlap, w]).domain([0, dataset.length-1]).clamp(true);



// So specifies the height as max in dataset and it takes up 1/2 the svg
var yscale = d3.scale.linear().domain([0, d3.max(dataset)]).range([h,h/2]);
var area1 = d3.svg.area()
    .x(function(d,i) { return xscale1(i); })
    .y0(h)
    .y1(function(d,i) { return yscale(d); });

 // plots filled normal distribution to svg  
g1 = svg.append("path")
      .datum(dataset)
      .attr("class", "area1")
      .attr("d", area1)
      .attr("opacity",0.75);

// Problem is probably with the below line and related function dragged
d3.select("path.area1").on("drag", dragged);


function dragged() {
        var dx = d3.event.dx,
           dy = d3.event.dy;
  d3.select(this)
     .attr("transform", path => "translate(" + dx + "," + dy + ")");
}

Upvotes: 3

Views: 2170

Answers (1)

Xavier Guihot
Xavier Guihot

Reputation: 61766

Here is a version of your code which implements the drag:

var w = 500;
var h = 250;

var svg = d3.select("body")
            .append("svg")
            .attr("width", w)
            .attr("height", h);

// Values for calculating pdf of normal distribution 
var sigma = 4;
var mu = 0;
var N = 10;
var step = 0.1;
var dataset = [];
var x;

// creating the pdf of the normal distribution and plotting it for -N to N
var C = 1/(sigma*Math.sqrt(2*Math.PI));
for (x=-N; x < N; x += step) {
    var E = (x-mu)/sigma;
    E = -(E*E)/2;
    var d = C*Math.exp(E);
    dataset.push(d);
}


// Scales slightly over fancy, required for features stripped out
var overlap = w*0.1;
var xscale1 = d3.scale.linear().range([0, w/2+overlap]).domain([0, dataset.length-1]).clamp(true);
var xscale2 = d3.scale.linear().range([w/2-overlap, w]).domain([0, dataset.length-1]).clamp(true);



// So specifies the height as max in dataset and it takes up 1/2 the svg
var yscale = d3.scale.linear().domain([0, d3.max(dataset)]).range([h,h/2]);
var area1 = d3.svg.area()
    .x(function(d,i) { return xscale1(i); })
    .y0(h)
    .y1(function(d,i) { return yscale(d); });

svg.append("path")
  .datum(dataset)
  .attr("class", "area1")
  .attr("d", area1)
  .attr("opacity",0.75)
  .call(d3.behavior.drag().on("drag", dragged));

function dragged(d) {

  // Current position:
  this.x = this.x || 0;
  this.y = this.y || 0;
  // Update thee position with the delta x and y applied by the drag:
  this.x += d3.event.dx;
  this.y += d3.event.dy;

  // Apply the translation to the shape:
  d3.select(this)
    .attr("transform", "translate(" + this.x + "," + this.y + ")");
}
<body></body>
<script src="https://d3js.org/d3.v3.min.js"></script>


It's actually the exact same way of doing as any other drags on other types of shapes. You just apply the drag behavior on the selected node.

Here is the part in charge of the drag implementation:

svg.append("path")
  .datum(dataset)
  .attr("d", area1)
  ...
  .call(d3.behavior.drag().on("drag", dragged));

function dragged(d) {

  // Current position:
  this.x = this.x || 0;
  this.y = this.y || 0;
  // Update thee position with the delta x and y applied by the drag:
  this.x += d3.event.dx;
  this.y += d3.event.dy;

  // Apply the translation to the shape:
  d3.select(this)
    .attr("transform", "translate(" + this.x + "," + this.y + ")");
}

The main thing you missed is the fact that the dx and dy you receive from the event are the movements of the mouse (the "delta" of movement). These movements can't become the new position of the shape. They have to be added to the existing x and y current position of the shape.


And here is the same code but for the version 4 of d3:

var w = 500;
var h = 250;

var svg = d3.select("body").append("svg").attr("width", w).attr("height", h)

// Values for calculating pdf of normal distribution 
var sigma = 4;
var mu = 0;
var N = 10;
var step = 0.1;
var dataset = [];
var x;

// creating the pdf of the normal distribution and plotting it for -N to N
var C = 1/(sigma*Math.sqrt(2*Math.PI));
for (x=-N; x < N; x += step) {
    var E = (x-mu)/sigma;
    E = -(E*E)/2;
    var d = C*Math.exp(E);
    dataset.push(d);
}

// Scales slightly over fancy, required for features stripped out
var overlap = w*0.1;
var xscale1 = d3.scaleLinear().range([0, w/2+overlap]).domain([0, dataset.length-1]).clamp(true);
var xscale2 = d3.scaleLinear().range([w/2-overlap, w]).domain([0, dataset.length-1]).clamp(true);

// So specifies the height as max in dataset and it takes up 1/2 the svg
var yscale = d3.scaleLinear().domain([0, d3.max(dataset)]).range([h,h/2]);
var area1 = d3.area()
    .x(function(d,i) { return xscale1(i); })
    .y0(h)
    .y1(function(d,i) { return yscale(d); });

 // plots filled normal distribution to svg  
g1 = svg.append("path")
      .datum(dataset)
      .attr("class", "area1")
      .attr("d", area1)
      .attr("opacity",0.75)
      .call(d3.drag().on("drag", dragged));

function dragged(d) {

  // Current position:
  this.x = this.x || 0;
  this.y = this.y || 0;
  // Update thee position with the delta x and y applied by the drag:
  this.x += d3.event.dx;
  this.y += d3.event.dy;

  // Apply the translation to the shape:
  d3.select(this)
  	.attr("transform", "translate(" + this.x + "," + this.y + ")");
}
<body></body>
<script src="https://d3js.org/d3.v4.min.js"></script>

Upvotes: 4

Related Questions