Heavy DOM Interaction

370 views
Skip to first unread message

ignacioola

unread,
May 12, 2011, 6:58:21 PM5/12/11
to d3-js
Hi,

Im am building a chart where multiple dots are drawn, similar to the
Scatterplot Matrix example (http://mbostock.github.com/d3/ex/
splom.html).

My problem is that when there's a high number of dots, the rendering
becomes very slow. I asume this is a consecuence of the amount of DOM
insertions.

Is there a posibility in a future for d3 to perform a single DOM
insertion for a set of data?

Thanks.

Mike Bostock

unread,
May 12, 2011, 7:16:51 PM5/12/11
to d3...@googlegroups.com
> Is there a posibility in a future for d3 to perform a single DOM
> insertion for a set of data?

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

ignacioola

unread,
May 13, 2011, 3:44:19 PM5/13/11
to d3-js
Yes, I could easily have 10,000 nodes, so I should probably change my
visualization.

I want to show density..

here's a simplified version of my code: http://jsfiddle.net/ignacioola/M4LfN/3/

Thank you very much.

Mike Bostock

unread,
May 13, 2011, 4:14:48 PM5/13/11
to d3...@googlegroups.com
> http://jsfiddle.net/ignacioola/M4LfN/3/

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:

http://jsfiddle.net/BLy3Z/2/

Mike

Reply all
Reply to author
Forward
0 new messages