Reproduce Protovis Sunburst Labels with D3.js - javascript

I'm migrating an older Rails/Protovis site to Django and D3.js. I used a modified version of the Protovis sunburst (http://mbostock.github.com/protovis/ex/sunburst.html -- for my code see http://www.eafsd.org/assignments_sunbursts) and would like to recreate this in D3.js using the sunburst example (http://bl.ocks.org/4063423) as a baseline, but I have run into a wall with attaching labels to the arcs.
I have looked at several other SO questions including Aligning text inside circular arc d3js and Looking for a way to display labels on sunburst chart (could not find a working example), but I can't seem to get the textpath working. If possible, it would be great to have the labels display radially as the text I'm displaying (18th Century diplomatic titles) can get quite long.
I have tried the following code from the example:
d3.json("../assignment-by-type.json", function(error, root) {
var path = svg.datum(root)
.selectAll("path")
.data(partition.nodes)
.enter()
.append("path")
.attr("display", function(d) {
return d.depth ? null : "none";
}) // hide inner ring
.attr("d", arc)
.style("stroke", "#fff")
.style("fill", function(d) {
return color((d.children ? d : d.parent).name);
})
.style("fill-rule", "evenodd")
.each(stash);
/* my additions begin here */
path.append("title")
.text(function(d) {return d.name + ": " + d.size});
path.append("text")
.attr("dy", ".3em")
.style("text-anchor", "middle")
.append("textpath")
.attr("class", "textpath")
.attr("xlink:href", "#path")
.text(function(d) { return d.name });
/* end of what I've added, back to the example code*/
d3.selectAll("input").on("change", function change() {
var value = this.value === "count"
? function() { return 1; }
: function(d) { return d.size; };
path.data(partition.value(value).nodes)
.transition()
.duration(1500)
.attrTween("d", arcTween);
});
});
The sunburst displays and the title show up on mouseover (although the inner ring doesn't have a size function so it returns as undefined).
Two other modifications I am trying to make: I can't find a D3js snippet that shows how to recursively compute the package sizes so that the inner nodes can show the total size of their associate leaves.
Finally, I can't figure out how to add my own color range.
I really appreciate your time. Many thanks.

Related

Changing Edge color with D3

I am reading a D3 tutorial and am following the code in this link:
http://www.d3noob.org/2013/03/d3js-force-directed-graph-example-basic.html
I understand the content so far, but am trying to learn more styling by changing different colors. I am trying to change the edge color between the nodes, but this is not working. I know I need to do
path.style("stroke", red)
for instance. But this changes every edge color as expected.
However, I want to change the color of the edge based on the value in the links array. So, if the links.value is < 1 I want green else I want an orange link.
I am somewhat stuck I know I need to use
.style("stroke", function(d) {if d.value < 1 {return 'green'} else {return 'orange'} });
I just can't figure out where to put this in the sample code. I'm just trying to learn by example from some basic D3. Thanks!
You set the style in the "enter" selection of the edges:
var path = svg.append("svg:g")
.selectAll("path")
.data(force.links())
.enter()
.append("svg:path")
.attr("class", function(d) { return "link " + d.type; })
.attr("class", "link")
.style("stroke", function(d){
if(d.value < 1) {return 'green'} else {return 'orange'}
})
.attr("marker-end", "url(#end)");
Here is the plunker: https://plnkr.co/edit/tOBZdHXVrvcAmh9aHlsl?p=preview
You should be able to add your chain your methods on to the end of:
// add the links and the arrows
var path = svg.append("svg:g").selectAll("path")
.data(force.links())
.enter().append("svg:path")
.attr("class", "link")
.attr("marker-end", "url(#end)");
It's approx line 73 of the sample code

Loop through SVG circles in directed graph

I have been going through some code I found online for creating and playing with directed graphs in D3 (http://bl.ocks.org/cjrd/6863459). I asked a question about this yesterday - Directed graph - node level CSS styles and that gave me a general idea of how to add CSS styles to SVG objects. However, I am still unable to do what I want. This is because, in the JS file, they seem to use the "nodes" to create "circles" and then render them all in one go instead of looping through them. In the updateGraph function, we have the lines -
// add new nodes
var newGs= thisGraph.circles.enter()
.append("g");
newGs.classed(consts.circleGClass, true)
.attr("transform", function(d){return "translate(" + d.x + "," + d.y + ")";})
.on("mouseover", function(d){
if (state.shiftNodeDrag){
d3.select(this).classed(consts.connectClass, true);
}
})
.on("mouseout", function(d){
d3.select(this).classed(consts.connectClass, false);
})
.on("mousedown", function(d){
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
})
.on("mouseup", function(d){
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
})
.call(thisGraph.drag);
First of all, I am not sure what the .append("g") means here. But more importantly, the line where the CSS class is applied,
newGs.classed(consts.circleGClass, true)
seems to apply the class to all "circles" in one line. Instead, I want to loop through each node and for the circle of that node, apply a CSS style based on attributes of the node (to keep things simple, lets say that it the "title" starts with a certain text, I want to make it a blue circle). I still have no idea how to do this. Can someone help here? Again, the answers to my previous question helped a lot in understanding CSS but this other issue is still blocking me from doing what I want.
Adding comments for more clarity.
// here thisGraph.circles is data selection
//so if the data array has 10 elements in array it will generate 10 g or groups.
var newGs= thisGraph.circles.enter()
.append("g");
//here we are adding classes to the g
newGs.classed(consts.circleGClass, true)
.attr("transform", function(d){return "translate(" + d.x + "," + d.y + ")";})
//attaching mouse event to the group
.on("mouseover", function(d){
if (state.shiftNodeDrag){
d3.select(this).classed(consts.connectClass, true);
}
})
.on("mouseout", function(d){
d3.select(this).classed(consts.connectClass, false);
})
.on("mousedown", function(d){
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
})
.on("mouseup", function(d){
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
})
.call(thisGraph.drag);//attaching drag behavior to the group
What does this line mean?
newGs.classed(consts.circleGClass, true)
This line means to add class to all the created g DOM element or group.
In the code you referring it means circleGClass: "conceptG"
Read this on how to add CSS to DOM in D3
In the code you are appending circle to the group like this
newGs.append("circle")
.attr("r", String(consts.nodeRadius));
So now each group will have a circle.
Next Question
I want to loop through each node and for the circle of that node, apply a CSS style based on attributes of the node
You can iterate through all the circles and add style depending on the data associated with the node like this.
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.style("fill", function(d){
if(d)//some condition on data
{
return "red";
}
else
return "blue";
});
Question:
if you could tell me how to add CSS classes instead of "red", "blue" it would be every thing I need.
To add class you can do like this.
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.attr("class", function(d){
function(d){
if(d)//some condition on data
{
return "red";//this will put class red in the node.
}
else
return "blue";//this will put class blue in the node.
});
Another way of doing the same:
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.classed({
'red': function(d) { return d.condition1 == "something"; },
'blue': function(d) { return d.condition1 != "something"; }
});
Hope this helps!

D3js : mouseover of one element change opacity of several others elements

Thanks to previous answers, I've made a map and a related graph with D3js.
The bar and the map are in specific divs, and I don't use the same data source. That's a part of my problem.
For the map, I used queue.js to load several files at a time. One of these files is a .csv which follow specifically the same order than the geojson where polygons are stocked. If I sort differently .csv's data, the correspondance with my .geojson's polygons is bad and my choropleth map become false.
Here's the associated code for the interactive polygons of the map :
svg.append("g").attr("class","zones")
.selectAll("path")
.data(bureaux.features) //"bureaux" is a reference to the geojson
.enter()
.append("path")
.attr("class", "bureau")
.attr("d", path)
.attr("fill", function(d,i){
if (progression[i].diff_ries<-16.1){ //"progression" is the reference to my .csv
return colors[0] // colors is a previous array with the choropleth's colors
}
else if (progression[i].diff_ries<-12.6){
return colors[1]
}
else if (progression[i].diff_ries<-9){
return colors[2]
}
else {return colors[3]
}
})
.on('mouseover', tip.show) // tip.show and tip.hide are specific functions of d3.js.tip
.on('mouseout', tip.hide)
};
No problem here, the code works fine. We arrived now to the graph. He used a .json array called at the beginning of the script, like this
var array=[{"id_bureau":905,"diff_keller":4.05,"diff_ries":-15.02},{etc}];
"id_bureau" is the common' index of my .geojson, my .csv and this .json's array. Then, I sort the array with a specific function. Here's a part of the code associated to the graph :
svg2.selectAll(".bar")
.data(array)
.enter().append("rect")
// I colour on part of the bars like the map
.attr("fill", function(d,i){
if (array[i].diff_ries<-16.1){
return colors[0]
}
else if (array[i].diff_ries<-12.6){
return colors[1]
}
else if (array[i].diff_ries<-9){
return colors[2]
}
else {return colors[3]
}
})
.attr("x", function (d) {
return x(Math.min(0, d.diff_ries));
})
.attr("y", function (d) {
return y(d.id_bureau);
})
.attr("width", function (d) {
return Math.abs(x(d.diff_ries) - x(0));
})
.attr("height", y.rangeBand());
// this part is for the other bars
svg2.selectAll(".bar")
.data(tableau)
.enter().append("rect")
// the others bars are always blue, so I used a simple class
.attr("class", "bar_k")
.attr("x", function (d) {
return x(Math.min(0, d.diff_keller));
})
.attr("y", function (d) {
return y(d.id_bureau);
})
.attr("width", function (d) {
return Math.abs(x(d.diff_keller) - x(0));
})
.attr("height", y.rangeBand());
svg2.append("g")
.attr("class", "x axis")
.call(xAxis);
svg2.append("g")
.attr("class", "y axis")
.append("line")
.attr("x1", x(0))
.attr("x2", x(0))
.attr("y2", height2);
So now, what I wan't to do is, when the mouse is over one polygon, to keep the correspondent bar of the graph more visible than the others with an opacity attribution (and when the mouse out, the opacity of all the graph returns to 1).
Maybe it seems obvious, but I don't get how I can correctly link the map and the graph using the "id_bureau" because they don't follow the same order like in this question : Change class of one element when hover over another element d3.
Does somebody know if I can easily transform the mouseover and mouseout events in the map's part to change at the same time my graph?
To highlight a feature on the map
To perform a focus on one feature, you just need a few line of CSS:
/* Turn off every features */
#carte:hover .bureau {
opacity:0.5;
}
/* Turn on the one you are specifically hovering */
#carte:hover .bureau:hover {
opacity:1;
}
To highlight a bar in your second chart
First of all, you need to distinguish the two kind of bar with two classes :
// First set of bars: .bar_k
svg2.selectAll(".bar_j")
.data(tableau)
.enter().append("rect")
// Important: I use a common class "bar" for both sets
.attr("class", "bar bar_j")
// etc...
// Second set of bars: .bar_k
svg2.selectAll(".bar_k")
.data(tableau)
.enter().append("rect")
.attr("class", "bar bar_k")
// etc...
Then you have to change your mouseenter/mouseleave functions accordingly:
svg.append("g").attr("class","zones")
.selectAll("path")
.data(bureaux.features)
.enter()
// creating paths
// ...
// ...
.on('mouseover', function(d, i) {
// You have to get the active id to highligth the right bar
var id = progression[i].id_bureau
// Then you select every bars (with the common class)
// to update opacities.
svg2.selectAll(".bar").style("opacity", function(d) {
return d.id_bureau == id ? 1 : 0.5;
});
tip.show(d,i);
})
.on('mouseout', function(d, i) {
// To restore the initial states, select every bars and
// set the opcitiy to 1
svg2.selectAll(".bar").style("opacity", 1);
tip.hide(d,i);
});
Here is a demo.
Performance issue
This implementation is kind of slow. You might improve it by toggling an "active" class to the bars you want to highlight.
An other good tail might be to gather the two kinds of bar in a single group that you describe singularly with an id (ie bureau187 for instance). That way you could select directly the bar you want into the mouseenter function and turn it on with an "active" class.
With this class you could mimic the strategy I implemented to highlight a feature and then remove svg2.selectAll(".bar").style("opacity", 1); from the mouseleave function :
/* Turn off every bars */
#carte:hover .bar {
opacity:0.5;
}
/* Turn on the one you want to highligth */
#carte:hover .bar.active {
opacity:1;
}

Displaying text over a node in a d3 force-layout graph

I am creating a force-layout graph using d3 and I am trying to make the name of a specific node appear when I mouse over that node. I know how to add the text before any mouse over effect and thought that I could just move that part of the code into the mouseover function but that did not work. I will also need to make the text disappear when I move the mouse off of the node. Here is the mouseover function where I attempt to add the name to the node:
function mouseover() {
d3.select(this).transition()
.duration(750)
.attr("r", function(d) {return d.size + 10;});
var labels = gnodes.append("text")
.text(function(d) { return d.name;})
console.log(labels);
}
Here is a link to a fiddle of the complete code as well:
http://jsfiddle.net/ohiobucks23/QvVU6/
If not using a tooltip per good recommendation by Bhatt, you will need to:
1) declare gnodes outside the drawGraph() function so that it is visible to the mouse functions, and
2) make the following changes to the mouse functions:
function mouseover(d) {
d3.select(this).transition()
.duration(750)
.attr("r", function (d) {return d.size + 10;});
// locate node and append text; add class to facilitate subsequent deletion
gnodes.filter(function (o) {return o.index === d.index;})
.append("text")
.attr("class", "nodetext")
.text(d.name);
}
function mouseout(d) {
d3.select(this).transition()
.duration(750)
.attr("r", function (d) {return d.size;});
// delete text based on class
d3.selectAll(".nodetext").remove();
}
Here is the complete FIDDLE. I changed the text of the root node element so that you can see that the mouseover function is really acting on the selected node.

Fading chords in d3js chord graph

I'm a total beginner with d3js, so please be patient if my question looks dumb.
I'm trying to reproduce a chord graph like the one proposed by Mike Bostock. In the code by Bostock if you go with your mouse on an arc, all the chords that are not involved (as target as well as source) in the arc will fade.
I'd like to change it in order to let all the chords fade except the one on which there is a mouse (in order to emphasize one single two-way relationship).
I've added a fade_single function that is triggered when the mouse is over a chord:
svg.append("g")
.attr("class", "chord")
.selectAll("path")
.data(chord.chords)
.enter().append("path")
.style("fill", function(d) { return fill(d.target.index); })
.attr("d", d3.svg.chord().radius(r0))
.style("opacity", 1)
.on("mouseover", fade_single(0.1))
.on("mouseout", fade_single(1));
The fade_single function follows:
function fade_single(opacity) {
return function(g, i) {
svg.selectAll("g.chord path")
.filter(function(d) {
//return d.source.index != 0 && d.target.index != 0;
})
.transition()
.style("opacity", opacity);
};
}
The problem is that I don't know what to put in the commented line, i.e. to filter out all the relationship that are have not the row and column of the single chord. I've tried to play with the subindexes but the parameter i only gives you the row, so I don't know how to isolate the chord I want to exclude from the fading.
Any idea? Any hint?
Thank you,
Elisa
To fade everything but the current elemeent, the easiest way is to use the this reference to the current DOM element:
function fade_single(opacity) {
return function() {
var me = this;
svg.selectAll("g.chord path")
.filter(function(d) {
return this != me;
})
.transition()
.style("opacity", opacity);
};
}

Categories

Resources