I'm working on a node graph in d3 (seen here: http://jsfiddle.net/u56Tr/1) The graph itself is quite circular (links/nodes spread out relatively equally in all directions) - how would I make this fit better in a 16:9 window? I assume I'd have to expand the x,y positions of the elements but I can't quite get it right.
Relevant D3 code:
force.on("tick", function() {
link.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
node_bg.attr("cx", function(d) {
return d.x;
})
.attr("cy", function(d) {
return d.y;
});
node.attr("x", function(d) {
return d.x - (d.group > 0 ? 24 : 32);
})
.attr("y", function(d) {
return d.y - (d.group > 0 ? 24 : 32);
});
});
You can easily squeeze diagram to fit 16:9 better.
9/16 is .5625. In this fiddle you just look for lines that contain .5625, thats all you need to get your diagram squeezed.
And in this another fiddle you can see your diagram completely smashed, with smaller constant. :) .
Since diagram has a little chaotic look, maybe the good constant for your case would be around 0.4. that way it will fit 16:9 with more certainty.
The point of the force layout is to automatically lay out a graph like this so that you don't have to specify the positions of the nodes yourself. How it does that is largely affected by the connections between the nodes. The graph you have doesn't lend itself very well to being laid out in a wide screen format because of its high connectivity -- that is, many nodes are connected to many other nodes. If you were to stretch the graph, some links would become much longer than others and some nodes would be much closer together than others -- this is exactly what the force layout prevents.
You can of course manually "hack" the layout to achieve what you want -- you could, for example, specify different link distances/strengths for each link so that the ones you want to be larger are weaker. Similarly, you could specify a charge function that assigns a weaker repulsion charge to the nodes you want to be closer together.
However, for all of this you would need a pretty good idea of what you want the graph to look like when finished. It would probably be easier to ditch the force layout altogether and simply specify the positions of the nodes yourself.
Related
I am working on a bubble chart that needs to look like this - its likely to have just 2 series.
My main concern is what to do - if the bubbles are of the same size or if the situation is reversed.
I thought of using this jsfiddle as a base..
http://jsfiddle.net/NYEaX/1450/
// generate data with calculated layout values
var nodes = bubble.nodes(data)
.filter(function(d) {
return !d.children;
}); // filter out the outer bubble
var vis = svg.selectAll('circle')
.data(nodes);
vis.enter()
.insert("circle")
.attr('transform', function(d) {
return 'translate(' + d.x + ',' + d.y + ')';
})
.attr('r', function(d) {
return d.r;
})
.style("fill", function(d) {
return color(d.name);
})
.attr('class', function(d) {
return d.className;
});
vis
.transition().duration(1000)
vis.exit()
.remove();
Slightly updated your first fiddle http://jsfiddle.net/09fsu0v4/1/
So, lets go through your questions:
a bubble chart that needs to look like this - its likely to have just 2 series.
Bubble charts as part of pack layout rely on data structure - nested JSON with children array. Each node could be either root node (container) or leaf node (node that represents some end value). And its role depends on 'children' value presence. (https://github.com/d3/d3-3.x-api-reference/blob/master/Pack-Layout.md#nodes)
So, to get you chart looking like on picture just re-arrange json structure (or write children accessor function - https://github.com/d3/d3-3.x-api-reference/blob/master/Pack-Layout.md#children). Brief example is in updated fiddle.
if the bubbles are of the same size or if the situation is reversed
Parent nodes (containers) size is sum of all child nodes size. So if you have only one child node parent node will have the same size.
As far as you can't directly change parent node size - situation with oversized child nodes is impossible.
It might be easier if you start with this instead.
You could set the smaller bubble to be a child of the larger one.
As for when the series have the same size, I would either have a single bubble with both series given the same color, or I would separate the two bubbles. There isn't really much you can do when the bubbles need to be the same size.
I'm currently attempting to build a a multi-line graph with a d3.time.scale() for the x-axis.
I'm trying to add circles to each point on lines of the graph, but have been unsuccessful thus far.
When I do something like:
.attr('cx', function(d){ return x(d.price) })
I get a negative number.
I was thinking of setting up another scale (pointsScale) to handle this but have been largely unsuccessful.
What am I doing wrong?
Please refer to my JSBin for the code.
You're running into a few issues here:
Since you made the x-axis a time-scale, I'm guessing that you actually want price to be the y variable, while date is the x variable. That's why x(d.price) is negative - d3 is trying to interpret the prices as dates, which doesn't end up making much sense. So replace your line of code above with this: .attr('cy', function(d){ return y(d.price) })
In order to actually have circles be visible, they need to have three parameters set: cx, cy, and r. Since d3 already knows that your x axis is a time scale, you can set cx with .attr('cx', function(d){ return x(d.date) }). You can make r be whatever radius you want for the circles. Just choose one, or it will default to 0 and you won't be able to see the circles. .attr('r', 4), for instance, would set the radius to a perfectly visible value of 4.
You're drawing the circles before you draw the lines. As a result, the lines get drawn over the circles and it looks kind of weird. So move the circle code to after the line code if you want to avoid that.
Putting it all together, this is roughly what the code to create your circles should look like, and it should go after you declare var paths:
var circles = company.selectAll('circle')
.data(function(d){ return d.values; })
.enter().append('circle')
.attr('cy', function(d){
return y(d.price);}) //Price is the y variable, not the x
.attr('cx', function(d){
return x(d.date);}) //You also need an x variable
.attr('r',4); //And a radius - otherwise your circles have
//radius 0 and you can't see them!
Updated jsbin:
http://jsbin.com/gorukojoxu/edit?html,console,output
This is an issue that I have not discovered a clean fix for.
Within my step plot below:
http://jsfiddle.net/q47r3pyk/7/
You can see that I have a vertical blue line at the start of the chart.
This is because I added an additional x-value and y-value so that the first step will show up in the step plot.
If you look at
http://jsfiddle.net/q47r3pyk/8/
where I remove the first dummy entry with
x-value of "12-Jul-14"
y-value of 0
The first step of my step plot will have a width of zero.
Is there a recommended approach within d3.js for having the first step show without losing the last step by using step-after? or a fix for removing the vertical blue line that shows up with my hack of adding a dummy value?
Step is specified in
var line = d3.svg.line()
.x(function (d) {
return x(d.x);
})
.y(function (d) {
return y(+d.y);
})
.interpolate("step-after");
You could clip the path using SVG clip-path:
svg.append("clipPath").attr("id", "canvasClip")
.append("rect")
.attr("height", height)
.attr("width", width)
Then reference the clip-path on your line:
svg.append("path")
.datum(formatted_data)
.attr("class", "line")
.attr("d", line)
.attr("clip-path", "url(#canvasClip)")
Here's the fiddle, it seems to work fine:
http://jsfiddle.net/q47r3pyk/11/
But, ultimately, I think this is a case where the interpolator is acting as it should, and you really shouldn't be thinking about how to subvert it but rather whether it's the right interpolator for your data. In this case, I'd say no, and suggest you use "step" as your interpolator instead of "step-before" or "step-after". I think that would give you the result you're looking for and lines up well with your interactivity.
Is this help?
I have tried several times, i think it is the best result i have got, hope it helps!
x.domain(d3.extent(x_axis, function (d) {
return parseDate(d);
})).nice(x_axis.length);
fiddle
I have a d3 chart that displays two lines showing a country's imports and exports over time. It works fine, and uses the modular style described in 'Developing a D3.js Edge' so that I could quite easily draw multiple charts on the same page.
However, I now want to pass in data for two countries and draw imports and exports lines for both of them. After a day of experimentation, and getting closer to making it work, I can't figure out how to do this with what I have. I've successfully drawn multi-line charts with d3 before, but can't see how to get there from here.
You can view what I have here: http://bl.ocks.org/philgyford/af4933f298301df47854 (or the gist)
I realise there's a lot of code. I've marked with "Hello" the point in script.js where the lines are drawn. I can't work out how to draw those lines once for each country, as opposed to just for the first one, which is what it's doing now.
I'm guessing that where I'm applying data() isn't correct for this usage, but I'm stumped.
UPDATE: I've put a simpler version on jsfiddle: http://jsfiddle.net/philgyford/RCgaL/
The key to achieving what you want are nested selections. You first bind the entire data to the SVG element, then add a group for each group in the data (each country), and finally get the values for each line from the data bound to the group. In code, it looks like this (I've simplified the real code here):
var svg = d3.select(this)
.selectAll('svg')
.data([data]);
var g = svg.enter().append('svg').append('g');
var inner = g.selectAll("g.lines").data(function(d) { return d; });
inner.enter().append("g").attr("class", "lines");
inner.selectAll("path.line.imports").data(function(d) { return [d.values]; })
.enter().append("path").attr('class', 'line imports')
.attr("d", function(d) { return imports_line(d); });
The structure generated by this looks like svg > g > g.lines > path.line.imports. I've omitted the code for the export line here -- that would be below g.lines as well. Your data consists of a list of key-value pairs with a list as value. This is mirrored by the SVG structure -- each g.lines corresponds to a key-value pair and each path to the value list.
Complete demo here.
The point is that you're thinking to imperative. That's why you have so much code. I really can't put it better than Mike Bostock, you have to start Thinking with Joins:
svg.append("circle")
.attr("cx", d.x)
.attr("cy", d.y)
.attr("r", 2.5);
But that’s just a single circle, and you want many circles: one for each data point. Before you bust out a for loop and brute-force it, consider this mystifying sequence from one of D3’s examples.
Here data is an array of JSON objects with x and y properties, such as: [{"x": 1.0, "y": 1.1}, {"x": 2.0, "y": 2.5}, …].
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 2.5);
I'll leave translating this example to the "from one line to many lines" as an excerxise.
I am following a d3 force laytout example like this.
I want to control the speed of the dots flying to the cluster. In other words, I want some dots take more time to get to their final positions, while some dots take less time.
I tried to add a timer function to control the time of each tick, but it did not work.
this.force = d3.layout.force()
.on("tick", setTimeout(tick(d), 50));
I need help for this.
Don't set a timer to call the tick function, this is done automatically by the force layout.
There are however a number of parameters you can set to modify the behaviour of the force layout. The ones most relevant to what you're trying to do are the following.
.friction() corresponds to how quickly the velocity decays and therefore directly controls how fast nodes move. The default is 0.9, to make everything slower, set it to a lower value.
.charge() controls how strong the attraction/repulsion between nodes is. This doesn't control the velocity directly, but affects it.
Of these parameters, only the latter can be set on a node-by-node basis. This makes achieving what you want a bit tricky, as you would have to carefully balance the forces. As a start, setting the charge of the nodes that you want to move slower closer to 0 should help.
There are a few other parameters of the force layout that I think would not be useful in your particular case (I'm thinking of .linkStrength() and .linkDistance()), but you may want to have a look nevertheless.
I came to a possible solution. I can just manually call tick function for each node, say 100 times, and record the path. Then I use a timer function to redraw the node according to the path. In this way, I can control the time of drawing for each node. Does this make sense?
Change the setIntervel function , intervel timing
Try this code:
Fiddle:
setInterval(function(){
nodes.push({id: ~~(Math.random() * foci.length)});
force.start();
node = node.data(nodes);
node.enter().append("circle")
.attr("class", "node")
.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; })
.attr("r", 8)
.style("fill", function(d) { return fill(d.id); })
.style("stroke", function(d) { return d3.rgb(fill(d.id)).darker(2); })
.call(force.drag);
}, 50); //change the intervel time