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

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;
}

Related

How to access elements from dataset in d3

I have a parallel coordinates plot and I want to show lines onclick for d.dataset = train else hide them.
I wanted to access the row using .filter() like this:
data.filter(function(d) { return d.dataset == "train"; }).attr("visibility", "hidden");
and then set the attr visibility to hidden so that afterwards I can write a function with onclick to make the visibility visible, something like this:
// On Click, we want to add data to the array and chart
svg.on("click", function() {
var line = d3.mouse(this);
if (d.dataset === "train"){
//Display line of d.dataset === train
// line.attr("visibility", "visible");
}
});
This one I found also d3.selectAll("[dataset=train]").attr("visibility", "hidden"); but this doesn't work when doing with data elements right?
Right now I tried these and nothing happens. This is the jsfiddle I am working in. The line with "dataset":"train", is visible and doesn't hide.
How can I hide the lines when "dataset":"train", and then show them when onclick to the other lines in the parallel coordinates plot?
Any help would be highly appreciated.
First, make some marks on each path, for example, give a class name like coorPath so that it will be easier to find them. I added it for both background and foreground since I didn't know their difference.
svg.append("g")
.attr("class", "background coorPath") // add classname
.selectAll("path")
.data(dataSet)
.enter().append("path")
.attr("d", draw);
// CHANGE: duplicate with below code
/* svg.append("g")
.attr("class", "foreground coorPath")
.selectAll("path")
.data(dataSet)
.enter().append("path")
.attr("d", draw); */
// USE THE COLOR SCALE TO SET THE STROKE BASED ON THE DATA
foreground = svg.append("g")
.attr("class", "foreground coorPath")
.selectAll("path")
.data(dataSet)
.enter().append("path")
.attr("d", draw)
.style("stroke", function(d) {
var company = d.type.slice(0, d.type.indexOf(' '));
return color(company);
})
Then, find out the line of train, and make it invisible at first
let trainline = d3.selectAll("path").filter(function(d) { return d.dataset == "train"; })
trainline.attr("visibility", "hidden");
Show the line of train when one of other lines is clicked.
svg.selectAll(".coorPath").on("click", function(d) {
// show train when click others
trainline.attr("visibility", "visible")
});
a demo here

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!

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);
};
}

Creating a dynamic list of DIVs with D3

I have been using D3 to create fancy animated charts, and the examples are great. However, I'm trying to do something seemingly a lot more basic, and having trouble - binding data to a simple list of DIVs.
I set up enter() to initialize elements at opacity 0, transition() to fade them in, and exit() to fade them out and remove them. enter() and exit() seem to be working fine - however, when an update contains an existing element already in the list, it seems to get partially removed - the containing DIV remains, but the contents disappear. I can't understand why the contents of the element would get changed in this way.
My code is as follows:
var data = [...];
sorted = data.sort(function(a, b) { return d3.descending(a.id, b.id); });
var tweet = tweetsBox
.selectAll('div')
.data(sorted, function(d) { return d.id; });
var enterDiv = tweet.enter()
.append("div")
.attr("class", "tweetdiv")
.style("opacity", 0);
enterDiv.append("div")
.attr("class", "username")
.text(function(d) { return "#" + d.username });
enterDiv.append("div")
.attr("class", "displayname")
.text(function(d) { return d.displayname });
enterDiv.append("div")
.attr("class", "date")
.text(function(d) { return d.date });
enterDiv.append("div")
.attr("class", "text")
.text(function(d) { return d.text });
tweet.transition()
.delay(200)
.style("opacity", 1);
tweet.exit()
.transition()
.duration(200)
.style("opacity", 0)
.remove();
I also set up a jsFiddle here demonstrating the issue.
The problem is that you're selecting the divs you created, but create more than one div per data element. When updating, d3 tries to match the data to the nested divs. As you're already assigning a special class to the top-level divs, the fix is very simple. Replace
.selectAll('div')
with
.selectAll('.tweetdiv')

Reproduce Protovis Sunburst Labels with D3.js

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.

Categories

Resources