Trouble with storing enter selections containing transitions - d3.js

If I try to store the following enter selection, I get an error when I try to access it. I don't have a problem if I remove the transition. Why? Are there other restrictions on storing selections? Here is an example:
// this works
var enterSel = d3.select("svg")
.selectAll("circle")
.data([100, 200, 300])
.enter()
.append("circle")
.attr("cx", d => d)
.attr("cy", "100")
.attr("fill", "red")
.attr("r", "0")
.transition()
.duration(2000)
.attr("r", "50");
The above appends and transitions three circles to red, as expected, but the enterSel variable cannot be used for further modifications:
// this doesn't work
enterSel.attr("fill", "green");
Uncaught Error: transition not found d3.v4.min.js:2
at zn (d3.v4.min.js:2)
at Cn (d3.v4.min.js:2)
at SVGCircleElement.<anonymous> (d3.v4.min.js:2)
at qn.each (d3.v4.min.js:2)
at qn.tween (d3.v4.min.js:2)
at qn.attrTween (d3.v4.min.js:2)
at qn.attr (d3.v4.min.js:2)
at <anonymous>:1:10
I can get around this by doing the transition separately, as follows, but I really want to understand why this is necessary.
// this works
var enterSel = d3.select("svg")
.selectAll("circle")
.data([100, 200, 300])
.enter()
.append("circle")
.attr("cx", d => d)
.attr("cy", "100")
.attr("fill", "red")
.attr("r", "0");
enterSel.transition()
.duration(2000)
.attr("r", "50");
enterSel.attr("fill", "green");

I'll post an answer for the future. A d3 selection returns a selection with the d3.selection.prototype. A transition on the other hand returns a transition with the d3.transition.prototype.
var enterSel = d3.select("svg")
.selectAll("circle")
.data([100, 200, 300])
.enter()
.append("circle")
.attr("cx", d => d)
.attr("cy", "100")
.attr("fill", "red")
.attr("r", "0")
.transition()
.duration(2000)
.attr("r", "50");
enterSel.attr("fill", "green");
Does not work because enterSel is now a transition and has different properties than a selection.
var enterSel = d3.select("svg")
.selectAll("circle")
.data([100, 200, 300])
.enter()
.append("circle")
.attr("cx", d => d)
.attr("cy", "100")
.attr("fill", "red")
.attr("r", "0");
enterSel.transition()
.duration(2000)
.attr("r", "50");
enterSel.attr("fill", "green");
This one works because enterSel is always a selection, which uses the selection prototype. The transition is sectioned away in the second call, but enterSel is always the selection of all the circles.
Hopefully this helps clear things up!

Related

How to display an image inside a circle inside an arc in d3

I've got a sunburst chart drawn and this works fine. I've tried to add a circle that displays an image within each segment (path) and I can't get them to show up. Weirdly if I inspect the DOM the circles are exactly where I want them to be in the tree but they simply don't display. Is anybody able to help?
.create("svg")
.attr("viewBox", [0, 0, width, width])
.style("font-size", ".6rem");
const g = svg
.append("g")
.attr("transform", `translate(${width / 2},${width / 2})`);
const path = g
.append("g")
.selectAll("path")
.data(root.descendants().slice(1))
.join("path")
.attr("fill", (d) => {
while (d.depth > 1) d = d.parent;
return color(d.data.name);
})
.attr("fill-opacity", (d) =>
arcVisible(d.current) ? (d.children ? 0.7 : 0.5) : 0
)
.attr("d", (d) => arc(d.current));
path
.filter((d) => d.children)
.style("cursor", "pointer")
.on("click", clicked);
const defs = path.append("defs").attr("id", "imgdefs");
const iconPattern = defs
.append("pattern")
.attr("id", "iconPattern")
.attr("height", 1)
.attr("width", 1)
.attr("x", "0")
.attr("y", "0");
iconPattern
.append("image")
.attr("xlink:href", function (d) {
return d.data.img;
})
.attr("height", 15)
.attr("width", 15);
path
.append("circle")
.attr("r", 5)
.attr("cy", 0)
.attr("cx", 0)
.attr("fill", "url(#iconPattern)");
path
.append("title")
.attr("pointer-events", "none")
.attr("color", "white")
.on("click", clicked)
.text((d) => d.data.name);```
Here is your code sample refactored to add g instead of path and add path, circle, and text under g:
const segments = g
.selectAll("g.segment")
.data(root.descendants().slice(1))
.join("g")
.classed("segment", true)
segments.append("path)
.attr("fill", ...)
.attr("fill-opacity", ...);
segments.filter((d) => d.children)
.style("cursor", "pointer")
.on("click", clicked);
const defs = segments.append("defs").attr("id", "imgdefs");
const iconPattern = defs
.append("pattern")
...
iconPattern
.append("image")
...
segments.append("circle")
.attr("r", 5)
.attr("cy", 0)
.attr("cx", 0)
.attr("fill", "url(#iconPattern)");
segments.append("title")
.attr("pointer-events", "none")
.attr("color", "white")
.on("click", clicked)
.text((d) => d.data.name);

Warbling circle using d3

I wanted to draw a pulsating warbling circle on a geomap using d3, using this example for guidance. However—and I know this is a very basic question, so apologies—I can't seem to get the selector to fire right. Here is the relevant code:
layer2
.attr("id", "locations")
.selectAll(".state")
.data(columbia.features)
.enter().append("circle")
.attr("class", "location")
.attr("r", "4px")
.attr("cx", function(d) { return proj(d['geometry']['coordinates'])[0]; })
.attr("cy", function(d) { return proj(d['geometry']['coordinates'])[1]; })
.attr("d", path)
.each(pulse);
function pulse() {
var circle = svg.select(".location");
console.log(circle);
(function repeat() {
circle = circle.transition()
.duration(2000)
.attr("stroke-width", 20)
.attr("r", 10)
.transition()
.duration(2000)
.attr('stroke-width', 0.5)
.attr("r", 200)
.ease('sine')
.each("end", repeat);
})();
}
Right now I have:
var circle = svg.select(".location");
What should I have instead of .location?
Here is the full file.
Edit: got it; I need selectAll, notselect.

Drawing a graph with images and arrows with d3 issues

I'm trying to draw a graph with d3 inserting images in node circles and drawing arrows on the target node. Nodes with images and edges are properly drawn, but the arrows are missing, although the marker is defined and used in links.
When I change the way nodes are attached to "g", arrows are drawn, but circle and images disappear. Can't figure out where the mistake stays.
D3 code is the following:
// define marker for the arrow
svg.append("defs").selectAll("marker")
.data(["arrow"])
.enter().append("marker")
.attr("id", function(d) { return d; })
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -1.5)
.attr("markerWidth", 6)
.attr("markerHeight", 6)
.attr("orient", "auto")
.append("path")
.attr("d", "M0,-5L10,0L0,5");
// add links
var link = svg.append("g").selectAll(".link")
.data(links)
.enter().append("line")
.attr("class", "link")
.attr("marker-end", function(d) { return "url(#arrow)"; })
.style("stroke", "#FF3300");
// add nodes
var node = svg.append("g").selectAll(".node")
.data(nodes)
.enter().append("svg:g")
.attr("class", "node")
.on("dblclick", dblclick)
.call(force.drag);
node.append("svg:circle")
.attr("r", 50)
.style("fill", "#FFEBE6")
.style("stroke", "#FF3300")
.style("stroke-width", 3);
// add images - from base64
node.append("image")
.attr("xlink:href", function(d){
if (d.imgB64) {
return 'data:image/png;base64, ' + d.imgB64 ;
}
})
.attr("x", -40)
.attr("y", -40)
.attr("width", 80)
.attr("height", 80)
.on("mouseout", function(d) {
div.transition()
.duration(500)
.style("opacity", 0.0);
})
Nothing wrong with the code, arrows seems to be hidden/overlaid by the node circle and image. Changing the refX and refY attributes to 65 and 0 respectively addresses the issue.

Create a line connecting circles upon mouse hover

I have a scatter plot made in D3 with circles denoting each data point. Here's my code:
viz.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr("cx", function(d) {return x(d.x)})
.attr("cy", function(d) {return y(d.y)})
.attr("r", 5)
.attr("fill", function(d) {return d.color})
.on('mouseover', function(d){
console.log(d.color)
})
What I would like to do is, when a given circle is hovered on, connect all circles through a line that have the same color. How can I do this? I can get the color logged into the console, but I don't understand how I can connect all points with the same color through a line upon mouse click?
You can assign a class with color code to your circles. Use d3.selectAll to retrieve all of them on mouseover. Then retrieve their coordinates and pass the coordinates to draw d3.svg.line.
svg.selectAll(".dot")
.data(data)
.enter().append("circle")
.attr("class", function(d) {
return 'dot color-' + color(d.species).replace('#','');
})
.attr("r", 3.5)
.attr("cx", function(d) { return x(d.sepalWidth); })
.attr("cy", function(d) { return y(d.sepalLength); })
.attr("dot-color", function(d) { return color(d.species).replace('#',''); })
.style("fill", function(d) { return color(d.species); })
.on("mouseover", function() {
d3.selectAll(".color-" + $(this).attr("dot-color"))
.attr("r", 5.5);
})
.on("mouseout", function() {
d3.selectAll(".color-" + $(this).attr("dot-color"))
.attr("r", 3.5);
});
Here's an example with color hover:
http://vida.io/documents/KinEKRkSPSfStA4Eu
You can also do it without relying on a common class attribute. In the mouseover handler:
d3.selectAll('.dot')
.filter(function (dOther) { return d.color == dOther.color })
.attr('r', 3.5)

D3.js - can clipping paths be used with d3.svg.symbol?

I'm trying to draw multiple 'cross' symbols inside of a circle for use in a visualization. I'd like to draw the crosses in a 'g' tag and then apply a clipping path.
Is it possible to use clip paths with d3.svg.symbol ?
In the below example, the svg circle is masked correctly with the clip path; however the cross (the last part of the code) isn't.
Am I doing something wrong or is this not a feature?
var svg = d3.select("#maskingExample")
.append("svg:svg")
.attr("width", 500)
.attr("height", 200);
svg.append("svg:clipPath")
.attr("id", "clipper")
.append("svg:rect")
.style("stroke", "gray")
.style("fill", "black")
.attr("x", 50)
.attr("y", 25)
.attr("width", 300)
.attr("height", 45);
svg.append("g").append("svg:circle")
.style("stroke", "gray")
.style("fill", "blue")
.attr("cx", 175)
.attr("cy", 55)
.attr("r", 50)
.attr("clip-path", "url(#clipper)");
svg.append("g").append("path")
.attr("d", d3.svg.symbol()
.size( function(d) { return 3000; })
.type( function(d) { return d3.svg.symbolTypes[1]; }))
.attr("transform", "translate(150, 50)")
.attr("clip-path", "url(#clipper")
.style("fill", "black");
You're missing a close paren. Instead of
.attr("clip-path", "url(#clipper")
it should read
.attr("clip-path", "url(#clipper)")

Resources