D3: When I add a transition, my mouseover stops working... why? - javascript

Any help would be greatly appreciated.
Basically, the mouseover was working fine until I added a transition to the line chart. The transition takes the circles' opacity from zero to one.
var dots = svg.selectAll('circle')
.data(data)
.enter()
.append('svg:circle')
.attr('cx', function(d, i){ return ((width - tickOffset) / (data.length - 1)) * i; })
.attr('cy', function(d){ return y(d.value); })
.attr('r', 4)
.attr('class', 'circle')
.style('opacity', 0)
.transition()
.duration(circleAnimation)
.delay(function(d,i){ return i * (circleAnimation / 4); })
.style('opacity', 1);
dots.on('mouseover', function(d, i){
// show tooltip
})
.on('mouseout', function(d, i){
// hide tooltip
});
When I implement the transition, the console throws the following error
TypeError: 'undefined' is not a function (evaluating 'dots.on')
This same issue was happening on a bar chart I just created and simply stopping the method chaining and starting it again fixed the problem. That's why in this example I've stopped the method chaining and started it again with "dots.on('mouseover..."

As soon as you call .transition(), the selection you have becomes a transition. This is what you save in dots and then try to call .on() on. Instead, save the selection and set the transition and event handlers on it:
var dots = svg.selectAll('circle')
.data(data)
.enter()
.append('svg:circle')
.attr('cx', function(d, i){ return ((width - tickOffset) / (data.length - 1)) * i; })
.attr('cy', function(d){ return y(d.value); })
.attr('r', 4)
.attr('class', 'circle')
.style('opacity', 0);
dots.transition()
.duration(circleAnimation)
.delay(function(d,i){ return i * (circleAnimation / 4); })
.style('opacity', 1);
dots.on('mouseover', function(d, i){
// show tooltip
})
.on('mouseout', function(d, i){
// hide tooltip
});

Related

Circles on top of bar chart d3.js

I am making grouped bar chart based on Mike Bostock's tutorial.
I can't figure out how to put circles on top of my bars to act as tooltip when hovering, just like in this tutorial except it's on bars and not on a line.
I tried appending the circles like this :
svg.selectAll("dot")
.data(data)
.enter().append("circle")
.attr("r", 5)
.attr("cx", function(d) { return x1(d.name); })
.attr("cy", function(d) { return y(d.value); })
});
But I get NaN values. I am very confused about which variable I should use to get the right cx and cy.
Here is my code.
Any ideas ?
Thank you
You will get NaN values since your data join is not correct, you are trying to get values that are not currently present in your data. In order to get those values you would need to make a reference to data.years.
Here is my approach:
// Inheriting data from parent node and setting it up,
// add year to each object so we can make use for our
// mouse interactions.
year.selectAll('.gender-circles')
.data(function(data) {
return data.years.map(function(d) {
d.year = data.year;
return d;
})
})
.enter().append('circle')
.attr("class", function(d) {
return "gender-circles gender-circles-" + d.year;
})
.attr("r", 10)
.attr('cx', function(d) {
console.log(d)
return x1(d.name) + 6.5;
})
.attr('cy', function(d) {
return y(d.value) - 15;
})
.style('display', 'none'); // default display
// ....
// Using an invisible rect for mouseover interactions
year.selectAll('.gender-rect-interaction')
.data(function(d) { // Inheriting data from parent node and setting it up
return [d];
})
.enter().append('rect')
.attr("width", x0.rangeBand()) // full width of x0 rangeband
.attr("x", function(d) {
return 0;
})
.attr("y", function(d) {
return 0;
})
.attr("height", function(d) { // full height
return height;
})
.style('opacity', 0) // invisible!
.on('mousemove', function(d) { // show all our circles by class
d3.selectAll('.gender-circles-' + d.year)
.style('display', 'block');
})
.on('mouseout', function(d) { // hide all our circles by class
d3.selectAll('.gender-circles-' + d.year)
.style('display', 'none');
});
Working plnkr: https://plnkr.co/edit/oH4KXdxdIW82nLGv46NI?p=preview

D3js highlight bar one by one continuously

Here is the sample fiddle
Below code is to create bar
svg.selectAll("rect")
.data(dataset, key)
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return h - yScale(d.value);
})
.attr("width", xScale.rangeBand())
.attr("height", function(d) {
return yScale(d.value);
})
.attr("fill", function(d) {
return "blue";
})
//Tooltip
.on("mouseover", function(d) {
d3.select(this).style("fill","red");
})
.on("mouseout", function() {
d3.select(this).style("fill","blue");
}) ;
On mouseover bar gets red color, and on mouseout it gets back to blue color,
I want it to continuously get red color one by one means first bar red then second bar, then third, after moving ahead previous bar should restore its origin color, there will be only one red bar at a time. and it should be like when it reach to end, it should again start from beginning
Here is the result: http://jsfiddle.net/DavidGuan/f07wozud/4/
Code I added:
function reRenderColor() {
svg.selectAll("rect")
.transition()
.delay(function(d, i){ return i* 500 })
.duration(200)
.style('fill', 'red')
.transition()
.delay(function(d, i){ return i* 500 + 400 })
.duration(200)
.style('fill', 'blue')
}
reRenderColor();
setInterval(reRenderColor, svg.selectAll("rect").size() * 500 + 500)
I gave .attr("id", function(d,i){return "rect"+i;}); to your rect elements in order to select them. Then, I used a recursive setTimout function to solve this with d3 transition property.
var z = 0;
var timeoutFunc = function(){
setTimeout(function(){
if(z < 20){
d3.select("#rect"+ z).transition().duration(350).attr("fill","red")
.transition().delay(550).attr("fill","blue");
z++;
timeoutFunc();
}else if(z == 20){
z = 0;
timeoutFunc();
}
},500);
};
Here's an updated fiddle.
Note that durations could be changed for a better color visualization but this will give you an idea.
http://jsfiddle.net/51Lsj6ym/5/
Hope this is what you want
//Tooltip
.on("mouseover", function(d) {
d3.selectAll("rect").style("fill","blue");
d3.select(this).style("fill","red");
})
.on("mouseout", function() {
}) ;
fiddle

How to add textPath labels to zoomable sunburst diagram in D3.js?

I have modified this sunburst diagram in D3 and would like to add text labels and some other effects. I have tried to adopt every example I could find but without luck. Looks like I'm not quite there yet with D3 :(
For labels, I would like to only use names of top/parent nodes and that they appear outside of the diagram (as per the image below). This doesn't quite work:
var label = svg.datum(root)
.selectAll("text")
.data(partition.nodes(root).slice(0,3)) // just top/parent nodes?
.enter().append("text")
.attr("class", "label")
.attr("x", 0) // middle of arc
.attr("dy", -10) // outside last children arcs
/*
.attr("transform", function(d) {
var angle = (d.x + d.dx / 2) * 180 / Math.PI - 90;
console.log(d, angle);
if (Math.floor(angle) == 119) {
console.log("Flip", d)
return ""
} else {
//return "scale(-1 -1)"
}
})
*/
.append("textPath")
.attr("xlink:href", function(d, i) { return "#path_" + i; })
.text(function(d) { return d.name + " X%"; });
I would also like to modify a whole tree branch on hover so that it 'shifts' outwards. How would I accomplish that?
function mouseover(d) {
d3.select(this) // current element and all its children
.transition()
.duration(250)
.style("fill", function(d) { return color((d.children ? d : d.parent).name); });
// shift arcs outwards
}
function mouseout(d) {
d3.selectAll("path")
.transition()
.duration(250)
.style("fill", "#fff");
// bring arcs back
}
Next, I'd like to add extra lines/ticks on the outside of the diagram that correspond to boundaries of top/parent nodes, highlighting them. Something along these lines:
var ticks = svg.datum(root).selectAll("line")
.data(partition.nodes) // just top/parent nodes?
.enter().append("svg:line")
.style("fill", "none")
.style("stroke", "#f00");
ticks
.transition()
.ease("elastic")
.duration(750)
.attr("x1", function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.attr("y1", function(d) { return Math.max(0, y(d.y + d.dy)); })
.attr("x2", function(d) { return Math.max(0, Math.min(2 * Math.PI, x(d.x))); })
.attr("y2", function(d) { return Math.max(0, y(d.y + d.dy) + radius/10); });
Finally, I would like to limit zoom level so the last nodes in the tree do not fire zoom but instead launch a URL (which will be added in JSON file). How would I modify the below?
function click(d) {
node = d;
path.transition()
.duration(750)
.attrTween("d", arcTweenZoom(d));
}
My full pen here.
Any help with this would be much appreciated.

How to cancel scheduled transition in d3?

Transition Code,
d3.select('chart').select('svg')
.selectAll("circle")
.data(sampleData)
.enter().append('circle')
.each(function (d,i)
{
d3.select(this)
.transition()
.delay(i*50)
.attr('cx', function(d) {return d.x;})
.attr('cy', function(d) {return d.y;})
.attr('r', 4);
});
How can I stop/cancel the scheduled/delayed transactions?
The accepted answer does not work with the most recent version of d3. If you're using d3 v4, you should call .interrupt() on your selection.
As pointed out in the other answer, all you need is to schedule a new transition. However, the whole thing is much easier than what you're doing in your code -- there's no need for the separate .each() function. To schedule the transitions initially, you can simply do
d3.select('chart').select('svg')
.selectAll("circle")
.data(sampleData)
.enter().append('circle')
.transition()
.delay(function(d, i) { return i*50; })
.attr('cx', function(d) {return d.x;})
.attr('cy', function(d) {return d.y;})
.attr('r', 4);
The function to stop all transitions (scheduled and running) is simply
d3.selectAll("circle").transition();
Complete demo here.
Starting a new transition on the element stops any transition that is already running. You can pause/stop a d3 transition by setting a new transition with duration as 0.
function stopCircleTransitions(){
if(startedApplyingTransitions)
d3.select('chart').select('svg')
.selectAll("circle")
.each(function(d,i){
d3.select(this)
.transition()
.duration(0);
});
}
}
If you would like to stop the transition if and only if it is started applying, you can try the code below.
var startedApplyingTransitions = false;
d3.select('chart').select('svg')
.selectAll("circle")
.data(sampleData)
.enter()
.append('circle')
.each(function (d,i){
d3.select(this)
.transition()
.delay(i*50)
.attr('cx', function(d) {return d.x;})
.attr('cy', function(d) {return d.y;})
.attr('r', 4)
.each("end", function(){ //this code will do the trick
startedApplyingTransitions = true;
});
});

on click event D3.js only works first time

I have a table that has different rows. Every row is a different data set.
I have an on click event attached to the rows that gives an extra chart when you click on the specific row.
But it only works the first time. After you first click on a specific row that data is shown in the chart, but if you click on another row the chart doesn't change.
Here is some of my code:
var chart = d3.select("body").append("svg")
.attr("class", "chart")
.attr("width", w * 24)
.attr("height", h);
//saturday
var saturday = d3.select(".saturday")
.selectAll("td")
.data(d3.values(twitterDays[5][5]))
.enter()
.append("td")
.attr("class", function(d) { return "hour h" + color(d); });
d3.select(".saturday").on("click", function() {
chart.selectAll("rect")
.data(d3.values(twitterDays[5][5]))
.enter().append("rect")
.attr("x", function(d, i) { return x(i) - .5; })
.attr("y", function(d) { return h - y(d) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d); })
chart.append("line")
.attr("x1", 0)
.attr("x2", w * d3.values(twitterDays[5][5]).length)
.attr("y1", h - .5)
.attr("y2", h - .5)
.style("stroke", "#000");
});
//sunday
var sunday = d3.select(".sunday")
.selectAll("td")
.data(d3.values(twitterDays[6][6]))
.enter()
.append("td")
.attr("class", function(d) { return "hour h" + color(d); });
d3.select(".sunday").on("click", function() {
chart.selectAll("rect")
.data(d3.values(twitterDays[6][6]))
.enter().append("rect")
.attr("x", function(d, i) { return x(i) - .5; })
.attr("y", function(d) { return h - y(d) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d); })
chart.append("line")
.attr("x1", 0)
.attr("x2", w * d3.values(twitterDays[6][6]).length)
.attr("y1", h - .5)
.attr("y2", h - .5)
.style("stroke", "#000");
});
Your are only taking care of the so-called enter selection; meaning only the creation but not the update or removal of the rects is implemented in your code.
See the General Update Pattern: General Update Pattern, I
// DATA JOIN
// Join new data with old elements, if any.
var text = svg.selectAll("text")
.data(data);
// UPDATE
// Update old elements as needed.
text.attr("class", "update");
// ENTER
// Create new elements as needed.
text.enter().append("text")
.attr("class", "enter")
.attr("x", function(d, i) { return i * 32; })
.attr("dy", ".35em");
// ENTER + UPDATE
// Appending to the enter selection expands the update selection to include
// entering elements; so, operations on the update selection after appending to
// the enter selection will apply to both entering and updating nodes.
text.text(function(d) { return d; });
// EXIT
// Remove old elements as needed.
text.exit().remove();
}
Make sure to have a look at Mike's Thinking with Joins.

Categories