gextra
gextra

Reputation: 8885

How to use D3 force layout with existing SVG elements as nodes

I have a javascript array of objects where each object has .ui attribute that contains an existing SVG shape/element of various kinds ( a self contained "g" element with others inside ).

I want to arrange these objects as a force graph layout in D3. So, instead of having d3 create circles (or whatever) I want to assign one of my ui objects to the graph.

I have created a simplified fiddle << HERE >> for that. The objects that are pre-existing and should be part of the graph are the 3 colored rectangles. In this example I failed to achieve that. The d3 append command can be used just to construct shapes/elements, but not to append an already constructed one. I was trying to do this, and it cannot work in D3:

node.append( function(d) { d.ui.node(); }) ...

... as mentioned, this wont work as d3 does not append elements.

 var svgContainer = d3.select("#svgContainer");

// =============================================================    
// creates the SVG elements to be used ( 3 colored rectangles )
// =============================================================

var element0a = svgContainer.append("g").attr("transform","translate(100,100)");
var element0b = element0a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","red");

var element1a = svgContainer.append("g").attr("transform","translate(100,200)");
var element1b = element1a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","green");

var element2a = svgContainer.append("g").attr("transform","translate(100,300)");
var element2b = element2a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","blue");

// =============================================================
// fills up an object array that contains details plus the UI attribute
// =============================================================

var nodeArray = new Array();
nodeArray[0] = { id : "000", label : "label 000", ui : element0a };
nodeArray[1] = { id : "001", label : "label 001", ui : element1a };
nodeArray[2] = { id : "002", label : "label 002", ui : element2a };

// not interested in dealing with the edge/link right now, just empty
var linkArray = new Array();


// =============================================================
// D3 force layout stuff
// =============================================================

var force = self.force = d3.layout.force()
    .nodes(nodeArray)
    .links(linkArray)
    .gravity(.05)
    .distance(80)
    .charge(-100)
    .size([600, 600])
    .start();

var node = svgContainer.selectAll("g.node")
    .data(nodeArray)
    .enter().append("svg:g")
    .attr("class", "node")
    .call(force.drag);

// HERE (below) COMES THE ISSUE, where you see append("cicle") I want to append the nodeArray's ".ui" element. 

node.append("circle")          // <--- 
    .attr("class", "node")
    .attr("r",10)
    .attr("fill","orange")
    .call(force.drag);

force.on("tick", function() {
      node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")";});
    });

Upvotes: 3

Views: 4342

Answers (2)

gextra
gextra

Reputation: 8885

Based on advice from Lars Kotfoff :

Working fiddle here

If the elements to be graph-ed already exist, simply skip the enter() D3 phase and use select() or selectAll() based on a common characteristic of your elements ( a class name for instance ).

This is how the element got created ( added a specific class for allowing an isolated selection ):

var element0a = svgContainer.append("g").attr("class","node").attr("transform","translate(100,100)");
var element0b = element0a.append("rect").attr("x",0).attr("y",0).attr("width",20).attr("height",10).attr("fill","red");

This is thene part of the code replacing the enter() phase

var node = svgContainer.selectAll("g.node")
    .data(nodeArray)
    .call(force.drag);

This is what was removed

var node = svgContainer.selectAll("g.node")
    .data(nodeArray)
    .enter().append("svg:g")
    .attr("class", "node")
    .call(force.drag);

// HERE (below) COMES THE ISSUE, where you see append("cicle") I want to append the nodeArray's ".ui" element. 

node.append("circle")          // <--- 
    .attr("class", "node")
    .attr("r",10)
    .attr("fill","orange")
    .call(force.drag);

Upvotes: 3

Christopher Chiche
Christopher Chiche

Reputation: 15325

The problem is that you append the elements to svgContainer to create them, thus they are already attached to something.

What I would suggest as a workaround is to store the element properties in a json file and then read the configuration from the json.

There might be something more elegant.

Upvotes: 0

Related Questions