Here is my problem, I want to use transition() method of d3.js but for a set of rectangles that I don't know the size.
For example: At first I have 2 rectangles then 3 then 4 and then 2.
Can I use transition() in this case? If so, what's the best way to do it?
Thanks in advance
I use invisible rectangles for providing good hover effect in charts. So while creating the rectangles I simply assign them a class. While updating I remove elements of that class first and just repeat the process.
tmpsvg = svg.transition();
g = tmpsvg.select('g');//prefer to refer by classname
g.selectAll(".bar-rect").remove();
var rect = g.selectAll(".bar-rect")
.data(data)
.enter().append("svg:rect")
.attr("class", "bar-rect")
.attr("x", function(d, i) { return x(d.key)-10; })
.attr("y", 0)
.attr("width", "20px")
.attr("height", h)
.on("mouseenter", function(d, i) {
//TOOLTIP EFFECTS ON MOUSE-ENTER
$('#myls'+i).animate( {opacity:1 },100);
$('.chart-tooltip[data-index='+i+']').addClass('hover');
// Add hover class to the targeted point
}).on("mouseleave", function(d, i) {
//REMOVE TOOLTIP EFFECT ON MOUSE-LEAVE
$('#myls'+i).animate( {opacity:0 },100);
$('.chart-tooltip').removeClass('hover');
// Remove hover class from the targeted point
});
This may not be the most efficient way but hope it helps.
Related
I'm trying to represent a selection sort visually with d3 to show some students and I'm having problems updating the data once the positions swap(I will add the transitions and delays once it's working). The positional attributes don't seem to be working as well, I don't know why, any ideas?. The codepen is here:
HTML:
<div id="canvas">
</div>
CSS:
rect{
border:1px solid black;
}
JS:
function selectionSort(array,svg){
//where the index will position
var positioningIndex=0;
var aux;
var minIndex;
var minVal=Number.MAX_VALUE;
while(positioningIndex<array.length){
//get the index of the mínimum
for(var i=positioningIndex;i<array.length;i++){
if(array[i]<minVal){
minIndex=i;
minVal=array[i];
}
}
//swap the mínimum to the positioningIndex
aux=array[minIndex];
array[minIndex]=array[positioningIndex];
array[positioningIndex]=aux;
//update visualization
svg.selectAll("rect").data(array);
minVal=Number.MAX_VALUE;
++positioningIndex;
}
return array;
}
var dataSet=[10,7,8,44];
var svg=d3.select("#canvas").selectAll("rect")
.append("svg")
.attr("width", 960)
.attr("height", 500);
var rect=svg.data(dataSet)
.enter()
.append("rect");
rect.text(function(el){
return el;
})
.attr("width", 30)
.attr("height", 30)
.attr("x", function(d, i) {
return i*5;
})
.attr("y", 30);
.style("color","green");
array=selectionSort(dataSet,svg);
You've got a lot of mixing up of html and svg elements going on there
First off, your svg element isn't getting appended:
var svg=d3.select("#canvas").selectAll("rect") // <- selectAll("rect") is causing problems
.append("svg")
No existing rect elements at the start means no svg's getting appended (do you mean to add one for each rect?) Edit: and in fact that one error is the cause of everything that happens afterwards - the selectAll("rect") needs moved to the line where elements are added to the svg - not on the line where the svg itself is added -->
var rect=svg.selectAll("rect").data(dataSet) // rect should really be g
.enter()
.append("rect");
Secondly, and because of the above error, the elements called 'rect' that are added (and added directly to the #canvas id div) aren't svg:rect objects - they're just html elements with the name 'rect' - see Is there a way to create your own html tag in HTML5?. The browser just treats them as inline elements, so none of your x's or y's make a difference they just line up one after the other
Finally, if this was svg you wouldn't be able to add text directly to a rect, you'd need to use a group (g) element and add both rect and text elements to that to keep them associated, and style("transform", translate(x,y)) the group element to move them around.
var g=svg.selectAll("g").data(dataSet) // <-- now changed from rect
.enter()
.append("g") // <-- and here
.attr ("transform", function(d,i) {
return "translate("+(i*35)+" 30)";
})
;
// texts n rects added here
g.append("text").text(function(el){
return el;
})
.attr("dy", "1em")
g.append("rect")
.attr("width", 30)
.attr("height", 30)
;
See http://codepen.io/anon/pen/bwJWEa?editors=1111
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!
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;
}
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);
};
}
Ok so I have the following code example where I have circles in an svg element. Each circle has a click event and I'm trying to animate the circle that was clicked. Currently all circles animate because I'm referring to the bubble object. What I want is to refer to the clicked object its self and not the other ones:
var data_items=[100,200,300];
var svg = d3.select("#chart")
.append("svg").attr("width", 800).attr("height", 600);
var g = svg.selectAll(".bubbleContainer")
.data(data_items)
.enter().append("g")
.attr("class","bubbleContainer");
var bubble = g.append("circle")
.attr("class","bubble")
.attr("cx", function(d) {
return d;
})
.attr("cy", function(d) {
return d
})
.attr("r", function(d) {
return d/2
})
.on("click",function(d){
bubble
.transition()
.duration(1000)
.attr("r",1000)
})
Any help is much appreciated
Thanks!
What Lars Kotthoff wrote would work. Alternatively – and I'm not sure which is more idiomatic:
Inside the click handler, the this context refers to the clicked DOM element.
So the following would do it too:
.on("click",function(d){
d3.select(this)
.transition()
.duration(1000)
.attr("r",1000)
});
You can use d3.event.target to access the element that is being clicked in the event handler. See for example this jsfiddle.