I have a piece of code that moves a line on mouseover like this:
some_svg
.on("mouseover", function() {
my_line.transition().attr("transform", function(d) {
return "translate(" + [d.new_x, d.new_y] + ")";
});
})
However let's say the transition takes a whole second to complete (I believe this is the d3 default), if I were to move the mouse over some_svg then quickly move the mouse out before one second, then the line stops as soon as I move the mouse out and it will be partly done with the transition. In other word, the line will be in some position between the old and new position.
How do I make sure the transition will always complete no matter if I move the mouse out (as long as I don't move it back to another some_svg)?
The default duration (if you don't set one) is 250ms.
The simpler way to do not interrupt a mouseover transition is not setting a mouseout behaviour that changes the transition. As long as you don't move to an element that has an mouseover/mousemove event handler that interferes with the ongoing transition, the transition will complete. That being said, I cannot understand or reproduce your statement ("the line stops as soon as I move the mouse out and it will be partly done with the transition").
This is easy to confirm: hover over the rectangle below. It will complete it's transition regardless you move the mouse out of it.
d3.select("rect").on("mouseover", function(){
d3.select(this).transition().duration(5000).attr("transform", "translate(350, 0)");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<svg width="400" height="200">
<rect x="0" y="50" width="40" height="20" fill="teal"></rect>
</svg>
Related
With assistance, I've uncovered the way to change elements in a radial dendrogram.
The lines below perform that function.
However, I'm trying to guess at what I need to bold text with the same mouse over. Can someone tell me what I'm missing?
// responsible for changing the style and type of the nodes when mousing over them
d3.selectAll('g.node').attr("id", function(d,i){ return "node"+i});
d3.selectAll('path.link').attr("id", function(d,i){ return "link"+i}); //my guess is on the line
below
d3.selectAll('text').attr("id", function(d,i){ return "text"+i});
// still trying to figure out how to make the text bold on mouse over
d3.selectAll('g.node').each(function(d, i) {
d3.select('#node'+i).on("mouseover", function() {
d3.select('#link'+(i-1))
.attr('style','stroke-width: 4px','style','font-weight: bold'); // my 2nd guess is on the next
line
d3.select('text').attr("font-weight",function(d,i) {return i*800+800;});
}).on("mouseout", function() {
d3.select('#link'+(i-1)).attr('style', 'stroke-width: 1.5px','stroke-opacity: 0.4','stroke:
#555');
});
});
In order to set font-weight - which is a CSS property -, .style should be used instead of .attr:
d3.select('#link'+(i-1))
.style('font-weight','bold');
Useful reference: modifying elements with d3-selection.
(I'm still figuring how best to use this platform)
But my answer was to create a CSS class called, .node text:hover, then increase the font weight within that class.
I'm creating a 'hand-drawn' style chart in d3:
Each bar below is a path inside of a g of class bar. When I hover over each g.bar, I highlight all paths inside with a basic mouseover function:
d3.selectAll('g.bar')
.on('mouseover', function() {
d3.select(this).selectAll('path').style('stroke', 'red')
})
The problem is, the highlighting only occurs when I hover over the paths, not the entire g.bar.
This makes the highlighting look super glitchy (running a mouse across it repeatedly highlights/unhighlights the path).
My question is: Is there a way to have all associated paths highlight whenever I'm hovering over the entire g.bar outlining the bar itself, and not just when I highlight over the path elements themselves?
A live-demo of my code is here: https://blockbuilder.org/jwilber/4bd8f5dd73666cdc5a30d7d6481e231a
Thanks for any help!
You could just add the following css:
g.bar {
pointer-events: bounding-box;
}
or directly set the g.bar elements' pointer-events attribute, which in your code would look like:
bar.setAttribute('pointer-events', 'bounding-box');
this sets up the g.bar elements to listen to events anywhere within the actual space that they take up (the bounding box).
However, the above only works in Chrome.
Another alternative that seems to work in all the browsers I've tried is to add a transparent rect element to each g.bar element (just as a sibling of the path).
data.forEach(function (d) {
let node = rc.rectangle(0, y(d.trick), x(d.count), y.bandwidth());
bar = roughSvg.appendChild(node);
bar.setAttribute('class', 'bar');
});
d3.selectAll('g.bar')
.data(data)
.append('rect')
.attr('x', 0)
.attr('y', d => y(d.trick))
.attr('width', d => x(d.count))
.attr('height', y.bandwidth())
.attr('fill', 'transparent');
I'm guessing that this works because rather than the g.bar mouseout event happening whenever it falls between the strokes of the path, there is a solid rect element filling the space, even if it is transparent.
I'm using the scrollama javascript library to write a "scrollytelling" article that involves transitioning D3 graphs in and out of view as the user scrolls. It is mostly working, but the graphs pile up on top of each other if I scroll too quickly.
Here is a jsfiddle based on this example by the scrollama author. In my example, the colored dots should fade in one at a time. If you were to scroll quickly to the end, the intermittent dots should not show up. The following snippets show how I've set up the transitions:
I define some functions that create my "graphs", and then call them.
var makeCircle0 = function(){
g.append("circle")
.attr("cx", 50)
.attr("cy", 100)
.attr("r", 20)
.attr("fill", "red")
.attr("class", "redcircle")
g.selectAll(".redcircle")
.attr("opacity", 0)
}
makeCircle0();
// Do this for makeCircle1, 2, and 3, also.
Then, I make functions to handle the transitions. This one says to make the red circle fade in and put the other circles at 0 opacity. I do this for all the circles/stages.
var showCircle0 = function(){
g.selectAll(".redcircle")
.transition()
.duration(1000)
.attr("opacity", 1)
g.selectAll(".yellowcircle").attr("opacity", 0)
g.selectAll(".greencircle").attr("opacity", 0)
g.selectAll(".bluecircle").attr("opacity", 0)
}
This section creates an array of my transition functions so that I can call them at specific steps in the page as you scroll. This is similar to how Jim Vallandingham handled his scroller.
var activateFunctions = [];
activateFunctions[0] = showCircle0;
activateFunctions[1] = showCircle1;
activateFunctions[2] = showCircle2;
activateFunctions[3] = showCircle3;
Finally, this calls the desired function at the right step in the page. Which it does... but not without halting the other transitions that got triggered in a previous step, resulting in multiple dots showing up at various stages.
function handleStepEnter(response) {
step.classed('is-active', function (d, i) {
return i === response.index;
})
figure.call(activateFunctions[response.index])
}
How can I prevent this?
If you need to interrupt a transition, d3-transition has a method for that:
selection.interrupt();
This will cancel a transition on an selection. If using named transitions you can specify a name by providing interrupt with one argument indicating the name of the transition to cancel.
If this is a generic version of your function to show an element:
function show() {
selectionToHide.attr("opacity",0);
selectionToShow.transition()
.attr("opacity",1);
}
Without using selection.interrupt you set the opacity to zero, and then the next tick of any transition in progress continues to update the opacity and finishes carrying out the transition. By adding interrupt we avoid that. Here's an updated fiddle.
However, there is another solution - we can apply another transition on the elements that we want to not show. To do so we just replace the transition with a new one:
function show() {
selectionToHide.transition()
.attr("opacity",0);
selectionToShow.transition()
.attr("opacity",1);
}
This will replace existing unnamed transitions (as yours are not named) and fade out elements, rather than simply hiding them all at once. Here's a fiddle of that. Of course if you have many elements this can be refined as to only apply a transition on any elements that are transitioning (not those that are already hidden) to reduce the amount of active transitions.
I haven't touched the scrolling, the circle that is shown should have its index match the displayed number, but it seems the number doesn't always match the scroll position, but this is a separate issue
i've managed to hack together a d3 transition, check it out here.
https://jsfiddle.net/guanzo/k1xw6ace/14/
Here's the basic idea:
I need to handle the possibility of 2 columns in case the svg height is not enough.
All transitions are a fade in/out and a translate +/- 30px on the x-axis;
On initialLoad, all legends transition into view.
When you click the "Reposition" button and data exits, exiting legends will immediately transition out, after the last legend exits the remaining legends will transition to their new position.
When you click the button again and data enters, updated legends will immediately transition to their new positions (taking into account entering legends), after the last update transition, the entering legends will transition in.
My problem is that my code is relying on setTimeouts and a couple hacks to deal with clumsy selections and conflicting enter/update states.
For example when data enters i need to selectAll update AND enter legends, in order to determine their new position. However i need to set different transitions to both groups.
The transition order depends on if data is entering or exiting. If data is entering, the update selection transitions first, then the enter selection. if data is exiting, the exit selection transitions first, then the update selection. How do i elegantly code this inverse relationship?
I'm just confused on the best way to do this.
I learnt this trick to chain transitions after the first transition has completed:
function endall(transition, callback) {
if (transition.size() === 0) {
callback()
}
var n = 0;
transition
.each(function() {
++n;
})
.each("end", function() {
if (!--n) callback.apply(this, arguments);
});
}
then in your transition function:
selection.transition()
.attr("cx", xMap)
.attr("cy", yMap)
.call(endall, function() {
console.log("all loaded");
// do your next transition
});
I have a pie chart and I'm trying to transition the rotation of my labels. For some reason, when I add transition, the text is removed. I've created a fiddle of my problem:
http://jsfiddle.net/samselikoff/k69We/
The chart renders but without labels. On line 110, uncomment out the setTimeout function. After a second, the transition will work correctly.
Why does the transition without the setTimeout blow away the label values?
The erring pair of lines in the code are line 65:
piece.append("g").attr("class", "label").append("text").style("opacity", 0);
And line 93:
g.selectAll(".label")
.data(function(d) {return d;})
.transition()
.duration(500)
// ...
.select('text')
// ...
.style("opacity", 1)
You cancel this transition on text on line 114 by starting a new transition:
g.selectAll(".label")
.select("text")
.transition()
.duration(500)
// ... (opacity is not changed here)
Hence, the opacity of the text stays zero. You can inspect the DOM to see that indeed the text elements exist (i.e. not blowen away) but just with opacity zero. This is a behavioural change between D3v2.7 and D3v3.
Now there are number of ways of correcting this depending on what was the behaviour you originally wanted. One of the ways is this: http://jsfiddle.net/zvPB6/ which straightens the labels in-sync with the other transitions.
If you wanted a .delay(500) for the straightening, then you'll probably have to listen to the end event and start a new transition in order to not delay the whole transition on all texts.