Use multiple shapes on a force directed graph

2,352 views
Skip to first unread message

vinod

unread,
Dec 22, 2011, 11:15:46 AM12/22/11
to d3-js
I'm trying to set the shape of a node based on the value of an element
within the node list. In looking at the examples I could find all
circles or a randomly generated shape. Any advise on how I could
implement this. Here's some additional context:

Sample JSON:
nodes[{"name":"foo", "shape": 1},{"name":"bar", "shape": 2}]

Now on my force graph I want foo represented as a circle but bar
represented as a square.

Thanks!

Mike Bostock

unread,
Dec 22, 2011, 11:31:07 AM12/22/11
to d3...@googlegroups.com
Start with this one:

http://bl.ocks.org/1062383

Then you could use an ordinal scale to map the names to shapes, such as:

var type = d3.scale.ordinal()
.domain(["foo", "bar"])
.range(d3.svg.symbolTypes);

Then, change the type accessor for the shape generator so that it's
type(d.name) rather than d.type.

Mike

vinod

unread,
Jan 24, 2012, 3:53:16 PM1/24/12
to d3-js
Thanks Mike! Haven't had a much time to work on this yet, but I get
how I can get to the solution. This is really amazing work.


On Dec 22 2011, 10:31 am, Mike Bostock <mbost...@cs.stanford.edu>
wrote:

vinod

unread,
Jan 25, 2012, 4:15:27 PM1/25/12
to d3-js
I was able to get past the first hurdle, now I'm stuck with a
different problem. Now that I have the different shapes, I can no
longer select by shape (because they are paths) How could I select a
specific shape to transform it? I'm looking to move the shape within
the canvas, but the new x and y coordinates would vary by shape type.

On Dec 22 2011, 10:31 am, Mike Bostock <mbost...@cs.stanford.edu>
wrote:

Mike Bostock

unread,
Jan 25, 2012, 7:20:47 PM1/25/12
to d3...@googlegroups.com
You've got at least two options.

The first is you could use data to filter and/or compute the new x & y
coordinates dynamically. When you compute the shape type, you're
presumably using data, such as function(d) { return type(d.foo); },
where `d` is your data, `d.foo` is some property of your data object
that determines which shape is assigned, and `type` which is the
ordinal scale I mentioned previously. So, you could for example say:

selection.filter(function(d) { return d.foo == 42; })

to pull out a subset of your elements. Or you could recompute the
attributes for all of them, conditional on the data:

selection.attr("x", function(d) { return d.foo == 42 ? "0%" : "50%"; });

The second option is that you could assign a class to your elements
based on the shape type, and then use that class to reselect them
later. That might look like this:

selection.attr("class", function(d) { return type(d.foo); });

Then, later you can select just a specific shape type, and set new attributes:

d3.selectAll(".square")
.attr("x", …)
.attr("y", …);

Setting a shape-specific class is also nice if you want to style them via CSS.

Mike

vinod

unread,
Jan 26, 2012, 9:48:02 AM1/26/12
to d3-js
Mike, I'm not sure what I'm doing wrong here. I've pasted in relevant
snippets here to give you some idea of what might be going on. Please
advise on where you see the problem. Also what I'm looking for is a
animation where the symbols appear on the canvas and start the
animation after a short delay. In researching that I found (http://
christopheviau.com/d3_tutorial/)the each method that I can bind the
next action with(I'd like to chain the animation functions with a
short delay). Could you suggest some pseudo code to do that as well?

Here's what I'm currently doing:

1. Initialize the type:
var names = ["Browse","Search","Scan","WishList","Cart", "Buy"];
var type = d3.scale.ordinal()
.domain(names)
.range(d3.svg.symbolTypes);

2. Create and enter shapes:
nodes.push({
action: names[interaction],
user: user,
size: Math.random() * 300 + 100,
x: Math.floor(Math.random() * (300 - 20 + 1) + 20),
y: Math.floor(Math.random() * (800 - 20 + 1) + 20)
});
......

vis.selectAll("path")
.data(nodes)
.enter().append("svg:path")
.attr("transform", function(d) { return "translate(" + d.x + ","
+ d.y + ")"; })
.attr("d", d3.svg.symbol()
.size(function(d) { return d.size; })
.type(function(d) { return type(d.action); }))
.attr("class", function(d){return type(d.action);})
.....


3. Select the shape based on class
d3.selectAll(".circle")
.size(500)
.attr(x, 400);

Step 3 is where I seem to have lost it and don't see the shape moving
based on the new x attribute (I tried the transform as well and know
that isn't working either).

Thanks in advance!

Mike Bostock

unread,
Jan 26, 2012, 3:13:19 PM1/26/12
to d3...@googlegroups.com
Couple things wrong with this snippet:

> d3.selectAll(".circle")
>    .size(500)
>    .attr(x, 400);

* Selections don't have a size method. I'm guessing you meant to
recreate the path data, in which case you'd need to use attr("d", …),
and use your d3.svg.shape to recompute the path data with the new
size.

* Did you mean attr("x", 400) rather than attr(x, 400)? The variable
`x` doesn't exist in your code, though it's often used to refer to a
scale. If you were using circle elements, you could say attr("cx",
400) to change the center x-position of the circles. However, you're
using path elements, so you need to update the transform attribute
instead: attr("transform", "translate(400,0)").

Mike

vinod

unread,
Jan 26, 2012, 3:42:31 PM1/26/12
to d3-js
I was able to solve the first problem. Turns out i was using just x
as the attribute for the circle where I should have been using cx. But
if you have a solution for the transition and chaining of the paths
that would be great!

Transition don't seem to work with the transform, and I 'm also
looking for a way to fire a sequence of events that I can chain. How
could I use the each() method with the paths?

Mike Bostock

unread,
Jan 28, 2012, 2:57:29 PM1/28/12
to d3...@googlegroups.com
> Transition don't seem to work with the transform, and I 'm also
> looking for a way to fire a sequence of events that I can chain. How
> could I use the each() method with the paths?

For the first part, read this tutorial:

http://bost.ocks.org/mike/path/

There's also this example:

http://bl.ocks.org/1345853

I'm not sure I understand your second example. Typically you either
schedule multiple transitions with a delay, or you use the transition
"end" event to start another transition. There are lots of examples of
this around, such as the stacked-to-grouped bar chart example on the
website, and this one:

http://bl.ocks.org/1125997

Mike

Julia Jürgens

unread,
Feb 18, 2015, 11:06:18 AM2/18/15
to d3...@googlegroups.com, mbos...@cs.stanford.edu
Hi Mike, that is a pretty nice way to add different symbols. I'm currently working on a similar problem but I am either adding a circle or an svg:use. Can I use the same approach? Do I need to selectAll("path") also and append "svg:path"?

  currentsvg.selectAll("path")
        .data(queryArray[l])
        .enter()
        .append("svg:path")     
        .type(function(d,i) {
            if (queryArray[l][i].name.substr(0,1) == "Q"){
             return type("Q");  
            }
            else if (queryArray[l][i].name.substr(0,1) == "C"){
              return type("C");  
            }
         });

What is attr("d")?
Reply all
Reply to author
Forward
0 new messages