Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unusual d3 zoom in reusable class - this and bind

I was busting my head on this problem for some time...

I have a usual constructor/prototype object (like a class) in js which holds all of my d3 chart logic:

figureGen = function(html_element) {
  this.svg = d3.select(html_element)
    .append('svg')
    .style('width', '100%')
    .style('height', '100%')
    .append('g')
      .attr("class", "sadrzalac")
      .attr("transform", "translate(" + 0 + "," + 0 + ")");
      this.element = element;
      this.data = [];
      this.yRange = d3.scale.linear();
      this.xRange = d3.scale.linear();
      this.xAxis = undefined;
      this.yAxis = undefined;
      this.width = undefined;
      this.height = undefined;
      this.zoom = undefined;
      this.margin = {top: 20, right: 20, bottom: 20, left: 35};
      this.svg.append("rect") 
       .attr("class", "clipPath")
       .attr("transform", "translate(" + this.margin.left + "," + -this.margin.top + ")");
      this.svg.append('g')
       .attr("class","xaxis axis");
      this.svg.append('g')
      .attr("class", "yaxis axis");
}

for the constructor, and for the methods something like this

figureGen.prototype = {
    constructor: figureGen,
    init: function(sampledata, width, height){
       var y_min = Number(d3.min(this.podaci, function (d) { return d.y;})); 
       var y_max = Number(d3.max(this.podaci, function (d) { return d.y;}));
       var x_min = Number(d3.min(this.podaci, function (d) { return d.x;}));
       var x_max = Number(d3.max(this.podaci, function (d) { return d.x;}));

       var ukupan_opseg = y_max - y_min;
       var spacer = (ukupan_opseg*25)/100;

       this.xRange = d3.scale.linear().range([this.margin.left + 10, this.width - this.margin.right]).domain([x_min, x_max]);
       this.yRange = d3.scale.linear().range([this.height - this.margin.top, this.margin.bottom]).domain([y_min-spacer, y_max+spacer]);

      this.zoom = d3.behavior.zoom()
        .x(this.xRange)
        .y(this.yRange)
        .scaleExtent([1, 5])
        .center([this.width / 2, this.height / 2])
        .size([this.width, this.height])
        .on("zoom", this.zoomProcess());

       this.svg
        .attr("width", this.sirina)
        .attr("height", this.visina)
        .call(this.zoom);

        this.xAxis = d3.svg.axis()
           .scale(this.xRange)
           .innerTickSize(-this.height+40)
           .outerTickSize(5)
           .tickPadding(10)
           .tickFormat(d3.format("d"));

        this.yAxis = d3.svg.axis()
           .scale(this.yRange)
           .orient("left")
           .innerTickSize(-this.width)
           .outerTickSize(5)
           .tickPadding(10)
           .tickSubdivide(false);

       this.svg.select('.xosa')
           .attr("transform", "translate(0," + (this.height - this.margin.bottom) + ")")
           .call(this.xAxis);

       this.svg.select(".yosa")
           .attr("transform", "translate(" + (this.margin.left) + ",0)")
           .call(this.yAxis);
  },
  zoomProcess: function{
      this.svg.select(".xaxis").call(this.xAxis);
      this.svg.select(".yaxis").call(this.yAxis);
      console.log(this.svg);
  }
}

and the problem is in the zoomProcess method which always returns (logs) HTML element on the this.svg instead of this.svg object which can .select. Zoom behaviour is properly attached to my element, and on mouse whell the zooming starts but it pops up an error saying that this .call on this.xAxis can't be performed.

What's even stranger when I trigger zoomProcess from another source, like a button click its console log outputs the proper this.svg object and not the HTML element, so it seems that the error comes from the zoom event handler. What's wrong here?

These are the two examples I've been looking at:
http://bl.ocks.org/stepheneb/1182434
http://bl.ocks.org/mbostock/7ec977c95910dd026812

EDIT: Now I guess that I probably should use .bind() on the this object so that its reference stays the same when going to zoomProcedura method. Still I`m not sure how to do this.

like image 207
Fedja Blagojevic Avatar asked Jul 03 '15 13:07

Fedja Blagojevic


People also ask

Which is or are the main selection in d3?

Selection methods come in two forms: select and selectAll: the former selects only the first matching element, while the latter selects all matching elements in document order. The top-level selection methods, d3.


1 Answers

I assume there is a typo that is not in your actual code...

  zoomProcess: function{
      this.svg.select(".xaxis").call(this.xAxis);
      this.svg.select(".yaxis").call(this.yAxis);
      console.log(this.svg);
  }  

is probably...

  zoomProcess: function(){
      this.svg.select(".xaxis").call(this.xAxis);
      this.svg.select(".yaxis").call(this.yAxis);
      console.log(this.svg);
  }

or maybe...

  zoomProcess: function(){
      return function() {
          this.svg.select(".xaxis").call(this.xAxis);
          this.svg.select(".yaxis").call(this.yAxis);
          console.log(this.svg);
      }
  }

I can't know for certain. And I assume you really are invoking like...

    .on("zoom", this.zoomProcess());

Not...

    .on("zoom", this.zoomProcess);

But anyway, based on that assumption, to make it work the way you want, try...

  zoomProcess: function(){
      //'this' is figureGen.prototype
      var that = this;
      return function () {
          //'this' is window
          that.svg.select(".xaxis").call(that.xAxis);
          that.svg.select(".yaxis").call(that.yAxis);
          console.log(that.svg);
      }
  }  

or...

  zoomProcess: function(){
      //'this' is figureGen.prototype
      return function () {
          //'this' is figureGen.prototype
          this.svg.select(".xaxis").call(this.xAxis);
          this.svg.select(".yaxis").call(this.yAxis);
          console.log(this.svg);
      }.bind(this)
  }
like image 123
Cool Blue Avatar answered Oct 04 '22 04:10

Cool Blue