4
\$\begingroup\$

This page (full source & demo) displays a 2-channel horizontal d3.js Streamgraph that takes realtime mouse coordinates as data inputs for the graph.

However, it tends to gradually use up an increasing amount of memory until there's none left, both on Chrome and Firefox (on Ubuntu 12.04 x64). Data is being collected but not released, yet the arrays that hold the incoming mouse coord data are being popped for every push, and I'm missing where the problem is occuring.

I'm working on profiling it, but wondering if any JS experts can eyeball it and see it. The only dependencies are JQuery and D3.js v3.

<body>
<div id="chart"></div>
<script>

    var l = 2; // number of stream channels (layers)
    var n = 100;  // number of samples per layer
    var random = d3.random.normal(0, .2);
    var a = [];
    var b = [];

    /*  Normalize mouse coords to a 0:1 range for d3. */ 
    var denom = { x: $(window).width(), y: $(window).height() };
    var currentMouseCoord = { x: 1, y: 1 };

    /*  While mouse is moving over the streamgraph iframe, replace data stream with realtime normalized mouse coords */
    $(document).mousemove(function(e) {
        currentMouseCoord.x = e.pageX/denom.x;
        currentMouseCoord.y = e.pageY/denom.y;
    }).mouseover();

    /*  When mouse leaves the streamgraph iframe, reset data stream to constant 1 */
    $(document).mouseout(function(e) {
        currentMouseCoord.x = 1;
        currentMouseCoord.y = 1;
    }).mouseout();

    function stream_layers(l, m, o) {
        return d3.range(l).map(function(d,i) {
            if (i == 0) {
                for (idx = 0; idx < m; idx++) a[idx] = currentMouseCoord.x;
                return a.map(stream_index);
            } else if (i ==1) {
                for (idx = 0; idx < m; idx++) b[idx] = currentMouseCoord.y;
                return b.map(stream_index);
            }
        });
    }

    function update_layers(l, m, o) {
        return d3.range(l).map(function(d,i) {
            if (i == 0) {
                a[m] = currentMouseCoord.x;
                return a.map(stream_index);
            } else if (i == 1) {
                b[m] = currentMouseCoord.y;
                return b.map(stream_index);
            }
        });
    }

    function stream_index(d, i) {
        return {x: i, y: Math.max(0, d)};
    }

    var data0 = d3.layout.stack().offset("wiggle")(stream_layers(l,n,0));
    var data1 = d3.layout.stack().offset("wiggle")(stream_layers(l,n,0));

    //var color = d3.interpolateRgb("#aad", "#556");
    var color = d3.interpolateRgb("#f30", "#fdb");

    var w = $(window).width();
    var h = 150;
    var mx = n - 1;
    var my = d3.max(data0.concat(data1), function(d) {
            return d3.max(d, function(d) {
            return d.y0 + d.y;
            });
        });

    var area = d3.svg.area()
        .x(function(d) { return d.x * w / mx; })
        .y0(function(d) { return h - d.y0 * h / my; })
        .y1(function(d) { return h - (d.y + d.y0) * h / my; });

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

    vis.selectAll("path")
        .data(data0)
        .enter().append("path")
        .style("fill", function() { return color(Math.random()); })
        .attr("d", area);

    function transition() {

        a.push(.01 + .01*Math.random());
        b.push(.01 + .01*Math.random());
        data0 = d3.layout.stack().offset("wiggle")(update_layers(l,n,0));

        vis.selectAll("path").data(data0).attr("d", area).attr("transform", null).transition().duration(40).ease("linear").attr("transform", "translate(" + -w/n + ")").each("end", function (d,i) { if (i==0) transition();});

        a.shift();
        b.shift();
    }

    $(document).ready(transition());

</script>

\$\endgroup\$
  • 1
    \$\begingroup\$ You use $(window) and $(document) quite a bit, so you might want to wrap with an IIFE and pass document and window into to it. \$\endgroup\$ – Jonny Sooter Mar 24 '13 at 20:43
  • 1
    \$\begingroup\$ I profiled your code on Chrome, your memory usage is stable, everything gets properly de-allocated. I suspect the problem is in WebGL, do you have up to date video drivers ? \$\endgroup\$ – konijn Dec 17 '13 at 18:58
  • \$\begingroup\$ Thanks, I was using a computer with an i3 with integrated gpu only, so that was probably the problem. Much appreciated! \$\endgroup\$ – Kurtosis Dec 22 '13 at 4:07
3
\$\begingroup\$

I am happy your problem is solved. All in all, your code looks solid, my review is mostly nitpicking:

  • I am all for spartan coding, but you have far too many 1 and 2 character variables, you ought to use more meaningful variables.

  • Try to have 1 var block at the top. More specifically, you declare data0 after declaring the function stream_layers, there is no need for that.

  • Please use lowerCamelCase coding, so update_layers -> updateLayers

  • There is no need to call .mouseover(); after setting the listener, especially since you also call .mouseout(); afterwards for which there is also no need as you already set x and y to 1 with var currentMouseCoord = { x: 1, y: 1 };

  • You can drop curly braces after loops, please don't drop newlines.

  • vis.selectAll("path").data(data0).attr.... in transition() really should be broken out into multiple lines.

\$\endgroup\$
  • \$\begingroup\$ Thanks, I'll make a pass on the production version incorporating your suggestions! \$\endgroup\$ – Kurtosis Jan 5 '14 at 21:52

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.