Cannot make labels work on zoomable d3 sunburst - javascript

After fiddling around for several hours now, I still cannot make labels work in my D3 Sunburst layout. Here's how it looks like:
http://codepen.io/anon/pen/BcqFu
I tried several approaches I could find online, here's a list of examples I tried with, unfortunately all failed for me:
[cannot post link list because of new users restriction]
and of course the coffee flavour wheel: http://www.jasondavies.com/coffee-wheel/
At the moment i fill the slices with a title tag, only to have it displayed when hovering over the element. For that I'm using this code:
vis.selectAll("path")
.append("title")
.text(function(d) { return d.Batch; });
Is there something similar I could use to show the Batch number in the slice?
--
More info: Jason Davies uses this code to select and add text nodes:
var text = vis.selectAll("text").data(nodes);
var textEnter = text.enter().append("text")
.style(...)
...
While the selection for his wheel gives back 97 results (equaling the amount of path tags) I only get one and therefore am only able to display one label (the one in the middle)

Needs some finessing but the essential piece to get you started is:
var labels = vis.selectAll("text.label")
.data(partition.nodes)
.enter().append("text")
.attr("class", "label")
.style("fill", "black")
.attr("transform", function(d) { return "translate(" + arc.centroid(d) + ")"; })
.text(function(d, i) { return d.Batch;} );
You can see it here
The trick is that in addition to making sure you are attaching text nodes to the appropriate data you also have to tell them where to go (the transform attribute with the handy centroid function of the arc computer).
Note that I do not need vis.data([json]) because the svg element already has the data attached (when you append the paths), but I still have to associate the text selection with the nodes from each partition.
Also I class the text elements so that they will not get confused with any other text elements you may want to add in the future.

Related

Selecting individual elements in d3

I've used the following code to create several rectangles and place them in a horizontal line:
var nodeIcons = svg.selectAll(".node")
.data(line.nodes)
.enter().append("rect")
.attr("x", screenWidth+100);
lastX = 50;
nodeIcons.transition().attr("x", function(d){
lastX += 150;
return lastX;
}).duration(1000);
As you can see, the rectangles are initially placed at the same x coordinate off the left edge of the screen, then they are animated into their place on the line.
Now, what would I call if I wanted to move just the first node in the line a bit further to the left?
I'm trying to wrap my head around the fundamentals of d3, here, and what I'm primarily asking is how to select the first node on the line.
First of all you probably want to give the class that you've selected the elements by to the created elements:
var nodeIcons = svg.selectAll(".node")
.data(line.nodes)
.enter().append("rect")
.attr("class", "node")
.attr("x", screenWidth+100);
To select just the first of these, you could either use .selectAll() and then index into the selection as pointed out in the comments, or simply use svg.select(".node"), which will give you the first element that matches.
D3 uses CSS selectors and these are quite flexible when it comes to things like this. If you wanted to select the 4th element for example, you could do something like this:
svg.select("rect:nth-child(4)");
In general, a better way to do this is to assign IDs to the elements so that you can select them explicitly though:
svg.append("rect").attr("id", "foo");
svg.select("#foo");

d3.js update bar chart labels after sorting data

I can't seem to figure out how to update bar labels when I re-sort ranking data; essentially the label names will all remain the same, but their order will change.
Originally I have:
// University Names
labelsContainer = chart.append('g')
.attr('transform', 'translate(' + (uniLabel - barLabelPadding) + ',' + (gridLabelHeight + topMargin) + ')')
.selectAll('text')
.data(sortedData)
.enter()
.append('text')
.attr('x', xoffset)
.attr('y', yText)
.attr('stroke', 'none')
.attr('fill', 'black')
.attr("dy", ".35em") // vertical-align: middle
.attr('text-anchor', 'end')
.text(barLabel);
I sort the data differently, which I still call sortedData. The rectangles and rest of the graph updates successfully...save for the labels (which I have on only one rectangle bar column.)
In a new function I tried:
// update University Names (this overwrites, however... I want to select the existing label instead of appending text on top of the original text)
labelsContainer = chart.append('g')
.attr('transform', 'translate(' + (uniLabel - barLabelPadding) + ',' + (gridLabelHeight + topMargin) + ')')
.selectAll('text')
.data(sortedData)
.enter() // using transition ... or selecting the group ... does not allow the new text to appear!
.append('text')
.attr('x', xoffset)
.attr('y', yText)
.attr('stroke', 'none')
.attr('fill', 'black')
.attr("dy", ".35em") // vertical-align: middle
.attr('text-anchor', 'end')
.text(barLabel);
The issue here is that this just adds the new (correct) labels on top of the existing ones, instead of replacing them.
Using transition() I'm able to update the rest of the graph, but not the labels.
Any ideas of how to fix? Happy to provide more info/context if need be...
UPDATE 12/24: JSFiddle: http://jsfiddle.net/myhrvold/BVB2d/
JSFiddle showing transition, but with labels being overwritten: http://jsfiddle.net/myhrvold/BVB2d/embedded/result/
I know that by appending, I'm overwriting; but in attempting to replace, nothing happens and the original text remains, so the idea here is that I'm showing that I am at least generating the correct new labels and putting them in the right place...it's just that I'm not substituting them from my original labels...
You're completely repeating your code when you update your data -- including the chart.append('g') which creates a new group and then adds text labels to it. Because you've just created this as a new group, when you select inside it you can't select any of the labels you created the first time, so instead you end up creating all new labels.
To fix: first, as #musically_ut suggested, give each of your groups a unique class name. Then, in your update method select this group and the text elements it contains using chart.select(g.univerity-labels-container).selectAll("text"). However, you'll find you still have problems because you've got everything chained to an enter(); since you don't expect any new elements to be added when sorting, just take out that line. *
That should get it working, but the program is still painfully complex for what you're trying to do. For starters, all of this could work a lot better as an HTML table which would handle a lot of the layout for you. More importantly, you could save a lot of work if, instead of grouping elements by column you grouped them by row. That way, you would only have to join the data once, to the group, instead of doing separate data joins for each variable. If I have a chance in the next few days, I might try to write up a from-the-ground up explanations of how to approach this. In the meantime, google "d3 sortable table" for a couple examples, or look at the source code for this NYT graphic by Mike Bostock.
*For updating with an enter() step, I find most tutorials don't describe the update process very clearly, but I wrote up a step-by-step breakdown here yesterday.

Replace Circles and Texts in d3 demo with foreignObject containing custom HTML and ko binding

Given this working fiddle which is an exact copy of this d3 demo, I would like to replace the circle and text elements in the SVG with foreignObject elements which contain some custom HTML.
I was able to manually add one using the following code:
var newFO = document.createElementNS('http://www.w3.org/2000/svg', "foreignObject");
$('svg').append(newFO);
$(newFO).append("<div class='test'>" + strNameVar + "</div>");
(fiddle with this implemented)
But it's not part of the graph, obviously. I really don't understand d3 enough to insert these on the fly using the "links" dataset in the demo. Basically I need to adapt the following code to use a foreignObject instead of a text element and then insert the custom HTML:
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function (d) { return d.name; });
update:
This version of the fiddle is the closest I've come, but it's applying the transform to the div instead of the parent foreignObject.
You can do this by appending the g elements first and then the foreignObject elements below. Like this (in a slight abuse of selectors):
var node = svg.selectAll("foreignObject")
.data(force.nodes())
.enter().append("g");
node.append("foreignObject")
// etc
Complete example here.

D3 - force layout, circle within circle

In the process of learning D3.js.
Is it possible using a force layout to place a circle within another circle shape as per the picture. I am hoping to transition between a single circle per node to a display showing two circles per node. The size of the effective donut is used to illustrate another variable in the data.
Is this possible?
You don't even need to use anything other than a basic svg circle, as you find in most examples. Just bind the data to it, apply a stroke, and set the stroke-width attr to your other variable. Or r - otherVar, I'm sure you can figure that part out.
If this doesn't satisfy, build your own shape. The 'g' svg element is a container element, and lets you build whatever you like. Add two circles to a g, fill them how you like. Make sure to add them in the right order, since svg has no concept of 'on top', things just get painted in the order that you add them.
edit: okay, quick demo so you can learn some syntax. I didn't add any comments but hopefully the code is very verbose and straightforward. Find it here.
d3/svg is something that you have to just bash your head against for a while. I highly recommend spending some time creating a sandbox environment where you can quickly test new things, save, refresh browser to see results. Minimizing that turnaround time is key.
Thanks to roippi I was able to create a group containing two circle shapes.
var nodeCircles = svg.selectAll("g")
.data(nodes);
// Outer circle
var outer = nodeCircles
.enter()
.append("circle")
.attr("class", "node_circle")
.attr("r", function(d) { return d.radius_plus; })
.style("fill", function(d) { return d.color_plus; })
.style("opacity", 0);
// Inner circle
var inner = nodeCircles
.enter()
.append("circle")
.attr("class", "node_circle")
.attr("r", function(d) { return d.radius; })
.style("fill", function(d) { return d.color; })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.on("mouseover", mouseOver)
.on("mouseout", mouseOut)
.call(force.drag);
Outer circle visibility is toggled via a button.
As mentioned, I use a desktop based IDE to run/test visualisation languages. Currently the IDE supports studies written in D3.js, Raphael, Processin.js, Paper.js and Dygraphs. Picture below...

Adding a title to my SVG window erases one datapoint? (d3)

I have a dataset of 100 numbers, and within an SVG I create a bunch of text objects to display those numbers using the code below:
svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.text(function(d) {
console.log(output_format(d));
return output_format(d);
This works perfectly. However, if I try to create a title later on (outside of my d3.csv brackets) with this code:
svg.append("text")
.text("Actual Labels")
.attr("x", w/1.92)
.attr("y", top_gap/1.5)
.attr("class", "title");
Then the first datapoint gets erased, and does not even display in console.log(output_format(d));. What is happening here and how do I fix this?
What happens is that your single text element is appended first because the other code has to wait for the AJAX request. So when you're appending the remainder of your text elements, one is already there. This existing text element is selected by selectAll("text") and then matched with the data in dataset. By default, d3 matches data based on the index -- the first element in the array matches the first element that is already there and is therefore not in the .enter() selection which you operate on.
The easiest way to fix this is to give the text labels that you append dynamically a different class and select based on that. That is, your code for appending the dynamic labels would look like
svg.selectAll("text.label")
.data(dataset)
.enter()
.append("text")
.attr("class", "label")
.text(function(d) {
console.log(output_format(d));
return output_format(d);
});
No other changes should be required.

Categories