d3.js Force layout only applied vertically - javascript

I'm doing a data visualisation with d3. To give you some context,
the graph contains about 400 nodes (all data is loaded from multiple
json files) that are connected to each other
they are all mapped by year in a timeline (x axis)
the position in the y axis is completely randomized
the nodes have all different sizes
Now my question:
How can I distribute the nodes in the y axis so that they don't overlap?
You can checkout the full sourcecode on the GitHub Repository (work in progress - currently on the real-database branch).
This is a screenshot of how it currently looks:

Basically, in the tick() function, reset the nodes array x values to what you want them to be (presumably some scale to do with year), and the node and links will be drawn at those x values, and subsequent force calculations will start again from those values too
force.on("tick", function() {
// Adjust to what you want nodePos to be, here I'm just doing it by index
graph.nodes.forEach (function(nodePos,i) {
nodePos.x = i * 15;
//nodePos.x = xscale (data[i].year); // or whatever
})
// then normal node/link layout
I've forked this standard force-directed example by blt909 to show one way it could be done -->
http://jsfiddle.net/vztydams/
PS If you have a lot of items and very few discrete x values, best to give them a bit of wiggle room at first (i.e. a range in x they're contained to rather than a value), then slowly narrow that range down. Otherwise nodes will get 'stuck' behind each other.
Edit 02/03/16:
Hi Alvaro, essentially the graph.nodes is your linked data, as these are the objects that are attached to the displayed nodes as the data.
So if I set up a scale, and stick in a random year per datum:
var dom = [1994,2014];
var xscale = d3.scale.linear().domain(dom).range([20,400]);
graph.nodes.forEach (function(datum) {
datum.year = dom[0] + Math.floor (Math.random() * (dom[1] - dom[0]));
});
...
We can then restrict the x position of each node's datum like this:
graph.nodes.forEach (function(d,i) {
//d.x = i * 15;
d.x = xscale(d.year);
})
(As I say, if you have a lot of nodes and few years, you'd be better restricting to a range and then narrowing that range down on each subsequent tick)
http://jsfiddle.net/vztydams/2/

Related

Bubble chart c3js

Following the c3js documentation there is no option for Bubble chart. One workaround for that is to setup scatter plot and specify point radius, but all of the bubbles will be the same height.
point = {
r: function(d) {
var num = d.value;
return num
},
Adding the value of axis inside the r solve the problem, but now the problem is how to setup very high or very low values ? For e.g if there is 1 000 000 value the whole chart will be colored. Is there any simple workarounds for that ?
First of all, set r to return the square root of your chosen variable e.g. return sqrt(num), that way a circle representing a data point 100 times the size of another has 100, not 10,000, times the area (area=pi r2 and all that)
If the numbers are still too big use a linear scale to restrict them to a usable size:
rscale = d3.scale.linear().domain([1,1000]).range([0,10])
and then return rscale(sqrt(num))
If your problem is to represent large and small values on the same chart so small values don't disappear and large values don't exceed the chart size look at using a d3 log scale:
rscale = d3.scale.log().base(10).domain([1,1000]).range([0,10])
Of course on a log scale the areas aren't linearly proportionate any more so whether the sqrt step is necessary is debatable. If you don't just remember to adjust the domain to account for this - change it to domain([1,1000000])
if you don't know the size of your numbers beforehand it will be worthwhile looping through your dataset to pick out the min and max to plug into the domain value: domain([your_min, your_max]). my examples above all assume a max of one million.
Here's an example I forked on jsfiddle, numbers from a few hundred to over a hundred thousand are displayed using a log scale and all are visible but the differences are still obvious:
http://jsfiddle.net/m9gcno5n/

Using d3.js, how can I display data faster on my chart?

In my code, I am loading a JSON with 508 entries on a line chart. This JSON contains data emitted by some machines, and the keys are the names of the machines.
This is the structure of my JSON:
{
"AF3":3605.1496928113393,
"AF4":-6000.4375230516,
"F3":1700.3827875419374,
"F4":4822.544985821321,
"F7":4903.330735023786,
"F8":824.4048714773611,
"FC5":3259.4071092472655,
"FC6":4248.067359141752,
"O1":3714.5106599153364,
"O2":697.2904723891061,
"P7":522.7300768483767,
"P8":4050.79490288753,
"T7":2939.896657485737,
"T8":9.551935316881588
}
I am currently reading the data with the help of a counter called cont, however, the code that I'm using takes too long to draw the graph:
data.length=508
if (data.length>cont)
cont++`
for (var name in groups) {
var group = groups[name]
group.data.push(aData[cont][name])
group.path.attr('d', line)
console.log(cont)
}
As you can see in the gif above, my code is taking too long to plot all the data points. I would like to draw all the data elements of my data set (in this case 508) without delay, for example:
data=[{508 elements}];
tick(data)=> draw the points in the graph at the same time, by dataset.
data2=[{50 elements}];
tick(data)=> draw the points in the graph at the same time, by dataset.
Where tick is the name of the function that would draw the coordinates, without losing the sense of animation.
How can do it?
Here is a link to my code:
http://plnkr.co/edit/y8h9zs1CpLU1BZRoWZi4?p=preview
It seems to me that your problem is the fact that the graph is synchronous - "duration" is both used for animation and for graph shifting. Essentially, changing duration will avail nothing.
You can introduce a time multiplier. Then try dividing duration by two, and using a multiplier of 2. Your actual data duration is now duration*timeMultiplier (you might want to change the names to make it less confusing, or use a timeDivider in the animation).
// Shift domain
x.domain([now - (limit - 2) * duration * timeMultiplier, now - duration * timeMultiplier])
// Slide x-axis left
axis.transition()
.duration(duration)
.ease('linear')
.call(x.axis);
// Slide paths left
var t = paths.attr('transform', null)
.transition()
.duration(duration)
.ease('linear')
t.attr('transform', 'translate(' + x(now - (limit - 1) * duration * timeMultiplier) + ')')
.each('end', tick)
Another thing you might try is to add the points two at a time, i.e. you skip the shift on odd ticks, and shift double the amount on even ticks. This reduces the overhead at the expense of making the animation a bit jaggier (but not very much, because it also plays faster).

Can't reference data objects in d3js

I can't wrap my head around manipulating the data object in d3js. I'm planning to create a chart composed of horizontal bars to hold data elements. Each data element is a circle. I figured out how to insert circles into the different bars, but I'm stuck on how to equally space the circles in each bar. For example, if the width is 800 and there are 8 circles, the x attribute should be 100*i.
Here's a link to my project: https://plnkr.co/edit/fHrdJsItEqA5qc35iUxG?p=preview
I think the problem is how to reference the data object in this block of code. Anyways, I would like to equally space the circles using scaleBand which I defined as variable x earlier in my code:
var x = d3.scaleBand()
.range([0,width]);
I think the solution would look something like this: .attr("x",x.domain(data.map(function(d,i){return d[i]})); x.bandwidth(), but obviously data is not the right object.
Selecting each bar and inserting circles:
bar.selectAll("rect")
.data(function(d,i){console.log(data_group[i].values.length);return data_group[i].values})
.enter().append("circle")
.attr("class","circle")
.attr("width", width)
//.attr("x",) //how to equally space these circle elements???
.attr("height",20)
.attr("y", y.bandwidth())
console.log(y.bandwidth());
As always, I would really appreciate your help.
There are a number of issues with your code that are preventing it from working, including:
You aren't setting a domain for your x scale.
You are attempting to place <circle>s inside of <rect>s but you cannot nest shapes in SVGs. You should place both inside of a <g>.
A <circle>'s position is set using the cx and cy attributes (and you also need to provide it an r radius attribute).
To address your question, you will need to determine how you want your items laid out. Because you are referencing the index in your question, I will use that.
You are breaking your data into nested groups where each one has a values array. You are rendering a <circle> for each datum in that array, so you will want to determine the length of the longest values array.
var longest = data_group.reduce(function(acc, curr) {
return curr.values.length > acc ? curr.values.length : acc;
}, -Infinity);
Once you have the length of the longest values array, you can set the domain for your x scale.
You are using d3.scaleBand (d3.scalePoint would probably work better here), which is an ordinal scale. Ordinal scales work on discrete domains, which means that you will need to have a domain value for each possible input (the indices). For this, you will need to generate an array of the possible indices from 0 to longest-1.
var domainValues = d3.range(longest);
Now that you have the input domain values, you can set them for the x scale.
x.domain(domainValues);
Then, for each <circle>, you will set its cx value using the index of the circle in its group and the x scale.
.attr('cx', function(d,i) { return x(i); })
As I mentioned in the beginning, there are other errors in your code, so just fixing this won't get it running correctly, but it should push you in the right direction.

Automatically resize d3.js chart y scale based on brush selection

I'm using d3.js v4.
Is there a more convenient way to find the minimum and maximum values of a brush selection. This is meant to resize the y axis when I select a period in my brush area below.
Here is my method (everything is inside my function called when the brush is used) :
I find the extent limits of my selection
extent = d3.event.selection.map(chartComponent.x2().invert))
Then I have to redo an array containing all my selected points: I go on each point and compare it to the extent[0] or extent[1] to see if it is in the limits. I store the beginning and end point indices and then I use data.slice(begin, end) on my original data to get a new array.
Then apply d3.min and d3.max on the new array to find the min and the max level.
Then set the y axis to use theses limits.
chart.y().domain([chartComponent.ymin, chartComponent.ymax]);
chart.yaxisGraph.call(chartComponent.yAxis(true));
Do someone have a better idea ?

Scale in Bilevel Partition in D3

I am trying to make the Bilevel Partition in D3 work with log scale.
However, it doesn't seem to be working properly.
I have specified a log scale for angles:
var angle1 = d3.scale.log()
.base(2.0)
.domain([0, 2 * Math.PI])
.range([root.x, root.x + root.dx]);
However, this applies only when the angles are recalculated on zoom.
I have tried to modify the original partition scale, but with no success.
Any hints appreciated. See example code at the link below.
http://bl.ocks.org/mbostock/5944371
The partition layout in D3 sums up the values of the leaf nodes in order to calculate layout of the the elements with children. By definition of layout it should word like that. Therefore, only leaf nodes values are taken into account when calling layout.nodes(). Hence, only leaf nodes can be scaled (for example as log(count + 1)). However, non-leaf nodes will be represented sums of underlying values. The only option for all nodes to scale logarithmically would be to write a new layout, that would take into account a value at each node, and not sum up the values of the children.

Categories