I have a graph made with d3.js and I have the following attributes and properties for the nodes:
// Enter any new nodes at the parent's previous position
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
.on("click", click)
.on("dblclick", dblclick)
I would like to add the ability to underline the node title when hovering over it. Something like this which unfortunately doesn't work:
var nodeEnter = node.enter().append("g")
.on("mouseover").style("text-decoration","underline")
.on("mouseout").style("text-decoration","none")
EDIT: I would prefer to put a condition to make this happen only for some nodes of the graph.
You aren't using the selection.on() method correctly. In order to do something on an event you need to provide the method with a second parameter: a function that describes the action taken on the event:
D3v6+
.on("mouseover", function(event, datum) { ... })
D3v5 and before
.on("mouseover", function(datum, index, nodes) { ... })
In all versions of D3 this will be the target element (unless using arrow notation). The datum is the data bound to the target element, one item in the array passed to selection.data().
If you only provide one parameter it returns the current event handling function assigned to that event. In your case you likely haven't done this already (because you are attempting to do so), so .on("mouseover").style(...) will return an error such as "Cannot find property style of null" because .on("mouseover") will return null: there is no event handling function assigned to this event to return.
So, to highlight nodes on mouseover with some logic so we can have different outcomes for different nodes, we can use something like:
selection.on("mouseover", function(event, datum) {
if(datum.property == "someValue") {
d3.select(this).style("text-decoration","underline");
}
})
.on("mouseout", function(event,datum) {
d3.select(this).style("text-decoration","none");
})
Where the if statement can be replaced with whatever logic you prefer.
I see you are probably using a hierarchical layout generator, D3's hierarchical layout generators nest the original datum's properties into a data property so that layout properties and non layout properties do not collide, so datum.property may reside at datum.data.property (log the datum if you are having trouble).
var svg = d3.select("body")
.append("svg");
var data = [
"Underline on mouse",
"Never underline"
];
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.attr("x", 20)
.attr("y", (d,i)=>i*50+40)
.text(d=>d)
.on("mouseover", function(event, datum) {
if(datum == "Underline on mouse") {
d3.select(this).style("text-decoration","underline");
}
})
.on("mouseout", function(event,datum) {
d3.select(this).style("text-decoration","none");
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/7.0.0/d3.min.js"></script>
You can add an underline on hover using CSS
.node:hover{
text-decoration: underline;
}
Related
I have code similar to the example in this plunkr.
I am trying to override the ".link" entry in the CSS to download as SVG document.
.link {fill: none; stroke: #ccc; stroke-width: 1.5px;}
The problem is that once I do that, the links are not removed on node expansion or collapse.
I commented the class attribute in the original code and inserted the attributes in the following function:
// Update the linksâ¦
var link = svg.selectAll("path.link")
.data(links, function (d) {
return d.target.id;
});
// Enter any new links at the parent's previous position.
link.enter().append("path", "g")
//.attr("class", "link")
.attr("stroke", "#ccc")
.attr("fill", "none")
.attr("stroke-width", "2px")
.attr("x", boxWidth )
.attr("y", boxHeight)
.attr("d", function (d) {
console.log(source)
var o = {
x: source.x0,
y: source.y0
};
return diagonal({
source: o,
target: o
});
});
Any insight as to why this behavior is occurring?
Your original code is:
var link = svg.selectAll("path.link")
...
link.enter()
.append("path")
.attr("class","link")
...
If you remove the class of the appended items, then every time you draw the tree the first selection (link) will be empty (because there are no paths with class link). So, any links in the data array will be entered. This is why the code works as expected initially. However, no link will ever be updated or exited because there are no elements in the selection to either update or exit - the selection will always be empty.
If these are your only paths, you could change the selection selector to be:
var link = svg.selectAll("path")
...
Eg.
Or, alternatively, you could keep the class associated with the elements, but remove any css styling associated with that class. In either event, you'd apply the styling properties with .attr() still, as you have done.
I'm working on a d3 force layout graph, with looks pretty like this.
What I want is the root node to be fixed not draggable. I fixed the root node in the json file by adding
"fixed": true
but it is still draggable. In my JS file there is the code
var nodeEnter = node.enter().append("g")
.attr("class", "node")
.on("click", click)
.call(force.drag);
If I remove the last line of this code, the whole graph isn't draggable anymore. I think 'force' is a global variable and determines, that the whole graph is draggable. But I want only the root node not to be draggable and the should be draggable. How can I do that?
You have to remove the drag event from the node you wish to stay fixed.
Here is an example : http://jsfiddle.net/qvco2Ljy/112/
I have looped through the data to give the first element a fixed attribute and also a donotmove attribute as when you drag and let go, perhaps you want the node to stay fixed (for children i mean), so using d.fixed here will cause a conflict :
graph.nodes.forEach(function(d, i) {
d.donotmove = false;
if (i == 0) {d.donotmove = true; d.fixed = true;}
})
And when calling drag events, check for the donotmove attribute like so :
var drag = d3.behavior.drag()
.origin(function(d) {
return d;
})
.on("dragstart", function(d) {
if (d.donotmove) return;
dragstarted(d)
})
.on("drag", function(d) {
if (d.donotmove) return;
dragged(d)
})
.on("dragend", function(d) {
if (d.donotmove) return;
dragended(d)
})
Hope that helps :)
I have two identical charts. The graphics for them are built like so:
circles.enter().append("circle")
.attr("r", 0)
.attr("fill", function(d) { return fill_color; })
.attr("class", function(d) { return "circle_" + d.id; })
.on("mouseover", function(d, i) { build_tooltip(d, i, this); })
.on("mouseout", function(d, i) { hide_tooltip(d, i, this); });
On mouseover, it triggers the following function:
build_tooltip = function(data, i, element) {
var content = "Title: " + data.title;
show_tooltip(content, d3.event);
}
My question is: How can I make it so mousing over a circle in Chart #1 triggers the same mouseover event in Chart #2, but with unique data for each chart? Chart #2 must generate its own set of data (in this example, just a title). So, how can I make Chart #2's mouseover event fire whenever Chart #1's does?
In jQuery, this would be quite simple -- there is a literal .trigger() event. But how can I go about accomplishing the same with D3?
Have you tried using D3's dispatch? If not, see through this example for more details on how to use it.
In Mike Bostocks example http://bost.ocks.org/mike/nations/ there is so much data that putting the names of the countries there would make it chaotic, but for a smaller project I would like to display it.
I found this in the source:
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter().append("circle")
.attr("class", "dot")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(position)
.sort(order);
dot.append("title")
.text(function(d) { return d.name; });
But somehow a title never shows up. Does anybody have an idea, how to display the name, next to the bubble?
As the other answer suggests, you need to group your elements together. In addition, you need to append a text element -- the title element only displays as a tooltip in SVG. The code you're looking for would look something like this.
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(interpolateData(2004))
.enter()
.append("g")
.attr("class", "dot")
.call(position)
.sort(order);
dot.append("circle")
.style("fill", function(d) { return color(d); });
dot.append("text")
.attr("y", 10)
.text(function(d) { return d.name; });
In the call to position, you would need to set the transform attribute. You may have to adjust the coordinates of the text element.
Unfortunately grouping the text and circles together will not help in this case. The bubbles are moved by changing their position attributes (cx and cy), but elements do not have x and y positions to move. They can only be moved with a transform-translate. See: https://www.dashingd3js.com/svg-group-element-and-d3js
Your options here are:
1) rewrite the position function to calculate the position difference (change in x and change in y) between the elements current position and its new position and apply that to the . THIS WOULD BE VERY DIFFICULT.
or 2) Write a parallel set of instructions to setup and move the tags. Something like:
var tag = svg.append("g")
.attr("class", "tag")
.selectAll(".tag")
.data(interpolateData(2004))
.enter().append("text")
.attr("class", "tag")
.attr("text-anchor", "left")
.style("fill", function(d) { return color(d); })
.text(function(d) { return d.name; })
.call(tagposition)
.sort(order);
You will need a separate tagposition function since text needs 'x' and 'y' instead of 'cx', 'cy', and 'r' attributes. Don't forget to update the "displayYear" function to change the tag positions as well. You will probably want to offset the text from the bubbles, but making sure the text does not overlap is a much more complicated problem: http://bl.ocks.org/thudfactor/6688739
PS- I called them tags since 'label' already means something in that example.
you have to wrap the circle element and text together , it should look like
<country>
<circle ></circle>
<text></text>
</country>
This is all the code that I have, there is something wrong with the "on click" part that I couldn't figure out.
It errors and says "drill not defined"
Isn't it the way we can call a method on click event of one of those bar charts that I am drawing in the D3 section?
$( document ).ready(function() {
gon.data.pop(); // get rid of query_time element.
var dataset = gon.data;
function drill(d, i) {
console.log(d.total_count); //data object
var url = "http://localhost:4567/drill/" + d.brand_name + "/" + d.generic_name;
window.location.href = url;
}
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.attr("onclick", "drill()")
.style("height", function(d) {
return d.brand_name + "px";
});
});
To add event listeners to selections you should use selection's on method.
You can write
d3.select("body").selectAll("div")
.data(dataset)
.enter()
.append("div")
.attr("class", "bar")
.on("click", drill)
.style("height", function(d) {
return d.brand_name + "px";
});
Move your declaration of the drill function to outside of your document-ready handler.
Better still, use the on method that d3 provides, rather than the onclick attribute.
you can also use jQuery .click() if you want, here's an example
$("#test").click(function(){
sayHello();
});
jsfiddle here:
http://jsfiddle.net/SGRmX/1/