5

I'm working on a visualization of data with d3js and hierarchical layout. My data looks like this:

       0
     / | \
    /  |  \ 
   1   5   3
    \  |   |
     \ |   |
       4  /
       | /
       2

As I can't make link to multiple parent, I duplicates nodes on display:

       0
     / | \
    /  |  \ 
   1   5   3
   |   |   |
   |   |   |
   4   4   |
   |   |   |
   2   2   2

I've made a fiddle demo to show my problem:

  • When I use correct data in my JSON input, I have the good layout (graphic in border blue).
  • When I use a loop to parse my JSON input, I have strange graph (graphic in border green).

This is the loop I used to parse the input:

for (i in root[2].Arcs){
  var d = root[1].Nodes[root[2].Arcs[i].D];
  var s = root[1].Nodes[root[2].Arcs[i].S];
  if (!d.children){
    d.children = [];
  }
  d.children.push(s);
}

For me : both printed element in console are the same, but not the render of layout. There are maybe some differents in object reference.

A bad solution I found is to decode then encode my var :

    var root = JSON.parse(JSON.stringify(root));

Then the script works good. But I root is a long long array, the parsing takes long time...

Any idea to explain why I need to use that encode/decode to display same things ?

Thank you

Matthieu
  • 221
  • 1
  • 11
  • 1
    1. The json data for root is different than that of root2. 2. I have no idea what you're trying to do. Your variable names are meaningless letters like S or D, your code is totally uncommented. It is not clear what code goes to which technique. – Jacklynn Nov 25 '14 at 04:49
  • Yes it's not the same data : I need to parse root to have something looks like root2... The name or code are not important : I would like to know "why I don't have the same result when I use two variables that seem identical in all respects ?". – Matthieu Nov 25 '14 at 13:53
  • Also, check http://mdaines.github.io/viz.js/example.html . It might be more suited for your needs and eliminate your problem altogether ;) – Rob Audenaerde Nov 27 '14 at 08:10
  • I'm sure that the problem doesn't come from the library of viz. And I made a little jsfiddle demo, I have a lot of code in background using d3js. – Matthieu Nov 27 '14 at 08:22

4 Answers4

1

You are supposed to encode/decode JSON to prevent shallow copy. To know more what about deepcopy and shallowcopy, here is link. What is the difference between a deep copy and a shallow copy?

As var root = JSON.parse(JSON.stringify(root)); is a wrong way to prevent shallow copy, you can use jquery's clone method or simply javascripts slice method to deepcopy javascript array.

for example.

var d=[1,2,3,4,5,6];//create array
var b=d;//copy array into another array (shallow copy/reference copy)
var e=d.slice();//clone array in another variable (deep copy)
d[0]=9; //change array element
console.log(d)// result : [9,2,3,4,5,6] (changed array)
console.log(b)// result : [9,2,3,4,5,6] (changed array due to reference)
console.log(e)// result : [1,2,3,4,5,6] (unchanged array due to deep copy)

There is another solution is use underscore. If you don't want full javascript code of underscore, then you can pick cloning portion in underscore library.

In underscore, you can clone object array using:

var a = [{f: 1}, {f:5}, {f:10}];
var b = _.map(a, _.clone);       // <----
b[1].f = 55;
console.log(JSON.stringify(a));

It will Print

[{"f":1},{"f":5},{"f":10}]
Community
  • 1
  • 1
Laxmikant Dange
  • 7,606
  • 6
  • 40
  • 65
  • The shallow copy is what I do in the green border graph : ok it doesn't work. But I'm not convinced by your proposal: I tried use slice it's the same bug. – Matthieu Nov 27 '14 at 07:36
  • If your arrays is object array then the array elements(objects) are shallow copied. you need to clone that object too. – Laxmikant Dange Nov 27 '14 at 12:24
1

You can replace your for loop with something like this, don't know about performance though. jsfiddle

traverse(root[1].Nodes[0]);
function traverse(node) {
    for (i in root[2].Arcs) {
        var d = root[1].Nodes[root[2].Arcs[i].D];
        if (node.name === root[2].Arcs[i].D) {
            var s = root[1].Nodes[root[2].Arcs[i].S];
            var sCopy={
                "name": s.name
            }
            traverse(sCopy);
            if (!node.children) {
               node.children = [];
            }
            node.children.push(sCopy);
        }
    }
}
ylamaki
  • 156
  • 1
  • 3
  • 2
    About performance (file : 90ko, 780nodes and 906links): my little loop + json.stringify() + json.parse() is executed in ~26ms . Your solution : ~872.548ms. – Matthieu Nov 27 '14 at 08:06
  • That's too bad, suspected it would be slow. To answer your question why root does not work even though the data looks the same. The problem is that in your three the same object is added multiple times but it needs to be added as a different instance. For example both 1 and 5 has a child that points to the same instance of 4. – ylamaki Nov 27 '14 at 09:57
1

NOTE: The performance is not as good as the original post, see comments below. This way is meant to be scalable, so no matter what the object (and the flags look like), it should work

Answer

Here's another take on the "deep copy" approach as suggested by @transcranial and @LaxmikantDange.

I suggest using jquery (my preferred approach: write less, do more) and using its extend routine:

<<load jquery>>

root = $.extend(true, {}, root[1].Nodes[0]);
graph(root2,svg);
graph(root,svg2);

Make sure you have that true as first argument as can be seen here http://api.jquery.com/jquery.extend/ or here What is the most efficient way to deep clone an object in JavaScript? (top answer)

Not sure about performance, but I hope it's not too bad. If you test, please let me know!

Please also not that a deep copy might be questionable depending on your application (broken links etc). For instance, I recently built a react app that shares events between graphs - and sharing e.g. zoom events (that adjust the rendered object) are not "automatically shared" (as the objects are separate, if you know what I mean.

Community
  • 1
  • 1
Pinguin Dirk
  • 1,433
  • 11
  • 18
  • As some people talks about deep copy, I made some test with [cloning an object in JS](http://web.archive.org/web/20140328224025/http://jsperf.com/cloning-an-object/2). For my example : 780 nodes and 906 links, the cloning takes ~200ms with ```$.extend(true, {}, oldObject);```, ~50ms with ```JSON.parse(JSON.stringify(oldObject));``` and ~1800ms with @ylamaki solution using custom function – Matthieu Nov 28 '14 at 08:30
  • @Matthieu: thanks, interesting input! I like the `$.extend` way as it is "scalable", so when I change the object etc., it should still work. But if performance is an issue, I agree that the specific function is quicker. (added a note at the top of my answer) – Pinguin Dirk Nov 28 '14 at 08:36
0

When you push additional nodes (which are objects) onto your arrays of children nodes, those objects must be cloned (deep copied). Reassignment "=" only works for string or number arrays. Deep copying your children nodes before pushing them onto your array fixes the layout and the rendering.

Your original code for pushing children nodes into the array:

for (i in root[2].Arcs){
  var d = root[1].Nodes[root[2].Arcs[i].D];
  var s = root[1].Nodes[root[2].Arcs[i].S];
  if (!d.children){
    d.children = [];
  }
  d.children.push(s);
}

Modified:

for (i in root[2].Arcs){
  var d = root[1].Nodes[root[2].Arcs[i].D];
  var s = root[1].Nodes[root[2].Arcs[i].S];
  if (!d.children){
    d.children = [];
  }
  d.children.push(JSON.parse(JSON.stringify(s)));
}

Check it out: JSFiddle.

Also, the d3 hierarchical layouts including cluster don't support having multiple parents out of the box. For that, you can try using a force graph layout.

transcranial
  • 381
  • 2
  • 3
  • So you use json.parse then json.stringify too. I know that hierarchical layout not support multiple parents that's why I duplicates some path (it's not a problem to have this for me). – Matthieu Nov 27 '14 at 12:41
  • there are other ways, but this is probably the easiest. see [this](http://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object) and [this](http://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-clone-an-object). – transcranial Nov 27 '14 at 13:16