Unexpected transition behaviour D3 - javascript

I am trying to loop over a three-fold D3 transition. For some reason the last command seems to be ignored by the script and I am not sure why.
The circle in the below script should diminish in radius and transition to white colour fill. I am not sure why it isn't -and nothing is cropping up in the console..?
Any ideas -and hoping it isn't something ridiculous. http://jsfiddle.net/Guill84/ww1r42ym/
Full code of transition:
function transitionx(size) {
marker.transition()
.duration(7500)
.ease('quad')
.style("fill", "red")
.attr("r", size)
.each("end", function() {
marker.transition()
.attr("r", size * 1.2)
.duration(3000)
.each("end", function() {
marker.transition()
.attr("r", size / 1.2)
.duration(3000)
.style("fill", "white")
.each("end", transitionx(size))
})
})
}

When you do this:
.each("end", transitionx(size))
You're calling transitionx immediately and passing its result.
If transitionx had no arguments, this would work:
.each("end", transitionx)
But, since transitionx has arguments, what you're doing right now is equivalent to:
.each("end", transitionx())
Solution: You'll have to wrap it in a function:
.each("end", function() {
transitionx(size)
})
Here is your code with that change (I divided all durations by 10, to make it quicker):
var size = 40
//Create a sized SVG surface within viz:
var sampleSVG = d3.select("body")
.append("svg")
.attr("width", 200)
.attr("height", 200);
//Add to the svg surface a circle, with size position,
var marker =
sampleSVG.append("circle")
.style("stroke", "gray")
.style("fill", "white")
.attr("r", size)
.attr("cx", 50)
.attr("cy", 50)
transitionx(size);
//give the object some behaviour:
function transitionx(size) {
marker.transition()
.duration(750)
.ease('quad')
.style("fill", "red")
.attr("r", size)
.each("end", function() {
marker.transition()
.attr("r", size * 1.2)
.duration(300)
.each("end", function() {
marker.transition()
.attr("r", size / 1.2)
.duration(300)
.style("fill", "white")
.each("end", function() {
transitionx(size)
})
})
})
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

Related

Difference in chaining D3.js transitions

What needs to be done that the second transition of the green circle behaves like the second transition of the blue circle?
I would have expected that the transitions of both circles behave the same. However it seems that the first transition of the green circle is applied in place of the second transition.
const svg = d3.select("svg");
const blueCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 10)
.attr("r", 5)
.style("fill", "blue");
const greenCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.style("fill", "green");
blueCircle
.transition()
.duration(4000)
.ease(d3.easeLinear)
.attr("cx", 100)
.transition()
.duration(2000)
.ease(d3.easeElastic)
.attr("cx", 200);
const firstTransition = d3.transition()
.duration(4000)
.ease(d3.easeLinear);
const secondTransition = d3.transition()
.duration(2000)
.ease(d3.easeElastic);
greenCircle
.transition(firstTransition)
.attr("cx", 100)
.transition(secondTransition)
.attr("cx", 200);
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>
Update
Thanks to Coola's answer and this question, I found a possibility to make the second transition of the green circle work as expected:
const greenCircle = svg.append("circle")
.attr("class", "green")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.style("fill", "green");
const firstTransition = d3.transition()
.duration(4000)
.ease(d3.easeLinear);
const secondTransition = firstTransition.transition()
.duration(2000)
.ease(d3.easeElastic);
firstTransition
.select("circle.green")
.attr("cx", 100);
secondTransition
.select("circle.green")
.attr("cx", 200);
However, this code has still the following flaws:
The transitions are not independent, thus cannot be reused in a different order.
You cannot insert an already selected element (i.e. greenCircle) into the select method of the transition (it results in a "Failed to execute 'querySelector' on 'Element': '[object Object]' is not a valid selector." exception).
The typical method chaining concept of D3.js is not used.
Does anybody know a solution without these issues, especially for the first point?
In order to chain the transitions you have to use the on("end", function(){<do something>}).
You can read more about advanced control flows in the documentation.
greenCircle
.transition(firstTransition)
.attr("cx", 100)
.on("end", () => {
greenCircle.transition(secondTransition)
.attr("cx", 200);
});
Full Snippet:
const svg = d3.select("svg");
const blueCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 10)
.attr("r", 5)
.style("fill", "blue");
const greenCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.style("fill", "green");
blueCircle
.transition()
.duration(4000)
.ease(d3.easeLinear)
.attr("cx", 100)
.transition()
.duration(2000)
.ease(d3.easeElastic)
.attr("cx", 200);
const firstTransition = d3.transition()
.duration(4000)
.ease(d3.easeLinear);
const secondTransition = d3.transition()
.duration(2000)
.ease(d3.easeElastic);
greenCircle
.transition(firstTransition)
.attr("cx", 100)
.on("end", () => {
greenCircle.transition(secondTransition)
.attr("cx", 200);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>
UPDATE:
The above code solves only part of the problem. You will notice the animation is not exactly identical between the blue and green circles.
You also need to chain the firstTransition into the secondTransition const. Like:
const secondTransition = firstTransition.transition()
.duration(2000)
.ease(d3.easeElastic);
Full snippet:
const svg = d3.select("svg");
const blueCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 10)
.attr("r", 5)
.style("fill", "blue");
const greenCircle = svg.append("circle")
.attr("cx", 10)
.attr("cy", 30)
.attr("r", 5)
.style("fill", "green");
blueCircle
.transition()
.duration(4000)
.ease(d3.easeLinear)
.attr("cx", 100)
.transition()
.duration(2000)
.ease(d3.easeElastic)
.attr("cx", 200);
const firstTransition = d3.transition()
.duration(4000)
.ease(d3.easeLinear);
const secondTransition = firstTransition.transition()
.duration(2000)
.ease(d3.easeElastic);
greenCircle
.transition(firstTransition)
.attr("cx", 100)
.on("end", function () {
greenCircle.transition(secondTransition)
.attr("cx", 200);
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.14.2/d3.min.js"></script>
<svg width="250" height="50"></svg>

Displaying text after an onclick event

I'm using click events to log data to the console, but i'd like to display this data in a separate box (which i have created). Does anyone have any advice or suggestions for this? Or is there a decent library that can help me achieve this?
Cheers
var circles = svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 7)
.attr("cx", function(d) { return xScale(d[1]); })
.attr("cy", function(d) { return yScale(d[2]); })
.on('click', function(d, i) {
console.log("click", d[0]);
})
.attr("fill", function(d) {
var result = null;
if (data.indexOf(d) >= 0) {
result = colours(d);
} else {
result = "white";
}
return result;
});
var textBox = svg.append("rect")
.attr("x", 5)
.attr("y", 385)
.attr("height", 150)
.attr("width", 509)
.style("stroke", bordercolor)
.style("fill", "none")
.style("stroke-width", border);
In the "click" listener just select your box, or use the selection you already have:
circles.on("click", function(d) {
selection.append("text")
//etc...
})
Here is a simple demo, click the circle:
var svg = d3.select("svg");
var circle = svg.append("circle")
.datum({
name: "foo"
})
.attr("cx", 100)
.attr("cy", 100)
.attr("r", 60)
.style("fill", "teal");
var box = svg.append("g")
.attr("transform", "translate(300,50)");
box.append("rect")
.attr("height", 50)
.attr("width", 100)
.style("stroke", "black")
.style("fill", "none")
.style("stroke-width", "2px");
circle.on("click", function(d) {
box.append("text")
.attr("x", 10)
.attr("y", 20)
.text(d.name)
})
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="500" height="300"></svg>
Finally, two tips: make your selection a group or any other valid container for the text, not a rectangle, because you cannot append a text to a rectangle. Also, be prepared for all kinds of problems trying to fit your texts inside that rectangle: wrapping texts in an SVG is notoriously complicated.

How to select a specific d3 node group element

I have a D3 v4 force simulation with several nodes. Each node has a group. When I mouse over one of the elements of that group(an invisible circle) I want one of the other elements (the red circle on that specific node only which I gave an id of "backcircle") to do something. Currently this is what I have, but it does it to all nodes not just the one I'm hovering over's element.
this.node = this.d3Graph.selectAll(null)
.data(this.props.nodes)
.enter()
.append("g")
.attr("class", "nodes");
this.node.append("circle")
.attr("id", "backCircle")
.attr("r", 60)
.attr("fill", "red")
this.node.append("svg:image")
.attr("xlink:href", function(d) { return d.img })
.attr("height", 60)
.attr("width", 60)
.attr("x", -30)
.attr("y", -30)
this.node.append("circle")
.attr("r", 60)
.attr("fill", "transparent")
.on( 'mouseenter', function(d) {
d.r = 65;
this.node.select("#backCircle")
.transition()
.attr("r", 80);
}.bind(this))
Before anything else, two important tips:
Do not use "transparent" in an SVG.
IDs are unique. So, use classes instead (or select by the tag name)
Back to your question:
There are several ways of selecting the circle element based on a sibling circle element. The first one is going up the DOM and down again, using this.parentNode. The second one, if you know exactly the sequence of the siblings, is using previousSibling.
In the following demos, I have 3 elements per group: a circle, a text and a rectangle. Hovering over the rectangle will select the circle.
First, the option with this.parentNode. in your case:
d3.select(this.parentNode).select(".backCircle")
Hover over the squares:
var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d + ",75)"
});
g.append("circle")
.attr("class", "backCircle")
.attr("r", 40)
.attr("fill", "teal")
g.append("text")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text("FOO");
g.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.style("fill", "firebrick")
.on("mouseenter", function() {
d3.select(this.parentNode).select(".backCircle")
.transition()
.attr("r", 50)
}).on("mouseleave", function() {
d3.select(this.parentNode).select(".backCircle")
.transition()
.attr("r", 40)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Then, the option with previousSibling (here, you don't even need to set a class). In your case:
d3.select(this.previousSibling.previousSibling)
Hover over the squares:
var svg = d3.select("svg");
var data = [50, 150, 250];
var g = svg.selectAll(null)
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + d + ",75)"
});
g.append("circle")
.attr("r", 40)
.attr("fill", "teal")
g.append("text")
.attr("font-size", 20)
.attr("text-anchor", "middle")
.text("FOO");
g.append("rect")
.attr("x", 20)
.attr("y", 20)
.attr("width", 20)
.attr("height", 20)
.style("fill", "firebrick")
.on("mouseenter", function() {
d3.select(this.previousSibling.previousSibling)
.transition()
.attr("r", 50)
}).on("mouseleave", function() {
d3.select(this.previousSibling.previousSibling)
.transition()
.attr("r", 40)
})
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
PS: Have in mind that, since I'm not using an object, there is no need for bind(this) in my snippets.
I think you need to select the node that is firing the mouseenter event from within its handler.
this.node.append("circle")
.attr("r", 60)
.attr("fill", "transparent")
.on( 'mouseenter', function(d) {
var mouseenterNode = d3.select(this)
mouseenterNode.attr("r", 65);
mouseenterNode.select("#backCircle")
.transition()
.attr("r", 80);
}.bind(this))

D3: Attach text to circle such that it has same priority as circle object

I am able to add text to my sketch, but I would like it if I could make my text attached directly to the circle. This means that if a circle gets over-written by another circle, the text will as well. On a higher level not, I am finding the d3 model hard for constructing objects in a way that makes them composable with different shapes, etc. The code seems very procedural to mean so any tips would be greatly appeciated :)
JSFiddle link
var link = "https://api.github.com/orgs/csci-4830-002-2014/repos"
d3.json(link, function(error, data) {
var w = 10000;
var h = 1000;
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("line")
.data(data)
.enter()
.append("line")
.attr("x1", 5)
.attr("y1", 5)
.attr("x2", function (d,i){
return 30*d.forks_count;
})
.attr("y2", function (d,i){
return 30*d.open_issues_count;
})
.attr("stroke-width", 2)
.attr("stroke", "black");
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("r", 40)
.attr("cx", function(d){ return 30*d.forks_count; })
.attr("cy", function(d){ return 30*d.open_issues_count; })
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "white")
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("dx", function(d,i){ return 30*d.forks_count; })
.attr("dy", function(d,i){ return 30*d.open_issues_count; })
.text(function(d){
if (d.name.indexOf("challenge") != -1)
return "C";
else
return "H";
});
});
With the way your code written right now, all the lines will be added first, then all the circles, and finally the texts. SVG will always put elements added later on top. So to achieve what you want, you will need to group them up. To do this, you will need to add a g element for each element of your data
var element = svg.selectAll(".element")
.data(data)
.enter()
.append("g")
.attr("class","element");
Now you can add the line, circle, and text to it
element.append("line")
.attr("x1", 5)
.attr("y1", 5)
.attr("x2", function (d, i) {
return 30 * d.forks_count;
})
.attr("y2", function (d, i) {
return 30 * d.open_issues_count;
})
.attr("stroke-width", 2)
.attr("stroke", "black");
element.append("circle")
.attr("r", 30)
.attr("cx", function (d) {
return 30 * d.forks_count;
})
.attr("cy", function (d) {
return 30 * d.open_issues_count;
})
.attr("stroke", "black")
.attr("stroke-width", 2)
.attr("fill", "white")
element
.append("text")
.attr("dx", function (d, i) {
return 30 * d.forks_count;
})
.attr("dy", function (d, i) {
return 30 * d.open_issues_count+6;
})
.style("text-anchor", "middle")
.text(function (d) {
if (d.name.indexOf("challenge") != -1) return "C";
else return "H";
});
You can check the updated JSFiddle at http://jsfiddle.net/9tp1yun7/2/

How do I animate the thickness of a stroke in D3.js?

i've successfully added transitions to my circles in a node graph, but i'm now trying to animate the mouseover of the connective line.
here's what I've tried:
//define the lines:
var edges = svg.selectAll("line")
.data(dataset.edges)
.enter()
.append("line")
.style("stroke", "#ccc")
.style("stroke-width", 1)
.on("mouseover", lineMouseover)
.on("mouseout", lineMouseout);
//the callback functions for mouseover / mouseout
function lineMouseover() {
d3.select(this).select("line")
.transition()
.duration(100)
.style("stroke-width", 3);
}
function lineMouseout() {
d3.select(this).select("line")
.transition()
.duration(100)
.style("stroke-width", 1);
}
Nothing seems to happen at all when i mouse over the lines. so, either i'm capturing the line incorrectly, or the attributes i'm animating are the wrong attributes.
any insight into what I'm doing wrong here?
In your code, the thiscontext in the lineMouseOverand lineMouseOut functions is the line element. You could simply use d3.select(this)to select each line and set its attributes. I wrote a small fiddle http://jsfiddle.net/pnavarrc/4fgv4/2
svg.selectAll('path')
.data(data)
.enter()
.append('path')
.attr('d', function(d) { return line(d.p); })
.attr('stroke-width', function(d) { return d.w; })
.attr('stroke', function(d) { return d.c; })
.on('mouseover', mOver)
.on('mouseout', function(d) {
d3.select(this)
.transition()
.duration(300)
.style('stroke-width', d.w);
});
function mOver(d) {
d3.select(this)
.transition()
.duration(300)
.style('stroke-width', 6);
}
Regards,

Categories