molotofc
molotofc

Reputation: 13

Migrating from D3.js v3 to D3.js v4 not working - a select issue?

I'm trying to migrate this JSFiddle which is in D3 v3 to D3 v4, but it is not working.

I know D3 drag behavior is now simply d3.drag() which I've changed, but when trying to run it, it is giving an error on line 53:

rect = d3.select(self.rectangleElement[0][0]);

with Chrome saying:

Uncaught TypeError: Cannot read property '0' of undefined

How do I go about changing this JSFiddle so it runs in D3 v4?

Upvotes: 1

Views: 2607

Answers (2)

Mark
Mark

Reputation: 108567

First, why are you re-selecting those things? They are already the selection you want. For example, self.rectangleElement is the selection of the rect. Second, passing an object to .attr is no longer supported in version 4. Third, the drag behavior has changed and the circle are eating your second mouse down. Here's a version where I've fixed up these things:

d3.select('#rectangle').on('click', function(){ new Rectangle(); });

var w = 600, h = 500;
var svg = d3.select('body').append('svg').attr("width", w).attr("height", h);

function Rectangle() {
    var self = this, rect, rectData = [], isDown = false, m1, m2, isDrag = false;
    
    svg.on('mousedown', function() {
    		console.log(isDown);
        m1 = d3.mouse(this);
        if (!isDown && !isDrag) {
            self.rectData = [ { x: m1[0], y: m1[1] }, { x: m1[0], y: m1[1] } ];
            self.rectangleElement = d3.select('svg').append('rect').attr('class', 'rectangle').call(dragR);
            self.pointElement1 = d3.select('svg').append('circle').attr('class', 'pointC').call(dragC1);
            self.pointElement2 = d3.select('svg').append('circle').attr('class', 'pointC').call(dragC2);            
            self.pointElement3 = svg.append('circle').attr('class', 'pointC').call(dragC3);
            self.pointElement4 = svg.append('circle').attr('class', 'pointC').call(dragC4);
            updateRect();
            isDrag = false;
        } else { 
            isDrag = true;
        }
        isDown = !isDown;   
    })
    
    .on('mousemove', function() {
        m2 = d3.mouse(this);
        if(isDown && !isDrag) { 
            self.rectData[1] = { x: m2[0] - 5, y: m2[1] - 5};
            updateRect();
        } 
    });  
    
    function updateRect() {  
        self.rectangleElement
        		.attr("x", self.rectData[1].x - self.rectData[0].x > 0 ? self.rectData[0].x : self.rectData[1].x)
            .attr("y", self.rectData[1].y - self.rectData[0].y > 0 ? self.rectData[0].y :  self.rectData[1].y)
            .attr("width", Math.abs(self.rectData[1].x - self.rectData[0].x))
            .attr("height", Math.abs(self.rectData[1].y - self.rectData[0].y));  

        var point1 = self.pointElement1.data(self.rectData);
        point1.attr('r', 5)
              .attr('cx', self.rectData[0].x)
              .attr('cy', self.rectData[0].y);        
        var point2 = self.pointElement2.data(self.rectData);
        point2.attr('r', 5)
              .attr('cx', self.rectData[1].x)
              .attr('cy', self.rectData[1].y);
        var point3 = self.pointElement3.data(self.rectData);
        point3.attr('r', 5)
              .attr('cx', self.rectData[1].x)
              .attr('cy', self.rectData[0].y);        
        var point3 = self.pointElement4.data(self.rectData);
        point3.attr('r', 5)
              .attr('cx', self.rectData[0].x)
              .attr('cy', self.rectData[1].y);
    }
    
    var dragR = d3.drag().on('drag', dragRect);
    
    function dragRect() {
        var e = d3.event;
        for(var i = 0; i < self.rectData.length; i++){
            self.rectangleElement
                .attr('x', self.rectData[i].x += e.dx )
                .attr('y', self.rectData[i].y += e.dy );
        }
        self.rectangleElement.style('cursor', 'move');
        updateRect();
    }
    
    var dragC1 = d3.drag().on('drag', dragPoint1);
    var dragC2 = d3.drag().on('drag', dragPoint2);
    var dragC3 = d3.drag().on('drag', dragPoint3);
    var dragC4 = d3.drag().on('drag', dragPoint4);
    
    function dragPoint1() {
        var e = d3.event;
        self.pointElement1
            .attr('cx', function(d) { return d.x += e.dx })
            .attr('cy', function(d) { return d.y += e.dy });        
        updateRect();   
    }   
    
    function dragPoint2() {
        var e = d3.event;
        self.pointElement2
            .attr('cx', self.rectData[1].x += e.dx )
            .attr('cy', self.rectData[1].y += e.dy );
        updateRect();   
    }   
    
    function dragPoint3() {
        var e = d3.event;
        self.pointElement3
            .attr('cx', self.rectData[1].x += e.dx )
            .attr('cy', self.rectData[0].y += e.dy );     
        updateRect();   
    }   
    
    function dragPoint4() {
        var e = d3.event;
        self.pointElement4
            .attr('cx', self.rectData[0].x += e.dx )
            .attr('cy', self.rectData[1].y += e.dy );
        updateRect();   
    }   
    
}//end Rectangle
svg {
    border: solid 1px red;
}

rect {
    fill: lightblue;
    stroke: blue;
    stroke-width: 2px;
}
<button id='rectangle'>Rectangle</button>
<script src="https://d3js.org/d3.v4.js" charset="utf-8"></script>

Upvotes: 1

altocumulus
altocumulus

Reputation: 21578

As of D3 v4 a selection no longer is an array of arrays but an object. The changelog has it:

Selections no longer subclass Array using prototype chain injection; they are now plain objects, improving performance.

When doing self.rectangleElement[0][0] in v3 you were accessing the first node in a selection. To get this node in v4 you need to call selection.node() on self.rectangleElement. With this your code becomes:

rect = d3.select(self.rectangleElement.node());

Have a look at the updated JSFiddle for a working version.

Upvotes: 2

Related Questions