简体   繁体   中英

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.

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)
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM