The W3C DOM API doesn't support batch-adding of nodes (outside of
setting innerHTML, which is slow for other reasons); nodes are added
using the appendChild or insertBefore method, each of which takes a
single node. In practice, this path is highly optimized so there would
not be a difference in performance between batch-adding and
single-adding.
Sometimes, you can improve performance slightly by adding nodes to an
off-screen container, and then adding the container to the DOM at the
end. This isn't always a performance improvement because browsers are
optimized to batch changes to the DOM. They don't do any heavy-lifting
until the event cycle has finished and all of the DOM changes are
applied.
D3 isn't strictly setup to manipulate detached nodes, but it's not
hard to mix-in raw W3C DOM, e.g.:
var offscreen = d3.select(document.createElementNS(d3.ns.prefix.svg, "g"));
offscreen.append("svg:text").text("Hello, I'm offscreen!");
document.body.appendChild(offscreen.node()); // and now I'm on-screen
It'd be possible to make this more tightly-integrated with D3 in the
future. For example, perhaps we could have an API like this:
var offscreen = d3.create("svg:g");
offscreen.append("svg:text").text("Hello, I'm offscreen!");
d3.select("body").append(offscreen);
(I don't particularly like the overloading of `append` in this example, though.)
How many nodes are you trying to display? What type of nodes are they?
How are you adding them—can you share your code? Depending on how
you've implemented it, it might be possible to optimize your code. On
the other hand, if you're trying to draw tens of thousands of nodes,
the slowness you're seeing is more likely to be SVG rendering rather
than DOM manipulation.
Mike
A few things I've noticed:
1. d3.range(0, n) is the same as d3.range(n).
2. You're binding columns to xLen and quadrants to yLen, but the data
you then associate with each circle is based on data[i]. In this case,
`i` ranges from 0 to yLen - 1 (1), because each quadrant is grouped by
the containing column. So, you aren't actually iterating over all of
the values in your data array! In this case, the simplest solution
would to define your data as a nested array that matches your
visualization structure:
var data = [[1000, 1234], [803, 1232], [1324, 2343]];
Then, bind it to columns and quadrants like so:
var columns = vis.selectAll("g")
.data(data)
.enter().append("svg:g")
.attr("transform", function(d, i) { return "translate(" + i *
size + ",0)"; });
var quadrants = columns.selectAll("g")
.data(function(d) { return d; })
.enter().append("svg:g")
.attr("transform", function(d, i) { return "translate(0," + i *
size + ")"; });
3. You're removing and re-adding all dots on render. Updating them in
place is much faster, using enter + update + exit:
// select
var dots = quadrants.selectAll("circle")
.data(function(d) { return d3.range(d); });
// enter
dots.enter().append("svg:circle")
.attr("cx", function() { return size * Math.random(); })
.attr("cy", function() { return size * Math.random(); })
.attr("r", 3);
// update
dots.attr("cx", function() { return size * Math.random(); })
.attr("cy", function() { return size * Math.random(); });
// exit
dots.exit().remove();
4. Using a multiply is faster than using an interpolator. So, `size *
Math.random()` is faster than `interpolate(Math.random())`. Of course,
this is just test code, and I'm assuming you'll be using a scale or an
interpolator in the future, so this optimization probably won't help
much. :)
5. Using external stylesheet is faster than setting attrs/styles on
each element:
circle {
fill: blue;
fill-opacity: .3;
}
Putting it all together:
Mike