Reputation: 386
I have been exploring how to add a legend to a plot and update the legend following a transition in which a new type of is plotted. I've run into two issues which I haven't been able to troubleshoot my way through.
The block is here http://bl.ocks.org/natemiller/df4b96809580fe7d00a6
The sample JSON dataset includes several different variables (temp, pH, name, y, etc.) and what I am trying to develop is a plot that changes which variable is plotted on the x-axis and which variable determines the fill of the data points when a button is clicked. So far I have the x-axis changing, the points moving appropriately, and the legend color changing properly. However, I have two issues.
I'm missing the legend text (labels). I'm not clear on why they are not being shown, but I assume its a small detail I am missing. I'd appreciate any help with that.
As you can see in the dataset there are several "samples" from the same site, or with the same pH (7.2,8.0,7.2,7.2,8.0,8.0,7.6), or with the same temp (30, 25, 25, 30, 30, 25, 25). As such, when I generate the legend such as in the code here...
varlegend = svg.selectAll('rect')
.data(data)
.enter().append("rect")
.attr('x', width-50)
.attr('y', function(d,i) {return i*20;})
.attr('width', 10)
.attr('height', 10)
.style('fill', function(d) {
return color(d.name);
});
...and set the color using d.name I get 7 rectangles (one for each value), through I would rather have 3 rectangles when the fill color is set by site or pH (and two if the fill is set by temp) given some values are duplicates. Is there a means of doing this? I've considered d3.nest, but when I initiate the transition I no longer want the data nested by name, but rather by pH (so the fill is set by d.pH). I'd appreciate any comments on this. Am I doing this in a too complicated manner? Are there simpler ways I should be thinking about accomplishing this same goal?
Thanks for you time and help, Nate
Upvotes: 1
Views: 3596
Reputation: 634
Question 1: The text is not showing up because you cannot append text to a rect in SVG. That is to say, a rect cannot act like a container. Your problem is that you have specified legend = svg.selectAll('rect')
, which means saying below legend.append("text")
will not work. The solution is thus as explunit suggested: join the data to a g
(group) and then separately append the rect and text to that group.
Question 2: One way is to first create an array that removes duplicate entries, then use that array for your legend data-join. Here is an example:
var legendData = d3.values(data.map(function (d) { return d.temp; }))
var unique = legendData.filter(function (elem, pos) {
return legendData.indexOf(elem) == pos; })
This you the unique
array, which contains: [30, 25]
.
To ease headache, I would recommend you put all of your .enter() and .transition() in a single redraw function into which you can pass your data. So you can first filter your legend data by pH or temp, then pass that newly created array into the redraw function and the legend will transition, also removing old/unneeded elements. See Mike Bostock's post on the General Update Pattern for info on how to lay out the redraw function.
Upvotes: 1
Reputation: 19377
2 - When you provide the .data value, you can also provide a function that returns a uniquely identifying value. Something like this:
varlegend = svg.selectAll('rect')
.data(data, function(d) { return d.name })
//...
From the documentation of selection.data: "To control how data is joined to elements, a key function may be specified. This replaces the default by-index behavior"
1 - Regarding the text not showing up, I think the issue is the missing 'stroke' attribute. You've probably got white on white. However, another approach to consider is using a 'g' tag to hold both the rect and the text. Something like this:
var legend = svg.selectAll('.legend')
.data(data, function(d) { return d.name; })
.enter().append('g')
.attr('class', 'legend')
.attr('ID', function(d) { return d.name })
.attr("transform", function(d,i) {
return "translate(" + 800 + "," + i*20 + ")";
});
legend.append("rect")
.attr('width', 10).attr('height', 10)
.style('fill', function(d) {
return color(d.name);
});
legend.append("text")
.attr('x', 25).attr('y', 10)
.text(function(d) { return d.name; })
.attr("font-family","sans-serif")
.attr("font-size","11px")
.attr("stroke","black");
Upvotes: 2