user2767799
user2767799

Reputation: 43

d3.js performace optimization with selective updates

I am creating a visualization using d3, which will have 60 top and 60 bottom rectangles. Each rectangle will contain about 100-150 circles. The data is streaming via websockets. Without filtering, the update is very time intensive. I am trying to optimize it by selective updates. However, it looks like I donot fully understand .filter(). Here is the jsFiddle to the portion of code dealing with rectangles

http://jsfiddle.net/vjaT2/1/

As you can notice from js comments in function updateRect, I have tried couple of approaches. 1. filtering inside .data 2. createing an id and doing a selectAll on the id.

But both appreaches do not work.

Any help as to what I am missing or doing wrong?

    var bottom_rects = 1;
var top_rects = 1;
var rects = [];

function generate_rect(){
    var created = '';

    // 6(*10) top rectangles & 6(*10) bottom rectangles 

    if(bottom_rects <= top_rects-1) {
        new_rect = {"name":"rect_bottom_"+bottom_rects,"location":"bottom","minx":0,"miny":Math.floor((2*h)/3),"maxx":0,"maxy":h};
        rects.push(new_rect);
        //fix x values
        var step  = Math.floor(w/bottom_rects);
        var x = 0;
        for (var i = 0; i < rects.length; i++) {
            var item = rects[i];
            if (item.location == "bottom"){
                    item.minx = x;
                    item.maxx = x+step;
                    x=x+step;
            }

        }
        bottom_rects++;
        created = "bottom";
    } else {
        new_rect = {"name":"rect_top_"+top_rects,"location":"top","minx":0,"miny":0,"maxx":0,"maxy":Math.floor(h/3)};
        rects.push(new_rect);
        //fix x values
        var step  = Math.floor(w/top_rects);
        var x = 0;
        for (var i = 0; i < rects.length; i++) {
            var item = rects[i];
            if (item.location == "top"){
                    item.minx = x;
                    item.maxx = x+step;
                    x=x+step;
            }
        }
        top_rects++;
        created = "top";
    }
    updateRect(created);
}

var w = 1200,
    h = 760;

var vis = d3.select("#chart").append("svg:svg")
    .attr("width", w)
    .attr("height", h);

function updateRect(location) {
    //var allrects = vis.selectAll("rect")
    var allrects = vis.selectAll(".rect_"+location)
        //.data(rects.filter(function(d)  { return d.location == location; }))
        .data(rects)
            //update
            .attr("x", function(d)      {  return d.minx;        })
            .attr("y", function(d)      {  return d.miny;        })
            .attr("width",function(d)   {  return d.maxx-d.minx; })
            .attr("height", function(d) {  return d.maxy-d.miny; })
            //create
        .enter().append("rect")         
            .attr("x", function(d)      {  return d.minx;        })
            .attr("y", function(d)      {  return d.miny;        })
            //.attr("id", "rect_"+location)
            .attr("width",function(d)   {  return d.maxx-d.minx; })
            .attr("height", function(d) {  return d.maxy-d.miny; })
            .attr("fill", "blue")
            .attr("stroke", "black")
            .style("stroke-width", 2)
            .style("fill-opacity", 0.2)
        .exit().remove();
}   

for(var i =0; i < 12; i++) setTimeout(generate_rect,(i+1)*200);

Upvotes: 1

Views: 1586

Answers (1)

Lars Kotthoff
Lars Kotthoff

Reputation: 109242

There are two different filter() functions. The first is the Javascript filter which you can apply to any array and has nothing to do with D3. The second is D3's filter which you can apply to selections.

For selective updates, you can in principle use both. In the first case, you filter the data before you pass it to .data() (as you've tried already) and then operating on what is matched to existing DOM elements. The important thing here is that you almost certainly need to supply a function to .data() that tells it how to match data in your array to DOM elements. There's more on this in the documentation.

For what you want to do, I suggest using the other .filter() function that operates on selections. That is, you select as you would in the usual case and then filter elements from that selection. For your example, the code would look something like this.

vis.selectAll("rect")
   .filter(function(d) { return d.location == location; });

Alternatively, you can use a third approach of selecting by class (which you have also tried), but then you don't need any of the other filter functions. However, if you want to bind new data to those elements, you have to take care to pass it only the data that is meant for this specific subset of elements, i.e. you would need to filter the data before passing it to .data(). In practice, this is probably the least intuitive way as you would need to partition the data in several places.

Upvotes: 2

Related Questions