d3.js: Modifying all elements of a class on Click - javascript

SOLVED
I am generating a hexbin plot in d3.js, where hexbins will be assigned different classes. I would like to change some attributes of the hexbins of a certain class on click. I would like to click on one hexbin (belonging to a class "class1"), and e.g. change the color of all the hexbins of that class to red. Here is the code I have written, which makes sense to me but it doesn't work:
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll("path")
.data(hexbin(inputForHexbinFun))
.enter().append("path")
.attr("class", "km1")
.attr("d", hexbin.hexagon())
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; })
.attr("fill", function (d) { return color(d.length); })
.attr("stroke", "black")
.attr("stroke-width", "0")
.on("click", click)
.on("mouseover", mouseover)
.on("mousemove", mousemove)
.on("mouseleave", mouseleave);
})
here I have assigned the same class "km1" to all hexbins for the sake of simplicity. I have inspected the result on Chrome and I can see that the class is successfully assigned to each hexbin. The function click, defined above the generation of the plot, is as follows:
var click = function () {
d3.selectAll("class", ".km1")
.style("fill", "red")
}
as a result, on click nothing happens, and no errors appear in console.
Thank you in advance for your help!
Solution
It was a silly mistake:
.selectAll does not require that you specify the type of selector, so instead of
.selectAll("class",".km1")
it is sufficient to write
.selectAll(".km1")

Related

D3 - how to fire an on-click function for only a specific group of nodes

In this d3 force layout viz project I'm trying to remove the user's ability to click on the bigger yellow nodes. I have an on-click, fire the clicknodeControl function,
nodes.append('circle')
.attr("r", 28)
.attr("id", "hoverdots")
.style("opacity", 0)
.style("fill", "#00bedd")
.on("click", function(d) { return clicknodeControl(d.group); })
.style("z-index", "10")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
and then ideally this variable would pass through the clicknode function to one group of nodes but not the other:
var clicknodeControl = d3.scaleOrdinal([`clicknode`, ``]);
This does not seem to be working in practice, the clicknode function is not being passed through.
Thanks if anyone has any ideas on this!!
In D3 V7 mouse event handlers have event as the first argument and datum as the second.
Replace
.on("click", function(d) { return clicknodeControl(d.group); })
with:
.on("click", (_, d) => clicknodeControl(d.group))

div element is getting appended outside the body

I want to implement a list on right-click of a data node. In order to do so I came across d3-context-menu plugin of d3.js. The problem I am facing is that the div element is getting appened outside the body tag.
I have never seen such an issue before.
I am following the plugin example given here:
http://plnkr.co/edit/hAx36JQhb0RsvVn7TomS?p=preview
This is the link to the library documentation:
https://github.com/patorjk/d3-context-menu
I have no clue why it is behaving in such manner. My code structure looks like this :
eventGroup = focusClip.selectAll(".event").data(data);
// Enter phase ---
eventGroupEnter = eventGroup.enter().append("svg");
eventGroupEnter.append("rect");
eventGroupEnter.append("circle");
eventGroupEnter.append("text");
// Event Group
eventGroup
.attr("class", "event")
.attr("x", function(d) {
return parseInt(x(d.time)) - 10;
}) // offset for the bg and center of dot
.attr("y", function(d) {
return parseInt(y(d.plotY));
})
.attr("width", function(d) {
return parseInt((d.label.length / 2)) + 60 + "em";
})
.attr("height", "20");
// Background
eventGroup.select("rect")
.attr("x", 0) // removes the "<rect> attribute x: Expected length, 'NaN'" Error
.attr("y", 4)
.attr("width", "100%")
.attr("height", "12")
.attr("fill", "url(#event-bg)");
menu = [{
title: "Item #1"
}];
// Dot
eventGroup.select("circle")
.attr("class", "dot")
.attr("r", 4)
.attr("cx", 10)
.attr("cy", 10)
.attr("fill", function(d) {
return d.evtColor ? d.evtColor : "#229ae5";
})
.attr("stroke", function(d) {
return d.evtColor ? d.evtColor : "#229ae5";
})
.attr("stroke-width", 2)
.on("contextmenu", d3.contextMenu(menu, function() {
console.log("Quick! Before the menu appears!");
}))
.on("mouseenter", tooltip.mouseover)
.on("mouseleave", tooltip.mouseout)
.on("click", annotateBox.click);
In order to explain it well I am adding the image of the chart:
The right click event is being called on the "dot" part of the event. Why would div element get appended outside the body?
This seems to be by design. If you look at the source code of that plugin, you'll see:
d3.selectAll('.d3-context-menu').data([1])
.enter()
.append('div')
.attr('class', 'd3-context-menu');
Since selectAll is called on the root, the div will be appended to the <html>, not to the <body>.
So, the author either did this intentionally or she/he forgot that d3.selectAll is different from selection.selectAll.
Here is a basic demo, click "Run code snippet", open your browser's dev tools and inspect the snippet window.
d3.selectAll("foo")
.data([1])
.enter()
.append("div")
.attr("class", "test")
<script src="https://d3js.org/d3.v3.min.js"></script>
You're gonna see this:
<html>
<head>...</head>
<body>...</body>
<div class="test"></div>
</html>

How to get the background attribute from the nodes of treemap in d3?

A part of my code which draws the d3 treemap nodes is given below:
var node = svg.datum(data).selectAll(".tree_rect")
.data(treemap.nodes)
.enter().append("rect")
.attr("class", "tree_rect")
.call(position)
.style("background", function(d) { return d.children ? null:color_scale(d.Percentage, d['Planned Date'], d['Actual Date']); })
.on("mousemove", mousemove)
.on("mouseout", mouseout)
Now, how can I get the color of that particular node, so that I could reuse it for a different function.
May be by giving each node an id something like this:
var node = svg.datum(data).selectAll(".tree_rect")
.data(treemap.nodes)
.enter().append("rect")
.attr("class", "tree_rect")
.attr("id", function(d) {/*SOME ID FOR THE NODE*/ return d.id;})
..
later on get the node's color using its id something like below:
var color = d3.select("#" ID)
.style("background")
Hope this helps!
Maybe try using the .style attribute.
Example
var t = node.append("text")
.style("color", function(d){ //your function definition for color here//});

Loop through SVG circles in directed graph

I have been going through some code I found online for creating and playing with directed graphs in D3 (http://bl.ocks.org/cjrd/6863459). I asked a question about this yesterday - Directed graph - node level CSS styles and that gave me a general idea of how to add CSS styles to SVG objects. However, I am still unable to do what I want. This is because, in the JS file, they seem to use the "nodes" to create "circles" and then render them all in one go instead of looping through them. In the updateGraph function, we have the lines -
// add new nodes
var newGs= thisGraph.circles.enter()
.append("g");
newGs.classed(consts.circleGClass, true)
.attr("transform", function(d){return "translate(" + d.x + "," + d.y + ")";})
.on("mouseover", function(d){
if (state.shiftNodeDrag){
d3.select(this).classed(consts.connectClass, true);
}
})
.on("mouseout", function(d){
d3.select(this).classed(consts.connectClass, false);
})
.on("mousedown", function(d){
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
})
.on("mouseup", function(d){
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
})
.call(thisGraph.drag);
First of all, I am not sure what the .append("g") means here. But more importantly, the line where the CSS class is applied,
newGs.classed(consts.circleGClass, true)
seems to apply the class to all "circles" in one line. Instead, I want to loop through each node and for the circle of that node, apply a CSS style based on attributes of the node (to keep things simple, lets say that it the "title" starts with a certain text, I want to make it a blue circle). I still have no idea how to do this. Can someone help here? Again, the answers to my previous question helped a lot in understanding CSS but this other issue is still blocking me from doing what I want.
Adding comments for more clarity.
// here thisGraph.circles is data selection
//so if the data array has 10 elements in array it will generate 10 g or groups.
var newGs= thisGraph.circles.enter()
.append("g");
//here we are adding classes to the g
newGs.classed(consts.circleGClass, true)
.attr("transform", function(d){return "translate(" + d.x + "," + d.y + ")";})
//attaching mouse event to the group
.on("mouseover", function(d){
if (state.shiftNodeDrag){
d3.select(this).classed(consts.connectClass, true);
}
})
.on("mouseout", function(d){
d3.select(this).classed(consts.connectClass, false);
})
.on("mousedown", function(d){
thisGraph.circleMouseDown.call(thisGraph, d3.select(this), d);
})
.on("mouseup", function(d){
thisGraph.circleMouseUp.call(thisGraph, d3.select(this), d);
})
.call(thisGraph.drag);//attaching drag behavior to the group
What does this line mean?
newGs.classed(consts.circleGClass, true)
This line means to add class to all the created g DOM element or group.
In the code you referring it means circleGClass: "conceptG"
Read this on how to add CSS to DOM in D3
In the code you are appending circle to the group like this
newGs.append("circle")
.attr("r", String(consts.nodeRadius));
So now each group will have a circle.
Next Question
I want to loop through each node and for the circle of that node, apply a CSS style based on attributes of the node
You can iterate through all the circles and add style depending on the data associated with the node like this.
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.style("fill", function(d){
if(d)//some condition on data
{
return "red";
}
else
return "blue";
});
Question:
if you could tell me how to add CSS classes instead of "red", "blue" it would be every thing I need.
To add class you can do like this.
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.attr("class", function(d){
function(d){
if(d)//some condition on data
{
return "red";//this will put class red in the node.
}
else
return "blue";//this will put class blue in the node.
});
Another way of doing the same:
newGs.append("circle")
.attr("r", String(consts.nodeRadius))
.classed({
'red': function(d) { return d.condition1 == "something"; },
'blue': function(d) { return d.condition1 != "something"; }
});
Hope this helps!

mouse over event line path d3

I created a multi line graph by d3.
I'm trying to create a handler for each line (path) but it doesn't work.
Here is the code creating the path:
var line2 = d3.svg.line()
.x(function(d, i){return x(AXValues[i])})
.y(function(d, i){return y(AYValues[i])});
p2 = svg.append("path")
.datum(ArticleData)
.transition()
.delay(1100)
.attr("id", "p"+i)
.attr("class", "p"+i)
.attr("d", line2(AXValues, AYValues))
.style("stroke", "Brown")
.style("stroke-width", 2)
.style("fill", "none");
Im trying to do something like this:
.on("mouseover", this.style("stroke-width", 5));
You'll need to attach the listener to the appended object:
p2.on("mouseover", function () {
d3.select(this).style("stroke-width", 5);
});
Thanks to #Lars Kotthoff for the correction
You can do this through css with the 'hover' event, for instance for the p2 class you are applying you can have some css that looks like this.
p2:hover {
stroke-width: 5;
}
hovering over would change the stroke-width to 5, and once the hover event is over the element will go back to its original stroke-width

Categories

Resources