D3: .transition() not working with events
The problem here is a confusion between two different methods that use the same name:
- selection.on(typenames[, listener])
- transition.on(typenames[, listener])
Since you have a transition selection, when you write...
.on(...
The method expects to see three things (or typenames):
- "start"
- "end"
- "interrupt"
However, "mouseover", "mouseout" or "click" are none of them. And then you get your error...
> Uncaught Error: unknown type: mouseover
Solution:
Bind the event listeners to a regular selection. Then, after that, create your transition selection.
Therefore, in your case, bind all the on
listeners to the "enter" selection (which, by the way, makes more sense), removing them from the update selection.
Have a look at this little demo. First, I create a regular, enter selection, to which I add the event listener:
var bars = svg.selectAll(null)
.data(data)
.enter()
.append("rect")
//some attr here
.on("mouseover", function(d) {
console.log(d)
});
Then, I add the transition:
bars.transition()
.duration(1000)
etc...
Hover over the bars to see the "mouseover" working:
var svg = d3.select("svg");var data = [30, 280, 40, 140, 210, 110];var bars = svg.selectAll(null) .data(data) .enter() .append("rect") .attr("x", 0) .attr("width", 0) .attr("y", function(d, i) { return i * 20 }) .attr("height", 18) .attr("fill", "teal") .on("mouseover", function(d) { console.log(d) });
bars.transition() .duration(1000) .attr("width", function(d) { return d })
.as-console-wrapper { max-height: 25% !important;}
<script src="https://d3js.org/d3.v4.min.js"></script><svg></svg>
d3.event not working when combining modules
import {event as d3event} from'd3-selection'
d3event === null // true; `event`'s value changes later with user input.
let d3 = Object.assign({}, d3selection, d3transition, d3event)
// won't have an `event` property since Object.assign({}, null) -> {}
Adding d3.event = event // or d3 = {event}
will still return the null event observed at assignment time; see Mike Bostock's method of getting the current event for a solution.
The d3.event note now contains a link to an explanation of what a live binding is: a reference to a variable within d3-selection
.
An additional caveat which cost me some time: if d3-drag
is pointing to a different version of d3-select
than your other tools, d3.select(/**/).call(d3.drag(/**/))
won't work, since events never reach the event
variable that d3.drag
is using. Somewhere around d3@^5
, the main package no longer pins the d3-selection
version, allowing this sort of mismatch.
D3 transition end events not firing correctly?
In the version in your jsfiddle, you only need to clear the timeout properly to make it work. The function setTimeout
returns a reference that you need to pass to window.clearTimeout
, i.e.
var t = null;
...
window.clearTimeout(t);
...
t = setTimeout(...);
Working fiddle here.
D3 transitions not working
You just have to correct one line of code:
instead of
var players = canvas
.selectAll(".player")
.data(data, function(d){
// always create a unique key for your data-binding
return d.name + d.saves + d.total;
});
you write:
var players = canvas
.selectAll(".player")
.data(data);
Now it works.
The problem was the following: The DOM elements you create in the enter selection are not valid SVG elements since at least one attribute was missing (E.g. the width is missing for the rect). As a result the transition created the object (from an underspecified SVG object).
So now you have transitions for all but the initial display because of the reason described above.
Here's the working JSFiddle:
https://jsfiddle.net/ee2todev/xjf0k2oy/
Single on event for multiple transitions
D3-transitions can be a bit tricky because the end event triggers at the end of each element's transition. There is no transition.on("endAll", ...
, so since you don't want to trigger the repeat function twice at the end of the transition you are using an empty transition to delay calling repeat.
You could use a counter to see when the last transition is done, but this too is less clean. Ultimately, you will get less elegant code when managing multiple events with transitions that act on elements individually.
Instead, since each element's timer and transition events for a d3-transition are specific to that element (even if you set them as a group, each element has it's own timer and events), create a repeat function that is specific to each element:
function scroll(d) {
d.push(Math.random());
// Transition
d3.select(this).attr("transform",null)
.attr("d",line)
.transition()
.attr("transform", "translate(" + x(0) + ")")
.duration(750)
.ease(d3.easeLinear)
.on("end",scroll); // repeat
d.shift();
}
And call with selection.each(scroll);
Now we are managing each transition (which handles each item in a selection separately) with a function that handles one item in the selection at a time.
This allows you to:
- drop the empty transition,
- access
d
directly if you call this function with.each()
(instead of usingpaths.each(...
twice in the function),
Other considerations:
you can apply this function to any group of paths you have a selection for without modifying the function itself.
the element being transitioned is
this
Here it is at work:
let n = 40;let random = d3.randomUniform(-1, 1);
let data = [d3.range(n).map(random), d3.range(n).map(random)];
let margin = {top: 6, right: 0, bottom: 6, left: 40};let width = 960 - margin.right;let height = 120 - margin.top - margin.bottom;
let x = d3.scaleLinear() .domain([1, n - 2]) .range([0, width]);
let y = d3.scaleLinear() .domain([-1, 1]) .range([height, 0]);
let line = d3.line() .x(function(d, i) { return x(i); }) .y(function(d, i) { return y(d); }) .curve(d3.curveBasis);
let svg = d3.select("body").append("p").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .style("margin-left", -margin.left + "px") .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath") .attr("id", "clip") .append("rect") .attr("width", width) .attr("height", height);
svg.append("g") .attr("class", "y axis") .call(d3.axisLeft(y).ticks(5));
let paths = svg.append('g') .attr('id', 'lines') .attr('clip-path', 'url(#clip)') .selectAll('path').data(data).enter() .append('path') .attr('class', 'line') .attr('stroke', (d, i) => d3.schemeCategory10[i]) .attr('d', line) .each(scroll); function scroll(d) { d.push(random()); d3.select(this).attr("transform",null) .attr("d",line) .transition() .attr("transform", "translate(" + x(0) + ")") .duration(750) .ease(d3.easeLinear) .on("end",scroll);
d.shift();}
#lines { fill: none; stroke: black; stroke-width: 1.5px;}.y.axis path { stroke: black;}p { padding: 40px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
D3 listening to end event of a transition
Ah, .each("end", function() {...})
(version 3) seems to have been replaced by .on("end", ...)
in version 4.
Responding to tick events during d3 transitions
There's no tick
event for transitions, so you can't do exactly what you want. There should be no need for this however -- you can simply add a transition to the line end in the same way as to the rectangle. The code would look something like this.
rect.transition().attr("x", newX).attr("y", newY);
line.transition().attr("x2", newX).attr("y2", newY);
D3 Stop interaction during ongoing transition
There are several ways to do this, like using a flag. However, the most idiomatic D3 is just check if the element has an active transition, which is can be as simple as:
if(d3.active(this)) return;
Here is your code with that change:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>D3v6 Transition Example</title>
<!-- call external d3.js framework -->
<script src="https://d3js.org/d3.v6.js"></script>
</head>
<style>
body {
background-color: rgb(220, 220, 220);
overflow: hidden;
margin: 0px;
}
.node {
stroke: white;
stroke-width: 2px;
cursor: pointer;
}
.node:hover {
stroke: red
}
.link {
fill: none;
cursor: default;
stroke: rgb(0, 0, 0);
stroke-width: 3px;
}
</style>
<body>
<svg id="svg"> </svg>
<script>
var graph = {
"nodes": [{
"id": 0,
},
{
"id": 1,
},
{
"id": 2,
}
],
"links": [{
"source": 0,
"target": 1,
},
{
"source": 1,
"target": 2,
},
{
"source": 2,
"target": 0,
}
]
}
var width = window.innerWidth
var height = window.innerHeight
var svg = d3.select("svg")
.attr("class", "canvas")
.attr("width", width)
.attr("height", height)
.call(d3.zoom().on("zoom", function(event) {
svg.attr("transform", event.transform)
}))
.append("g")
// remove zoom on dblclick listener
d3.select("svg").on("dblclick.zoom", null)
var linkContainer = svg.append("g").attr("class", "linkContainer")
var nodeContainer = svg.append("g").attr("class", "nodeContainer")
var isRed = false;
var simulation = d3.forceSimulation()
.force("link", d3.forceLink().id(function(d) {
return d.id;
}).distance(100))
.force("charge", d3.forceManyBody().strength(-500))
.force("center", d3.forceCenter(width / 2, height / 2))
.force("collision", d3.forceCollide().radius(50))
initialize()
function initialize() {
link = linkContainer.selectAll(".link")
.data(graph.links)
.join("line")
.attr("class", "link")
node = nodeContainer.selectAll(".node")
.data(graph.nodes, d => d.id)
.join("g")
.attr("class", "node")
node.selectAll("circle")
.data(d => [d])
.join("circle")
.attr("r", 30)
.style("fill", "whitesmoke")
.on("mouseenter", mouseEnter)
node.selectAll("text")
.data(d => [d])
.join("text")
.style("class", "icon")
.attr("font-family", "FontAwesome")
.attr("dominant-baseline", "central")
.attr("text-anchor", "middle")
.attr("font-size", 30)
.attr("fill", "black")
.attr("stroke-width", "0px")
.attr("pointer-events", "none")
.text((d) => {
return d.id
})
simulation
.nodes(graph.nodes)
.on("tick", ticked);
simulation
.force("link")
.links(graph.links)
}
function mouseEnter(d) {
if (d3.active(this)) return;
if (!isRed) {
d3.select(this)
.transition()
.duration(1500)
.style("fill", "red")
isRed = true
} else {
d3.select(this)
.transition()
.duration(1500)
.style("fill", "whitesmoke")
isRed = false
}
}
function ticked() {
// update link positions
link
.attr("x1", function(d) {
return d.source.x;
})
.attr("y1", function(d) {
return d.source.y;
})
.attr("x2", function(d) {
return d.target.x;
})
.attr("y2", function(d) {
return d.target.y;
});
// update node positions
node
.attr("transform", function(d) {
return "translate(" + d.x + ", " + d.y + ")";
});
}
</script>
</body>
</html>
Getting error on applying transition in d3
Add the .on(...)
call before the .transition()
, then it should be fine.
Related Topics
How to Change the Font Size in Jqgrid
Access the -Webkit- Vendor Prefix in JavaScript
Jquery: Animate Margins to Auto
How to Color Specific Letters in HTML Element Text
Setting Multiple Style.Background Values
How to Add Validation/Restrictions for HTML5 Date Field Without Jquery/Javascript
Resize Iframe Height According to Content Height
Vue Transition Not Triggering on Button Click
Select Element by CSS Style (All with Given Style)
CSS Injection: What's the Worst That Can Happen
How to Disable JavaScript for Responsive Design
How to Make Div Slide from Right to Left
Generate Random Element Position and Prevent Overlapping in JavaScript
How to Extract Color Values from Rgb String in JavaScript
Getting Absolute Position of an Element Relative to Browser