title: d3 graph with limited zoom
body: <p>I have implemented a d3 line graph which reads data from a CSV file, and then plots multiple lines which react to mouseover events. It works fine with pan and zoom using the following code (sorry that it is so long and slightly untidy but I felt it best to display the full code):</p>
  
  <pre><code>    d3.csv("ijisb.csv", function(error, data) {
  
      var margin = {top: 50, right: 120, bottom: 50, left: 70},
      width = 1200 - margin.left - margin.right,
      height = 800 - margin.top - margin.bottom;
  
      var parseDate = d3.time.format("%m%d").parse;
  
      var color = d3.scale.category20();
  
      color.domain(d3.keys(data[0]).filter(function(key) { return key !== "date"; }));
  
      data.forEach(function(d) {
          d.date = parseDate(d.date);
      });
  
      var sources = color.domain().map(function(name) {
          return {
              name: name,
              values: data.map(function(d) {
                  return {date: d.date, temperature: +d[name]};
              })
          };
      });
  
      var x = d3.time.scale()
          .range([0, width])
          .domain([
          d3.min(data, function(d) { return d.date; }),
          d3.max(data, function(d) { return d.date; })
      ]);
  
      var y = d3.scale.linear()
          .range([height, 0])
          .domain([
          0,
          d3.max(sources, function(c) { return d3.max(c.values, function(v) { return v.temperature; }); })
      ]);
  
      var xAxis = d3.svg.axis()
          .scale(x)
          .orient("bottom")
          .ticks(12)
          .tickFormat(d3.time.format("%b %d"));
  
      var yAxis = d3.svg.axis()
          .scale(y)
          .orient("left")
          .ticks(5);
  
      var line = d3.svg.line()
          .defined(function(d) { return d.temperature &gt;= 0; })
          .x(function(d) { return x(d.date); })
          .y(function(d) { return y(d.temperature); });
  
      var zoom = d3.behavior.zoom()
      .x(x)
      .y(y)
      .scaleExtent([1, 8])
      .on("zoom", zoomed);
  
      var svg = d3.select("body").append("svg")
      .attr("width", "100%")
      .attr("height", "100%")
      .attr("viewBox", "0 0 1200 800")
      .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
      .call(zoom);
  
      var rect = svg.append("svg:rect")
      .attr("width", width)
      .attr("height", height)
      .attr("class", "plot");
  
      var make_x_axis = function () {
          return d3.svg.axis()
              .scale(x)
              .orient("bottom")
              .ticks(12)
              .tickFormat(d3.time.format("%b %d"));
      };
  
      var make_y_axis = function () {
          return d3.svg.axis()
              .scale(y)
              .orient("left")
              .ticks(5);
      };
  
      svg.append("g")
          .attr("class", "x axis")
          .attr("transform", "translate(0," + height + ")")
          .call(xAxis);
  
      svg.append("g")
          .attr("class", "y axis")
          .call(yAxis);
  
      svg.append("g")
          .attr("class", "x grid")
          .attr("transform", "translate(0," + height + ")")
          .call(make_x_axis()
          .tickSize(-height, 0, 0)
          .tickFormat(""));
  
      svg.append("g")
          .attr("class", "y grid")
          .call(make_y_axis()
          .tickSize(-width, 0, 0)
          .tickFormat(""));
  
      var source = svg.selectAll(".source")
      .data(sources)
      .enter().append("g")
      .attr("class", "source");
  
      var clip = svg.append("clipPath")
      .attr("id", "clip")
      .append("rect")
      .attr("x", 0)
      .attr("y", 0)
      .attr("width", width)
      .attr("height", height)
      .append("text");
  
      source.append("path")
          .data(sources)
          .attr("class", "line")
          .attr("clip-path", "url(#clip)")
          .attr("d", function(d) { return line(d.values); })
          .style("stroke", function(d) {return color(d.name);})
          .style("opacity", 0.8)
          .on("mouseover", function(d){
              d3.select(this)
                  .style("stroke",function(d) {return color(d.name);})
                  .style("opacity", 1.0)
                  .style("stroke-width", 2.5);
                  this.parentNode.parentNode.appendChild(this.parentNode);
              d3.select('#text-' + d.name)
                  .style("fill",function(d) {return color(d.name);})
                  .style("font-weight", 700);
          })
          .on("mouseout", function(d) {
              d3.select(this)
                  .transition()
                  .duration(250)
                  .style("stroke", function(d) {return color(d.name);})
                  .style("stroke-width", 1.5)
                  .style("opacity", 0.8);
              d3.select('#text-' + d.name)
                  .transition()
                  .duration(250)
                  .style("fill", function(d) {return color(d.name);})
                  .style("font-weight", 400);
          })
          .attr("id", function(d, i) { return "path-" + d.name; }); 
  
      source.append("text")
          .datum(function(d) { return {name: d.name}; })
          .attr("x", function(d, i) { return width+10; })
          .attr("y", function(d, i) { return (i*(height/16)); })
          .style("fill", function(d) {return color(d.name);})
          .on("mouseover", function(d){
              d3.select('#path-' + d.name)
                  .style("stroke",function(d) {return color(d.name);})
                  .style("opacity", 1.0)
                  .style("stroke-width", 2.5);
                  this.parentNode.parentNode.appendChild(this.parentNode);
              d3.select(this)
                  .style("fill",function(d) {return color(d.name);})
                  .style("font-weight", 700);
          })
          .on("mouseout", function(d) {
              d3.select('#path-' + d.name)
                  .transition()
                  .duration(250)
                  .style("stroke", function(d) {return color(d.name);})
                  .style("stroke-width", 1.5)
                  .style("opacity", 0.8);
              d3.select(this)
                  .transition()
                  .duration(250)
                  .style("fill", function(d) {return color(d.name);})
                  .style("font-weight", 400);
          })
          .text(function(d) { return d.name; })
          .attr("font-family","sans-serif")
          .attr("font-size","14px")
          .attr("id", function(d, i) { return "text-" + d.name; }
          ); 
  
      var minT = new Date('01/01/1900'), maxT = new Date('01/01/2002'), w = $(window).width();
  
  function zoomed() {
          d3.event.translate;
          d3.event.scale;
  
      svg.select(".x.axis")
          .call(xAxis);
      svg.select(".y.axis").call(yAxis);
      svg.select(".x.grid")
          .call(make_x_axis()
          .tickSize(-height, 0, 0)
          .tickFormat(""));
      svg.select(".y.grid")
          .call(make_y_axis()
          .tickSize(-width, 0, 0)
          .tickFormat(""));
      source.select(".line")
          .attr("d", function(d) { return line(d.values); })
          .style("stroke", function(d) {return color(d.name);});  
      }
  
      });
  </code></pre>
  
  <p>The problem I have is that I want to limit the panning and zooming so that it is not possible for the graph to be zoomed or panned out offscreen. I can do this by setting the scaleExtent on the zoom (which is implemented in the above example) and changing the zoomed function to:</p>
  
  <pre><code>    function zoomed() {
  
      var t = zoom.translate(),
  
      tx = t[0];
      ty = t[1];
  
      tx = Math.min(tx, 0);
      zoom.translate([tx, ty]);
  
      d3.event.translate;
      d3.event.scale;
  
      svg.select(".x.axis")
          .call(xAxis);
      svg.select(".y.axis").call(yAxis);
      svg.select(".x.grid")
          .call(make_x_axis()
          .tickSize(-height, 0, 0)
          .tickFormat(""));
      svg.select(".y.grid")
          .call(make_y_axis()
          .tickSize(-width, 0, 0)
          .tickFormat(""));
      source.select(".line")
          .attr("d", function(d) { return line(d.values); })
          .style("stroke", function(d) {return color(d.name);});
  }
  </code></pre>
  
  <p>This limits the x-axis minimum to zero. However hard I try however, I cannot limit the maximum value of the x-axis, which means the graph can still pan too far to the right.</p>
  
  <p>Any help? I hope this makes sense!</p>
  
  <p>Nick</p>