Search code examples
javascriptd3.jszooming

How can I zoom into this graph using D3v6


I'm trying to get zoom working on some rather simple line charts for a greenhouse project.

This is what I have, it seems like when I mouse over the SVG my mouse events start getting captured, the Y axis gets redrawn like I'm zooming in, my X axis labels disappear, and the lines and dots in the actual plot don't change.

// draw the temperature over time graph
function draw_temp_graph(data) {

  const button = d3.selectAll('input[name="units"]');
  const isFahrenheit = button.property('checked');

  // Creating tooltip div
  var div = d3.select("#temp_graph").append("div")
  .attr("class", "tooltip")
  .style("opacity", 0);

  // Handling data when it's arrived
  data.then(function (data) {
    data.forEach( (d) => {
      d.d_date = new Date(d.time_stamp * 1000);
      if (isFahrenheit) {
        d.t_temp = (d.temp * (9.0 / 5.0) + 32).toFixed(2);
      } else {
        d.t_temp = d.temp;
      }
    });

    // set dimensions and margins
    var margin = {top: 50, right: 30, bottom: 100, left: 60 },
      width = 800 - margin.left - margin.right,
      height = 400 - margin.top - margin.bottom;

    // Create svg
    var svg = d3.select("#temp_graph")
      .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
      .append("g")
        .attr("transform",
              "translate(" + margin.left + "," + margin.top + ")");

    // Add Graph title
    svg.append("text")
      .attr("x", (width / 2 ))
      .attr("y", 0 - (margin.top / 2))
      .attr("text-anchor", "middle")
      .style("font-size", "1.4em")
      .style("text-decoration", "underline")
      .text("Temperature");

    // Add X axis
    var x = d3.scaleTime()
      .domain(d3.extent(data, function(d) { return d.d_date; }))
      .range([0, width]);
    var xAxis = svg.append("g")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x))
      .selectAll("text")
          .style("text-anchor", "end")
          .attr("dx", "-.8em")
          .attr("dy", ".15em")
          .attr("transform", "rotate(-65)");

    // Add X axis label
    svg.append("text")
      .attr("transform",
            "translate(" + (width/2) + " ," +
                          (height + margin.top + 40) + ") ")
      .style("text-anchor", "middle")
      .text("Time");


    // Add Y axis
    minY = d3.min(data, function(d) { return +d.t_temp } );
    maxY = d3.max(data, function(d) { return +d.t_temp } );

    var y = d3.scaleLinear()
      .domain([minY, maxY])
      .range([height, 0]);
    var yAxis = svg.append("g")
      .call(d3.axisLeft(y));

    // Add Y axis label
    svg.append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 0 - margin.left)
      .attr("x", 0 - (height / 2))
      .attr("dy", "1em")
      .style("text-anchor", "middle")
      .text("Degrees");

    // Add a line
    var line = svg.append("path")
      .datum(data)
      .attr("fill", "none")
      .attr("stroke", "#00a899")
      .attr("stroke-width", 1.5)
      .attr('id', 'temp_line')
      .attr("d", d3.line()
        .x(function(d) { return x(d.d_date) })
        .y(function(d) { return y(d.t_temp) })
      )

    // Add dots with tooltips
    var dots = svg.selectAll('dot')
      .data(data)
    .enter().append('circle')
      .attr('r', 1.5)
      .attr('cx', function(d) { return x(d.d_date); })
      .attr('cy', function(d) { return y(d.t_temp); })
      .attr('fill', '#00A198')
      .on('mouseover', function(event, d) {
        div.transition()
          .duration(200)
          .style('opacity', .9);
        div.html(formatTime(d.d_date) + '<br/>' + d.t_temp + '\u00B0')
          .style('left', (event.pageX) + 'px')
          .style('top', (event.pageY - 28) + 'px');
      })
      .on('mouseout', function(d) {
        div.transition()
          .duration(500)
          .style('opacity', 0);
      })

    // Setting a clipPath, this is to "clip" everything outside of a certain area
    // var clip = svg.append('defs').append('svg:clipPath')
    //   .append('svg:rect')
    //   .attr('width', 800)
    //   .attr('height', 500)
    //   .attr('x', 0)
    //   .attr('y', 0);

    // Creating a zoom object
    var zoom = d3.zoom()
      .scaleExtent([0.5, 20])
      .extent([[0, 0], [width, height]])
      .on('zoom', updateTemp);

    svg
      .style('pointer-events', 'all')
      .call(zoom);

    // function to redraw while zooming
    function updateTemp() {
      console.log('zoomed')
      // get the new scale
      var transform = d3.zoomTransform(this);
      var newX = transform.rescaleX(x);
      var newY = transform.rescaleY(y);

      // update the axes
      xAxis.call(d3.axisBottom(newX));
      yAxis.call(d3.axisLeft(newY));

      // update the line
      line
        .selectAll('path')
        .attr('d', d3.line()
          .x(function (d) {
            return newX(d.d_date);
          })
          .y(function (d) {
            return newY(d.t_temp);
          })
        )

      // update the chart
      dots
        .selectAll('dot')
        .selectAll('circle')
          .attr('cx', function (d) {
            return newX(d.d_date);
          })
          .attr('cy', function (d) {
            return newY(d.t_temp);
          })
    }
  })
}

Solution

  • in d3 js v 6 you don't need 'pointer-events' you also need to change the update to take in 'event'

    //.style('pointer-events', 'all') in d3 js v6 you don't need it anymore
    svg.call(zoom);
    
    // function to redraw while zooming
    // in d3 js v6 you need to use event in update
    function updateTemp(event) {
      console.log('zoomed')
      // get the new scale
      //var transform = d3.zoomTransform(this);
      //var newX = transform.rescaleX(x); 
      //you need use event like this :
      var newX = event.transform.rescaleX(x); 
      //var newY = transform.rescaleY(y); 
      //you need to use event like this :
      var newY = event.transform.rescaleY(y)
      // update the axes
      xAxis.call(d3.axisBottom(newX));
      yAxis.call(d3.axisLeft(newY));
    
      // update the line
      line
        .selectAll('path')
        .attr('d', d3.line()
          .x(function (d) {
            return newX(d.d_date);
          })
          .y(function (d) {
            return newY(d.t_temp);
          })
        )
    
      // update the chart
      dots
        .selectAll('dot')
        .selectAll('circle')
          .attr('cx', function (d) {
            return newX(d.d_date);
          })
          .attr('cy', function (d) {
            return newY(d.t_temp);
          })
    }