Search code examples
javascriptsvgd3.jszoomingpan

D3 Pan Zoom Overflow


I was wondering if you could help me with the follwoing D3js Zoom and pan functionality in the following fiddle: http://jsfiddle.net/moosejaw/nUF6X/5/

I hope the code (although not great) is straight forward.

I have a chart that has total chromosome length by total chromosome length. The tick values are the individual lengths (totals) of each chromosome. The ticks are formatted to be the name of the chromosomes (to look nice to the end user).

The problems that I am having are:

  1. The x-axis and y-axis labels are extending outside the graph area. When I do not supply the tick values explicitly, the labels "disappear" as they should. See:

    var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .tickValues(tickValues)
    .tickFormat(function(d) { 
        var ret = bpToChrMBP(d);
        return ret.chr;
    });
    
  2. How do I prevent the x axis to not pan to the left before the minimum value? Also not pan to the right past the maximum value? This happens whether or not I am zoomed in. (The same for y-axis, except top and bottom).

  3. Is there a way to "center" the axis labels between the tick marks. The tick marks are not evenly spaced. I tried using subdivide for minor tick marks, but that doesn't subdivide between tick marks correctly.

Any help would be greatly appreciated!

Matt


Solution

  • This Fiddle solves most of your problems: http://jsfiddle.net/CtTkP/ The explanations are below:

    • I am not sure what you meant by extending beyond the graphs area. Should the labels be insde the chart-area? If you mean that on panning, the labels extend beyond the axis, the problem can be solved by using two more clip-paths judiciously, though this does not allow for graceful fading of values which svg.axis translations provide:
    var clipX = svg.append("clipPath")
          .attr('id', 'clip-x-axis')
          .append('rect')
          .attr('x', 0)
          .attr('y', 0)
          .attr('width', width)
          .attr('height', margin.bottom);
    
    svg.append("g")
        .attr("class", "x axis")
        .attr('clip-path', 'url(#clip-x-axis)')
        .attr("transform", "translate(0, " + height + ")")
        .call(xAxis);
    
    // ...
    
    var clipY = svg.append("clipPath")
          .attr('id', 'clip-y-axis')
          .append('rect')
          .attr('x', - margin.left)
          .attr('y', 0)
          .attr('height', height)
          .attr('width', margin.left);
    
    svg.append("g")
        .attr("class", "y axis")
        .attr('clip-path', 'url(#clip-y-axis)')
        .call(yAxis);
    
    • To prevent the panning from extending beyond values, you will have to manually restrict the translate for the zoom:
    function zoomed() {
    
         var trans = zoom.translate(),
             scale = zoom.scale();
    
         tx = Math.min(0, Math.max(width * (1 - scale), trans[0]));
         ty = Math.min(0, Math.max(height * (1 - scale), trans[1]));
    
         zoom.translate([tx, ty]);
    
        svg.select(".x.axis").call(xAxis);
        svg.select(".y.axis").call(yAxis);
    
        // ...
    

    This will not allow the graph from panning beyond the limits.

    • As you are explicitly overriding the tickValues, you can tweak the values to center them:
    var tickValues2 = [];
    tickValues.forEach(function (t, idx) {
        if (idx < tickValues.length - 1) {
            tickValues2.push((t + tickValues[idx + 1]) / 2);
        }
    });
    

    Then instead of using tickValues for xAxis and yAxis, use tickValues2.