3

I am running Django 1.5.1 and Python 2.7, along with D3.js v3.

I have created a GUI where a user can dynamically add / delete nodes from a D3.js collapsable indented tree (like this block, but with buttons for add / remove). This all works fine on the browser side.

However, I would now like to save the user's changes into my database, via Django / Python. Is it possible to "export" my data from d3 and pass it to a Django view? From the documentation, it seems no, or at least I didn't see any command for it. I have tried to do a simple POST using Ajax, but the problem is that d3.selectAll returns objects...which are slightly recursive and cause my browser's stack to overflow. Example code is here:

var nodes = d3.selectAll('g.node');
var root_node = nodes[0][0].__data__;
var children = root_node.children;

$.ajax({
    type: "POST",
    url: "update_map/",
    data: {
        'children': children,
        'bank': obj_bank_id
    },
    success: function( result ) {
        if (result == true) {
            lscache.set( 'canvas_clean', 'true' );
        } else {
            alert( 'Error in saving data' );
        }
    },
    error: function( xhr, status, error ){
        alert( error );
    }
});

The issue I see (using Chrome's developer tools) is that 'children' is essentially infinitely long--it is an array of objects, but within each object is a "parent" object that includes all of its children, etc. ad inifinitum. A chunk is below:

Object {source: "", item_class: "root", name: "Topics", children: Array[76], x0: 0…}
children: Array[76]
    0: Object
        _children: Array[0]
        bank: "objectivebank"
        children: null
        depth: 1
        id: 2
        item_class: "objective"
        item_id: "objective"
        name: "Antiderivative"
        parent: Object
            children: Array[76]
            depth: 0

So is there a way to get a "flat" view of all the nodes without d3 metadata either using built-in d3.js commands, or some other way? I would like something cleaner, but do I just have to save things in a separate object? Thanks!

user
  • 4,651
  • 5
  • 32
  • 60
  • 1
    Sounds to me like you have to "manually" construct the object, by initializing a blank one and then looping over the children (and their children) while building up the necessary tree. It shouldn't be that hard though. One d3 method to be aware of, if you're not already, is the `.each` method of a selection, which is probably what you'll want to use to loop over the children and gather their data or html strings. Also, this might be handy: within the handler of the `.each` loop, you can always call `d3.select(this).datum()` to get the data associated with a given child element. – meetamit Aug 05 '13 at 18:34
  • How many nodes are you dealing with? It seems unlikely you would overflow the call stack with a number of nodes the browser could possibly visualize. I wonder if you have a circular reference somewhere? – Scott Cameron Aug 05 '13 at 20:42
  • @ScottCameron, so there are only 77 nodes (root + 76 children). But you can see from my object snippet that the parent (plus its children) are embedded in each children object. This continues infinitely--in Firebug or Chrome, I can keep digging deeper and the levels never end. Each child (second line from the bottom) also has a parent object with 76 children, each of which has a parent with 76 children, etc. That seems to be the inherent nature of d3.js data nodes? – user Aug 06 '13 at 01:35
  • @meetamit, thanks for your tip, that lead me in the right direction! You are right--it was actually easy...the tree is constructed within the root node already, I just had to loop through and remove the "parent" attribute from each of the children to remove the recursiveness. Note that this attribute was automatically added by d3.js, not something I put in. – user Aug 06 '13 at 01:41
  • By navigating to the children of parent you're just going around in a cycle. My son's parent's child is my son. :) By looking at a child node's parent's children you're making a loop that will continue forever. You need to pick a direction and stick with it. Either start at root and navigate down "children" or start at a leaf node and navigate up "parent". – Scott Cameron Aug 06 '13 at 04:04
  • @ScottCameron, right, I realize that if I cycle through the nodes manually, I can control the direction...but is there a way to do it programmatically without having to cycle through all the nodes? Basically--if I make a tree and call d3.selectAll('g.node') and try to POST it somewhere via Ajax, my browser dies because the object returned by d3.selectAll seems infinitely recursive. Can I POST it somewhere without having to loop through it in a certain direction? – user Aug 06 '13 at 15:24
  • 2
    Oh, I see now. Serializing the object via jQuery.ajax is problematic because it's walking the entire object. I'm not sure what jQuery uses internally to serialize. You might be able to make some progress by setting "dataType" to "json". But more likely you'll need to serialize yourself. You can use JSON.stringify to turn the object into a string. stringify take a second arg for specifying what happens when it hits an object it has already seen. Here's a related SO question with more details: http://stackoverflow.com/questions/10392293/stringify-javascript-object-with-circular-reference. – Scott Cameron Aug 06 '13 at 16:20

1 Answers1

2

So I solved this before seeing Scott Cameron's link to another post--JSON.stringify with the filter seems like it would definitely work and have the same functionality as what I did. Basically I created a copy of d3.selectAll('g.node') and walked through it manually, as suggested by both meetamit and Scott Cameron. I then just deleted all references to parent plus the other d3.js metadata I didn't want. I then copied the root node and viola--had my serialized tree.

        var nodes = $.extend(true, [], d3.selectAll('g.node'));

    // Only need the 'root' nodes at depth 0, because it has all the children
    //   associated with it. Just strip out the unneeded metadata...
    var remove_d3_metadata = function(node_data) {
        // remove the d3 metadata
        delete node_data.parent;
        delete node_data.x;
        delete node_data.x0;
        delete node_data.y;
        delete node_data.y0;
        delete node_data.__proto__;

        var grandchildren = node_data.children;
        if (grandchildren != null) {
            $.each( grandchildren, function(index, grandchild) {
                remove_d3_metadata(grandchild);
            });
        }
    };

    nodes.each( function() {
        var node_data = d3.select(this).datum();
        if (node_data.depth == 0) {
            var children = node_data.children;
            if (children != null) {
                $.each(children, function(index, child) {
                    remove_d3_metadata(child);
                });
            }
        }
    });

    var root_node = nodes[0][0].__data__;
user
  • 4,651
  • 5
  • 32
  • 60