score:4

Accepted answer

Instead of using a drag, why not listen for a mousemove event on your canvas and push points to a path drawing function that way.

As a simple example:

var points = [];
var canvas = d3.select("body")
   .append("canvas")
   .attr("height",500)
   .attr("width", 600)
   .on("mousemove", addPoint);
      
var context = canvas.node().getContext("2d");
      
function addPoint() {
   points.push(d3.mouse(this));
   draw();
}

function draw() {
  context.clearRect(0,0,600,600);
  context.beginPath();
  context.moveTo(...points[0]);
  points.forEach(function(d) {
    context.lineTo(...d);
  })
  context.lineWidth = 1;
  context.stroke();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

If you need to have drawing after a click on each entry of the mouse over the canvas, you could assign the mousemove listener on click too (mouse out removes the mousemove listener below, so you can draw multiple lines):

var points = [];

var canvas = d3.select("body")
   .append("canvas")
   .attr("height",500)
   .attr("width", 600)
   .on("click", function() {
     points.push([])
     d3.select(this).on("mousemove", addPoint);
   })
   .on("mouseout", function() {
     d3.select(this).on("mousemove",null);
   })
   
      
var context = canvas.node().getContext("2d");
      
function addPoint() {
   points[points.length-1].push(d3.mouse(this));
   draw();
}

function draw() {
  context.clearRect(0,0,600,600);
  points.forEach(function(p) {
    context.beginPath();
    context.moveTo(...p[0]);
    p.forEach(function(d) {
      context.lineTo(...d);
    })
    context.lineWidth = 1;
    context.stroke();
  })
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

And if you wanted to smoothen out the curves because hands are shaky you could use d3.line and a d3 curve to draw the lines:

var points = [];
var canvas = d3.select("body")
   .append("canvas")
   .attr("height",500)
   .attr("width", 600)
   .on("mousemove", addPoint);
      
var context = canvas.node().getContext("2d");

var line = d3.line().context(context)
    .x(function(d) { return d[0]; })
    .y(function(d) { return d[1]; })
    .curve(d3.curveBasis);
      
function addPoint() {
   points.push(d3.mouse(this));
   draw();
}

function draw() {
  context.clearRect(0,0,600,600)
  context.beginPath();
  line(points);  
  context.lineWidth = 1;
  context.stroke();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>

Though you could also filter out points in the array to get something smoother (since points added by mousemove will be added very close together depending on mouse speed, making smoothening harder), or use different d3 curves:

var points = [];
var canvas = d3.select("body")
   .append("canvas")
   .attr("height",500)
   .attr("width", 600)
   .on("mousemove", addPoint);
      
var context = canvas.node().getContext("2d");

var line = d3.line().context(context)
    .x(function(d) { return d[0]; })
    .y(function(d) { return d[1]; })
    .curve(d3.curveBasis);
      
function addPoint() {
   points.push(d3.mouse(this));
   draw();
}

function draw() {
  context.clearRect(0,0,600,600)
  context.beginPath();
  line(points.filter(function(p,i) {
    if(i%8 == 0 || i == points.length-1) return p;
  }));  
  context.lineWidth = 1;
  context.stroke();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>


Related Query