Search code examples
d3.jszooming

Horizontal pan with ordinal x axis in d3


I would like to have vertical zooming and horizontal panning with a simple bar chart. Here's my example: http://jsfiddle.net/bjames/rR7ee/.

Most of what I've tried involves viewBox and I understand I might have more luck with a clipPath. But all the examples I've found use a continuous x-axis in the zoom function to get the job done.

In my example, there are 8 bars but only 4 are visible. I'd like to drag the panel to the left. What am I missing?

Thanks in advance.

width = 600
height = 600
padding = {left:40, top:20, right:20, bottom:30}
size = {
    x: width - padding.left - padding.right,
    y: height - padding.top - padding.bottom
}
var svg = d3.select('.container').append('div')
    .append("svg")
    .attr("width", 300)
    .attr("height", height)
    .attr('class', 'frame')
    .append("g")
    .attr("transform", "translate(" + padding.left + "," + 
          padding.top + ")")


svg.append('rect')
    .attr('class', 'background')
    .attr('pointer-events', 'all')
    .attr('fill', 'none')
    .attr('height', size.y + 'px')
    .attr('width', size.x + 'px')


var d = [5,6,7,8,9,10,11,12]

var x = d3.scale.ordinal()
      .domain(d3.range(d.length))
      .rangeRoundBands([0, size.x], .15)

var y = d3.scale.linear()
            .domain([0,d3.max(d)])
            .range([size.y, 0])

var xax = d3.svg.axis().scale(x)
            .tickSize(-size.y).orient('bottom')

var yax = d3.svg.axis().scale(y)
            .tickSize(-size.x).orient('left')

svg.append("g")
    .attr("class", "y axis")
    .call(yax)

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + size.y+ ")")
    .call(xax)

svg.append('g').attr('class', 'rects')

function update(){

    var rects = d3.select('.rects').selectAll('rect')
                    .data(d)

    rects.attr('x', function(d,i) { return x(i)})
        .attr('width', x.rangeBand())
        .attr('y',  function(d) { return y(d)})
        .attr('height', function(d) { return size.y - y(d)})

    rects.enter().append('rect')
        .attr('x', function(d,i) { return x(i) })
        .attr('width', x.rangeBand())
        .attr('y',  function(d) { return y(d)})
        .attr('height', function(d) { return size.y - y(d)})
        .style('fill', 'orange')
    svg.select('.y.axis')
        .call(yax)
    svg.select('.x.axis')
        .call(xax)
    svg.select('.background')
        .call(zoom)
}


var zoom = d3.behavior.zoom()
          .on("zoom", zoomed)

function zoomed() {
    y.domain([0, d3.max(d)*1/d3.event.scale])
    update();
}
update()

Solution

  • The vertical zooming was already implemented. I implemented the horizontal panning.

    Note: Both zooming and panning happens on the rectangle in the background, as listeners attached to it.

    I have added comments in the fiddle code that should help you out in the logic.

    width = 600
    height = 600
    padding = {left:40, top:20, right:20, bottom:30}
    size = {
        x: width - padding.left - padding.right,
        y: height - padding.top - padding.bottom
    }
    var svg = d3.select('.container').append('div')
        .append("svg")
        .attr("width", 300)
        .attr("height", height)
        .attr('class', 'frame')
        .append("g")
        .attr("transform", "translate(" + padding.left + "," + 
              padding.top + ")")
    
    
    
    svg.append('rect')
        .attr('class', 'background')
        .attr('pointer-events', 'all')
        .attr('fill', 'none')
        .attr('height', size.y + 'px')
        .attr('width', size.x + 'px')
    
    
    var d = [5,6,7,8,9,10,11,12]
    
    var x = d3.scale.ordinal()
          .domain(d3.range(d.length))
          .rangeRoundBands([0, size.x], .15)
    
    var y = d3.scale.linear()
                .domain([0,d3.max(d)])
                .range([size.y, 0])
    
    var xax = d3.svg.axis().scale(x)
                .tickSize(-size.y).orient('bottom')
    
    var yax = d3.svg.axis().scale(y)
                .tickSize(-size.x).orient('left')
    
    svg.append("g")
        .attr("class", "y axis")
        .call(yax)
    
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + size.y+ ")")
        .call(xax)
    var svg1 = svg.append('svg')
        .attr('height', size.y + 'px')
        .attr('width', size.x + 'px')
    svg1.append('g').attr('class', 'rects')
    
    function update(){
    
        var rects = d3.select('.rects').selectAll('rect')
                        .data(d)
    
        rects.attr('x', function(d,i) { return x(i)})
            .attr('width', x.rangeBand())
            .attr('y',  function(d) { return y(d)})
            .attr('height', function(d) { return size.y - y(d)})
    
        rects.enter().append('rect')
            .attr('x', function(d,i) { return x(i) })
            .attr('width', x.rangeBand())
            .attr('y',  function(d) { return y(d)})
            .attr('height', function(d) { return size.y - y(d)})
            .style('fill', 'orange')
        svg.select('.y.axis')
            .call(yax)
        svg.select('.x.axis')
            .call(xax)
        svg.select('.background')
            .call(zoom)
        svg.select('.background')
            .call(drag)
    }
    
    
    var zoom = d3.behavior.zoom()
              .on("zoom", zoomed)
    
    function zoomed() {
        y.domain([0, d3.max(d)*1/d3.event.scale])
        update();
    }
    var drag = d3.behavior.drag()
        .on("drag", dragmove).on("dragstart", dragstart);
    
     var moved = 0;//record the translate x moved by the g which contains the bars.
     var dragStartX = 0;//record the point from where the drag started
     var oldTranslateX = 0;
    
    function dragstart(d){
        dragStartX = d3.event.sourceEvent.clientX;
        oldTranslateX = moved;//record the old translate 
         console.log(d3.event)  
    }
    function dragmove(d) {
      var x = d3.event.x;
      var y = d3.event.y;
      var dx =   x-dragStartX 
      x = dx + oldTranslateX + 50; //add the old translate to the dx to get the resultant translate
      moved = x; //record the translate x given
      //move the bars via translate x
      d3.select('.rects').attr("transform", "translate(" + x + "," + 0 + ")");
        //move the x axis via translate x
      d3.select('.x').attr("transform", "translate("+x +" ," + size.y+ ")")
    }
    update()
    

    Here is the fiddle: http://jsfiddle.net/cyril123/o0qeom7n/3/