javascriptd3.jsmouseevent

How can I slow down drag behavior with D3/JavaScript?


I am designing a visualization which begins with the user drawing points on the screen as shown here: https://jsfiddle.net/jtr13/j2yrknf3/25/

The relevant part of the code (inspired by this answer) is:

svg.on("mousedown", mousedown)
  .on("mouseup", mouseup);

function mousedown() {
  const new_x = xScale.invert(d3.pointer(event)[0]);
  const new_y = yScale.invert(d3.pointer(event)[1]);
  svg.append("circle")
    .data([{x: new_x, y: new_y}])
    .attr("cx", d => xScale(d.x))
    .attr("cy", d => yScale(d.y))
    .attr("r", "3");

svg.on("mousemove", mousemove);
}

function mousemove() {
  const new_x = xScale.invert(d3.pointer(event)[0]);
  const new_y = yScale.invert(d3.pointer(event)[1]);
  svg.append("circle")
    .data([{x: new_x, y: new_y}])
    .attr("cx", d => xScale(d.x))
    .attr("cy", d => yScale(d.y))
    .attr("r", "3"); 
}

function mouseup() {
  svg.on("mousemove", null);
}

The problem is that too many points are added during mousemove. I would like points to be added at about 1/5 or 1/10 of the current rate. I tried adding delays of various sorts and nothing worked. How can I slow down the drag behavior?


Solution

  • Throttle Function: Limits how often a function runs by making it wait a set time between calls.

    Throttled Mousemove: Slows down how often the mousemove event runs, so it only triggers every 100 milliseconds. You can change the timing if you want it faster or slower.

    // Throttle function to limit the rate at which the mousemove function is called
    function throttle(func, delay) {
      let lastCall = 0;
      return function(...args) {
        const now = new Date().getTime();
        if (now - lastCall >= delay) {
          lastCall = now;
          return func(...args);
        }
      };
    }
    
    // Width and height
    const w = 400;
    const h = 350;
    
    // Create scale functions
    const xScale = d3.scaleLinear()
      .domain([0, 4])
      .range([0, w]);
    
    const yScale = d3.scaleLinear()
      .domain([0, 4])
      .range([h, 0]);
    
    // Define the SVG container
    const svg = d3.select("div#plot")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    
    svg.append("rect")
      .attr("x", "0")
      .attr("y", "0")
      .attr("width", w)
      .attr("height", h)
      .attr("fill", "lightblue");
    
    // Add event listeners for mouse interactions
    svg.on("mousedown", mousedown).on("mouseup", mouseup);
    
    // Function for mousedown event
    function mousedown() {
      const new_x = xScale.invert(d3.pointer(event)[0]);
      const new_y = yScale.invert(d3.pointer(event)[1]);
      svg.append("circle")
        .data([{ x: new_x, y: new_y }])
        .attr("cx", d => xScale(d.x))
        .attr("cy", d => yScale(d.y))
        .attr("r", "3");
      d3.select("h3#num").text("Number of points: " + d3.selectAll("circle").size());
    
      svg.on("mousemove", throttledMousemove);
    }
    
    // Function for mousemove event with throttling
    const throttledMousemove = throttle(function () {
      const new_x = xScale.invert(d3.pointer(event)[0]);
      const new_y = yScale.invert(d3.pointer(event)[1]);
      svg.append("circle")
        .data([{ x: new_x, y: new_y }])
        .attr("cx", d => xScale(d.x))
        .attr("cy", d => yScale(d.y))
        .attr("r", "3");
      d3.select("h3#num").text("Number of points: " + d3.selectAll("circle").size());
    }, 100); // Adjust the delay to control the rate (e.g., 100 milliseconds)
    
    // Function for mouseup event
    function mouseup() {
      svg.on("mousemove", null);
    }
    
    // Function to restart and clear all points
    function restart() {
      d3.selectAll("circle").remove();
      d3.select("h3#num").text("Number of points: 0");
    }
    <h3 id="info">Click and drag to add points.</h3>
    <h3 id="num">Number of points: 0</h3>
    <button onclick="restart()">Restart</button>
    <p></p>
    <div id="plot"></div>
    
    <script src="https://d3js.org/d3.v7.js"></script>