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.
Related
I'm creating a similar graph to this:
https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172
However when I'm zooming in to a certain section, I would like to scale the Y axis to the local (displayed) maximum, instead of the global one. For example when I zoom to the data between 2009 and 2010 there is a lot of empty white space at the top.
Basically what I would like to achieve is get the range to which I've zoomed, and get the maximum value within that.
The other possibility would be adding another brush bar on the side, but that would be very inconvenient on the long run for the users.
You just need to change the y scale domain for that.
First, let's create a global variable named globalData and associate the data array to it. Note: this is not the correct way to do this, but I'll do it simply because the brushed and zoomed functions lie outside d3.csv, which is asynchronous, and refactoring it takes some work... so, it will be your job refactoring it.
Then, in both the brushed and zoomed functions, we filter the data according to the brush:
var filteredData = globalData.filter(function(d){
return d.date > x.domain()[0] && d.date < x.domain()[1]
});
After that, we calculate the new y domain:
y.domain([0, d3.max(filteredData, function(d){
return d.price
})]);
Don't forget to call the axis again.
This is the updated bl.ocks: https://bl.ocks.org/GerardoFurtado/17fd6b82324e355c768992e78140fe9a/33b9a6c58265454864a9d921df032e708fad5237
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 ?
I suspect that the answer lies somewhere between my ignorance of what kind of object I'm actually working and the inner darkness of D3 and/or SVG.
I can see 30 circles on the screen. So I go in the console like this.
var circles = d3.select("#svg1").selectAll("circle");
>undefined
circles;
>[Array[30]]...
circles.length;
>1
I know I'm targeting the right elements because executing .remove() on the set clears them from the screen. But what's up with the count?
var circles = d3.select("#svg1").selectAll("circle");
circles is an array of array, hence the length is 1. That is the length of the outer array. If you want to get the count of circles selected then you should use d3s built-in method size.
var count = d3.select("#svg1").selectAll("circle").size();
This will give you the expected result.
I am going for this: http://examples.oreilly.com/0636920026938/chapter_10/14_div_tooltip.html except with custom data that has two fields: a "Month" and a "Ratio."
This my javascript code, which looks very similar to that on the link posted above. http://pastebin.com/KG2tX5Xm
The main differences would be in the scale (mine goes across months) and the x, y attributes which would need to be based off data.Ratio or data.Month.
When I view the source of my page, I see that the rectangles' coordinates do change after clicking; however, they are staying in position. Why is this, and how do I fix it?
The thing that changes when you .sort() the selection (of rect elements in this case) is the index of each element. The data does not change. The example you've linked to uses the index of the respective element to determine the x position of the bars after sorting and during redrawing. Hence, the positions of the bars change.
In your code, you're not using the index of the bars to determine their position at all. You're using the data bound to the elements, which does not change when sorting.
Your general approach is different from the one taken in the example you've linked to. There, the data itself is sorted (or rather the selection) and its order matters for the positions of the bars. In what you're doing, the order of the data does not matter because you're only using the data itself.
So to make the sorting have any effect, change your code to use the index to determine the position of the bars:
svg.selectAll("rect")
.sort(function(a, b) {
if (sortOrder) {
return d3.ascending(a.Ratio, b.Ratio);
} else {
return d3.descending(a.Ratio, b.Ratio);
}
})
.transition()
.delay(function(d, i) {
return i * 50;
})
.duration(1000)
.attr("x", function(d, i) {
return i * x.rangeBand();
});
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.