So I am having a problem when following a very simple d3 tutorial. Essentially I am attempting to add SVG circle elements by binding data to the current svg circle elements then calling enter.append As shown below:
var svg =d3.select("body").selectAll("svg");
var circle = svg.selectAll("circle");
var w=window.innerWidth;
console.log(circle);
circle.data([500,57,112,200,600,1000]);
circle.enter().append("circle")
.attr("cy",function(d) {
return (d)
})
.attr("cx", function(i){
return (i*100)
})
.attr("r", function(d) {
return Math.sqrt(d);
})
Which seems like it would add 3 new circle elements (because I already have 3 created). However, instead of getting these 3 new circle elements added, I am running into this error:
Uncaught TypeError: Object [object SVGCircleElement],[objectSVGCircleElement],[object SVGCircleElement] has no method 'enter'
I have done essentially the same thing with paragraphs, and it seems to work fine:
var p =d3.select("body").selectAll("p")
.data([4, 8, 15, 16, 23, 42])
.text(function(d) { return "I'm number " + d + "!"; });
//Enter
p.enter().append("p")
.text(function(d) { return "I'm number " + d + "!"; })
.style("color", function(d, i) {
return i % 2 ? "#000" : "#eee";
});
However as soon as I try to add SVG Elements into it, I continue to get the same error.
It seems like there should be just a syntax error or something, but I have gone through the code 5 trillion times, and can't find anything.
Any help is much appreciated, and thank you before hand for your time.
Isaac
You want to call enter on the result of circle.data instead of circle itself.
var circle = svg.selectAll("circle");
circle.data([500,57,112,200,600,1000]).enter().append("circle");
You did this correctly in your p example by storing the return of data in the p variable. Whereas in your circle example, you are storing the return of d3.select.selectAll in your circle variable.
Related
I am using d3v4 and within the html document I have 20 g.node elements, with 4 having the value: d.nameY === "responded No".
The following statement works correct:
d3.select("g.sankeyFrame.single")
.selectAll("g.node")
.filter( function(d) { return d.nameY === "responded No";})
.size();
It returns 4 as desired.
However, the following data join doesn't return the expected selection:
d3.select("g.sankeyFrame.single")
.selectAll("g.node")
.data(["responded No"], function(d){ return d.nameY;})
.size();
It returns 0, it should return all 4 g.node elements, where d.nameY === 4.
What possible reason prevents the data join from working?
(The code base is too large to publish here)
What possible reason prevents the data join from working?
No reason at all. It is working. And the result is not wrong, that's the expected behaviour. You have another problem here.
To show you the problem, let's create a very simple demo, which appends 20 <div>s, 4 of them with the datum "foo". Here is the code using your first approach, with filter:
var p = d3.select("body")
.selectAll(null)
.data(d3.range(20).map(function(d) {
return d < 4 ? "foo" : "bar"
}))
.enter()
.append("div")
.html(String);
var filtering = d3.selectAll("div")
.filter(function(d) {
return d === "foo"
}).size();
console.log("size is: " + filtering)
<script src="https://d3js.org/d3.v4.min.js"></script>
Now let's see your second approach:
d3.select("g.sankeyFrame.single")
.selectAll("g.node")
.data(["responded No"], function(d){ return d.nameY;})
.size();
You have two problems here:
The key function will not work. There is no property nameY in a simple, flat array like ["responded No"]. That's why your selection's size is 0.
There is just one element in that array. So, even if you remove the wrong key function, that selection will have just one element. Let's prove it:
var p = d3.select("body")
.selectAll(null)
.data(d3.range(20).map(function(d) {
return d < 4 ? "foo" : "bar"
}))
.enter()
.append("div")
.html(String);
var dataBinding = d3.selectAll("div")
.data(["baz"])
.size();
console.log("size is: " + dataBinding)
<script src="https://d3js.org/d3.v4.min.js"></script>
That point #2 is the most important here: the size of the update selection returned by data() is the size of its array.
Finally, to clearly show that the data() is working, let's see the datum of each <div>:
var p = d3.select("body")
.selectAll(null)
.data(d3.range(20).map(function(d) {
return d < 4 ? "foo" : "bar"
}))
.enter()
.append("div")
.html(String);
var dataBinding = d3.selectAll("div")
.data(["baz"])
.size();
d3.selectAll("div").each(function(d, i) {
console.log("div number " + i + " - datum: " + d)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
I do not manage to update a bar-chart with nested data in D3.js with new data.
I have nested data of the form:
data = [[1,2,3,4,5,6],[6,5,4,3,2,1]];
I managed to visualize the data by first appending a group for every subarray.
In the groups I then add the arrays as data (simplified):
function createGraph(l, svg){
var g = svg.selectAll("g")
.data(l)
.enter().append("g");
var rect = g.selectAll("rect)
.data(function(d){return d;})
.enter().append("rect")
. ...
}
However, when call the function again with different data, nothing happens.
It seems like in the second row, the rects do not get updated.
I have created a full example over at jsBin: http://jsbin.com/UfeCaGe/1/edit?js,output
A little more explanation of Lars' bug-catch, since I'd already started playing around...
The key was in this section of the code:
var group = svg.selectAll("g")
.data(l)
.enter().append("g");
The variable group is assigned the enter selection, not the raw selection. Then in the next line:
var bar = group.selectAll("rect")
.data(function(d){
return d;
});
You end up defining bar as only the rectangles that are children of just-entered groups. So even though you were handling update correctly for the rectangles, that whole section of code wasn't even running. You need to save the group selection before branching the chain to deal with entering groups:
var group = chart.selectAll("g")
.data(dt);
group.enter().append("g");
var bar = group.selectAll("rect")
.data(function(d){
return d;
});
Also, you're missing a j in your function declaration in your update. And you can reduce code duplication by putting your rectangle update code after your rectangle enter code, and then any attributes that get set in the update don't have to be specified for enter. (Some older examples don't use this pattern, because the original versions of d3 didn't automatically transfer newly-entered elements to the main selection.)
// enter
bar.enter().append("rect")
.attr("fill", function(d,i,j){
return colors(j);})
.attr("height", 0);
// update
bar.attr("transform", function(d, i, j) {
x = "translate("+(i*2.2*w+j*w)+",0)";
return x; })
.transition()
.duration(750)
.attr("width", w)
.attr("height", function(d){return d*10;});
I having trouble getting the data on the graph. I only get one data set bar in.
You can see it here : http://infinite-fjord-1599.herokuapp.com/page2.html
But when I console.log the foreach for it. It displays all the objects:
data.days.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d.values[name]}; });
console.log(d.ages);
});
The code on jsFiddle. http://jsfiddle.net/arnir/DPM7y/
I'm very new to d3.js and working with json data so I'm kinda lost here. I took the example of the d3.js example site and modified it.
See the updated fiddle here: http://jsfiddle.net/nrabinowitz/NbuFJ/4/
You had a couple of issues here:
Your x0 scale was set to a domain that displayed a formatted date, but when you were calling it later you were passing in d.State (which didn't exist, so I assume it was a copy/paste error). So the later days were being rendered on top of the first day.
There was a mismatch between the way you were selecting the group g element and the way you were appending it - not actually a root cause here, but likely to cause problems later on.
To fix, move the date formatting to a different function:
function formatDate(d) {
var str = d.modified;
d.date = parseDate( str.substring(0, str.length - 3) );
var curr_month = d.date.getMonth() + 1;
var curr_date = d.date.getDate();
var nicedate = curr_date + "/" + curr_month;
return nicedate;
}
and then use the same function for the scale setup:
x0.domain(data.days.map(formatDate));
and the transform (note the fix in the selector and class here as well):
var state = svg.selectAll("g.day")
.data(data.days)
.enter().append("g")
.attr("class", "day")
.attr("transform", function(d) {
return "translate(" + x0(formatDate(d)) + ",0)";
});
There are a couple of small things that threw you off. First, the domain of the x0 scale should be an array of datetime objects, not an array of strings:
x0.domain(data.days.map(function(d) {
var str = d.modified;
d.date = parseDate( str.substring(0, str.length - 3) );
return d.date;
}));
will return datetimes, not strings like it was before (minor nitpick: really not a fan of this use of map, I would add the date property separately in a forEach function as the data is loaded).
Second, x0 needs to be passed a property that actually exists:
var state = svg.selectAll(".state")
.data(data.days)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.date) + ",0)"; });
Before, you were using x0(d.state) which is a vestige from the grouped bar example (several others still exist; I've changed the minimum to get your project working). Since the value didn't exist, all of the rectangles were getting drawn over each other.
Additionally, we need to format the axis labels so we aren't printing out the entire datetime object all over the labels:
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom")
.tickFormat(d3.time.format("%m-%d"));
Finally, I noticed that the newest dates were being printed on the left instead of the right. You could sort the results of data.days.map( ... ) to fix that, I just reversed the range of x0:
var x0 = d3.scale.ordinal()
.rangeRoundBands([width, 0], .1);
fixed files
I'm confused about data joins.
I have an entering group element, called gEnter, to which I append
gEnter.append("g").attr("class", "dataLabels");
dataLabels is the container element for each data label I will make.
g is the update selection for the original group element. I bind my data like this:
var dataLabels = g.select(".dataLabels")
.selectAll(".dataLabel")
.data(function(d) {return d;});
where d is coming from the parent g element. For each new data point I append a .dataLabel, and give it a starting position 30 pixels up from the axis:
var dataLabelsEnter = dataLabels.enter()
.append("g")
.attr("class", "dataLabel")
.attr("transform", function(d, i) { return "translate("+ (xScale(d.category) + (xScale.rangeBand() / 2)) +","+(yScale(0) - 30)+")"; });
Each .dataLabel is itself a container for two text elements, so I append them for each new data point:
dataLabelsEnter.append("text")
.attr("class", "category")
.attr("text-anchor", "middle")
.style("font-weight", function(d, i) {
return (d.category == 'Total')
? 'bold'
: 'normal';
})
.text(function(d) {return d.category;});
dataLabelsEnter.append("text")
.attr("class", "value")
.attr("text-anchor", "middle")
.attr("transform", "translate(0,20)")
.style("font-weight", "bold")
.style("fill", function(d, i) {
return (d.count >= 0)
? '#1f77b4'
: '#BB1A03';
})
.text(function(d) {
var accounting = d3.format(",");
return (d.count >= 0)
? '+$' + accounting(d.count)
: '-$' + accounting(-d.count);
});
I then move to my update code, where things get interesting. First, I update the position of the container .dataLabel element. This works well:
dataLabels
.transition()
.duration(duration)
.attr("transform", function(d, i) {return "translate("+ (xScale(d.category) + (xScale.rangeBand() / 2)) +","+( yScale(d3.max([d.count,0])) - 30)+")"; });
Now I want to update the values of my labels. I try this:
dataLabels
.selectAll(".value")
.text(function(d, i) {
var accounting = d3.format(",");
// return d.count;
return (d.count >= 0)
? '+$' + accounting(d.count)
: '-$' + accounting(-d.count);
});
but it doesn't work. I try rebinding the data, using a .data(function(d){return d;}), but to no avail. No matter what I do, even if the data updates, here it's still the same as the initial draw. However, if I switch to
dataLabels
.select(".value")
.text(function(d, i) {
var accounting = d3.format(",");
// return d.count;
return (d.count >= 0)
? '+$' + accounting(d.count)
: '-$' + accounting(-d.count);
});
it works.
Can anyone explain why the latter selection gets the updated the data, but the former selection doesn't? I've read Mike Bostock's recent article on selections, but am still a little confused. I believe it has something to do with this sentence from the article:
Only selectAll has special behavior regarding grouping; select preserves the existing grouping.
Perhaps selectAll is creating new groups from each .dataLabel element, but the data is not being bound to them? I'm just not sure.
The difference is that selection.select propagates data from parent to child, whereas selection.selectAll does not. Read the paragraph you quoted again, in Non-Grouping Operations section:
Only selectAll has special behavior regarding grouping; select preserves the existing grouping. The select method differs because there is exactly one element in the new selection for each element in the old selection. Thus, select also propagates data from parent to child, whereas selectAll does not (hence the need for a data-join)!
So, when you did the data join on dataLabels, you’ve updated the data on the parent elements. But when you call dataLabels.selectAll(".value"), it doesn’t propagate data, so you were getting the old child data. If you switch to dataLabels.select(".value"), it propagates data to the selected children, so you get the new data again.
You could have propagated the data using selection.data, too, but since each label has one value element here, using selection.select is easier.
(Also, you might want to specify a key function.)
I am trying to figure out how to transition paths and text bound to the same data within a g element.
This gist shows the behavior I want. When you click the change button the path and text positions transition smoothly. The problem is I accomplish this by separately joining the path and text elements to the data. I am not the only one using join this way, but I think it violates the goal of maintaining one to one mapping between elements and data with d3.
Is there a way to emulate the
var path = svg.selectAll("path")
.data(dataset);
path
.enter().append("path")
.attr("d", function(d) { return line(d.values) + "Z"; })
.style("fill", function(d, i) { return color(i); });
path
.transition().duration(500)
.attr("d", function(d) { return line(d.values) + "Z"; });
path
.exit().remove();
pattern for elements linked to the same data within a g element?
Here is one of my failed attempts. Rather than updating the paths and text, the change button causes new g elements to be added.
Added on edit: failed attempt now works after correcting the typo found by explunit
You have a typo in your class name on the join. This code:
var g = svg.selectAll(".shapes")
.data(dataset);
Should be this:
var g = svg.selectAll(".shape")
.data(dataset);
When I change it as above it works fine for me.