Essentially I'm trying to have all the paths except the one being hovered over turn gray, while the one being selected keeps it's original color. I've been able to turn all of the other paths gray, however I'm having trouble with the "select.this" function and actually accessing the path I want to change style. It would appear that I've actually managed to get down to the path element within the g group, however I'm confronted with an error in the console saying
Uncaught TypeError: Property 'style' of object #<SVGGElement> is not a function
Relevant code:
svg.selectAll("g.team")
.on("mouseover",function(){
console.log("I see you!");
var lineName;
var accuracy = 10;
svg.selectAll("path.team.line").style("stroke","#C0C0C0");
//set all to gray
var selectedArray = d3.select(this);
console.log(selectedArray);
var selectGroup = selectedArray[0];
console.log("should be group:"+selectGroup);
var selectedLine = selectGroup[0];;
selectedLine.style("color",function(d){ //let active keep color
lineName = abbrDict[d.name]; //full name to be at end of line
return color(d.name);
});
//get position of end of line
var len = d3.select(this).children().node().getTotalLength();
var pos = d3.select(this).node().getPointAtLength(len);
//append text to end of line
svg.append("text")
.attr("id","tooltip")
.attr("x",pos.x-55)
.attr("y",pos.y)
.text(lineName)
.style("font-family","sans-serif")
.style("font-size",13);
this.parentNode.parentNode.appendChild(this.parentNode);
//brings team to front, must select the path's g parent
//to reorder it
})
.on("mouseout",function(){
d3.select("#tooltip").remove();
d3.selectAll("team").selectAll("path")
.transition()
.style("stroke",function(d){
return color(d.name); //return all the colors
});
d3.selectAll("axis").selectAll("line").style("color","black");
});
Please and thank you!
D3 selections are arrays of arrays of DOM elements. (They are nested arrays so they can implement nested selections, while keeping separate index counts and properties for each sub-selection.)
So when you run a statement like:
var selectedArray = d3.select(this);
The selectedArray
is of the structure [[ {SVGGElement} ]]
. That much you seem to understand.
But your selectedArray
isn't just an array containing an array containing a single DOM element. It is also a d3.selection
object, with all the special functions that selections have, including the .style()
function.
However, when you extract the sub-array in the next line:
var selectGroup = selectedArray[0];
You now just have an ordinary array containing an SVG <g>
element node. It has no special functions from d3. And finally, when you extract the element from that array:
var selectedLine = selectGroup[0];
You just return the DOM element node itself. Which is the exact same object as the this
that you originally selected. That node has a .style
property, but not a .style()
function.
Sometimes, you do want to extract a node from a d3 selection in order to use the properties or methods that are part of the DOM interface. If you did want to do this, the approach above would work, or you could access it all in one line with
var svgNode = d3.select("svg")[0][0];
Or, you could use the selection.node()
method which does the exact same thing (grabs the first node in the first nest in the selection):
var svgNode = d3.select("svg").node();
But, if you want to use d3 selection methods on a single DOM element, you select that element and then call the methods on the selection. It doesn't matter whether the selection contains one element or 1000, your code is just the same. (It won't even throw an error if your selection is completely empty -- it just won't do anything!) If you want to use d3 methods on a child of your original selection, you need to use a subselection method (either selection.select()
or selection.selectAll()
).
svg.selectAll("g.team")
.on("mouseover",function(){
var lineName;
svg.selectAll("path.team.line").style("stroke","#C0C0C0");
//set all to gray
var selectedGroup = d3.select(this);
var selectedLine = selectedGroup.select("path.team.line");
//this only selects the (first) path that is a child
//of the selected group
selectedLine.style("color",function(d){ //let active keep color
lineName = abbrDict[d.name]; //full name to be at end of line
return color(d.name);
});
/* ...etc... */
By the way, when you add an event handler to an element with d3's selection.on()
method, d3 will automatically pass that element's data object as the first parameter to your event handling function. Which means you can simplify your code to avoid the secondary function call:
svg.selectAll("g.team")
.on("mouseover",function(d){ //d as a function parameter
var lineName;
svg.selectAll("path.team.line").style("stroke","#C0C0C0");
//set all to gray
var selectedLine = d3.select(this);
selectedLine.style("color", color(d.name) );
//already have the `d` for this element available
lineName = abbrDict[d.name];
/* ...etc... */
To continue with your code: For positioning your text element, you are trying to use the .getTotalLength()
and .getPointAtLength()
methods of the <path>
element. Now, these methods are DOM interface methods, not d3 methods, so you need the actual {SVGPathElement}
node, not a d3 selection. However, you're mixing up your d3 selections with your DOM methods currently.
To access the node using pure Javascript from the this
element (which is it's parent <g>
element):
var pathNode = this.children[0]; //assuming the <path> is the first child of <g>
var len = pathNode.getTotalLength();
var pos = pathNode.getPointAtLength( len );
Alternatively, you can access the <path>
element from the d3 selection you created above:
var pathNode = selectedLine.node(); //grab the node from the selection
var len = pathNode.getTotalLength();
var pos = pathNode.getPointAtLength( len );
Finally, this line:
this.parentNode.parentNode.appendChild(this.parentNode);
//brings team to front, must select the path's g parent
//to reorder it
I think should be just:
this.parentNode.appendChild(this);
//brings team to front, must select the path's g parent
//to reorder it
(since this
is the <g>
element already).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With