Select and Style Objects via Data id - javascript

I am trying to have a mouse hover event where the circle radius gets bigger, and the corresponding data label increases font size. The data labels are on the circle, in the graph itself. So my idea was to have two functions, one to style the circles nice and smooth with a transition, and use a separate function with tweening to style the text. I want to call both functions at the same time and have them only style objects with data binding matching its 'id'. Id is just an index that I parsed from the .tsv. The idea is both the text and the circle have the same 'id'.
!boring circle svg code above
.on('mouseenter', function(d) {circle_event(this); text_event(this);})
function circle_event(id) {
if (d3.select(this).data==id) {
d3.select(this)
.transition()
.attr('r', radius*1.5)
.duration(500);
}
}
function text_event(id) {
if (d3.select(this).data==id) {
d3.select(this)
.transition()
.styleTween('font', function() {return d3.interpolate('12px Calibri', '20px Calibri' )
.duration(500);
}
}
For some reason when I hover over the circles, nothing happens. Dev tools didn't have any errors. If I had to guess, I have misunderstood how to use d3's select functions.
Thank you
EDIT
Please note I need to call the circle and text style functions simultaneously I will accept the answer that shows how to style the circle and its corresponding data_label text JUST by mousing over the CIRCLE. It seems this cannot be used as a circle and a text object concurrently. Other solutions aside from this-based are welcome.

It looks like you edited your question which changes things considerably. Here is one approach that could work or be modified to make it work:
// Assumes circle & text objects have the same .x, .y and .id data bound to them and that
// when created, they each have classes "circle-class" and "text-class" respectively
!boring circle svg code above
.on('mouseenter', function(d) {circle_event(d.x, d.y, d.id); text_event(d.x, d.y, d.id);})
function circle_event(x, y, id) {
d3.selectAll(".circle-class")
.filter(function (d) {
return (d.x === x) && (d.y == y) && (d.id == id);
})
.transition()
.attr('r', radius * 1.5)
.duration(500);
}
function text_event(x, y, id) {
d3.selectAll(".text-class")
.filter(function (d) {
return (d.x === x) && (d.y == y) && (d.id == id);
})
.transition()
.styleTween('font', function() {return d3.interpolate('12px Calibri', '20px Calibri' )
.duration(500);
}
Alternatively, if you structure the creation of the circle and text DOM elements such that they have a sibling relationship, you could get a reference to the text selection using d3.select(this.previousElementSibling) where this is the circle node that is being moused over (See here - How to access "previous sibling" of `this` when iterating over a selection?). Such an approach could use the code in my previous reply above.

Your primary issue is that this is not what you want it to be in your nested function. It is no longer bound to your circle DOM node that was being hovered over but instead is referencing the global window.
More info on the scoping of this in javascript can be found here:
Javascript "this" pointer within nested function
http://javascriptissexy.com/understand-javascripts-this-with-clarity-and-master-it/
Try changing to this:
!boring circle svg code above
.on('mouseenter', function(d) {circle_event(this, d.id)})
function circle_event(selection, id) {
if (d3.select(selection).data==id) {
d3.select(selection)
.transition()
.attr('r', radius*1.5)
.duration(500);
}
}
function text_event(selection, id) {
if (d3.select(selection).data==id) {
d3.select(selection)
.transition()
.styleTween('font', function() {return d3.interpolate('12px Calibri', '20px Calibri' )
.duration(500);
}
}

You should pass this in the function :
.on('mouseenter', function(d) {circle_event(this)})
function circle_event(item) {
d3.select(item)
.transition()
.attr('r', radius*1.5)
.duration(500);
}
function text_event(item) {
d3.select(item)
.transition()
.styleTween('font', function() {return d3.interpolate('12px Calibri', '20px Calibri' )
.duration(500);
}
You dont need d.id as this is already the selected circle

Related

Underline node text on mouse over

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;
}

Place text inside a circle. d3.js

I am trying to modify a code, and in the modifications that I have made, I have not been able to put a text in the middle of a circle. I've tried many things, and I've seen several examples but it does not work for me. How can I do it?
I know it should be done in this piece, and I add a text tag but it does not work.
bubbles.enter().append('circle')
.classed('bubble', true)
.attr('r', 0)
.attr('fill', function (d) { return fillColor(d.group); })
.attr('stroke', function (d) { return d3.rgb(fillColor(d.group)).darker();
})
.attr('stroke-width', 2)
.on('mouseover', function(){})
.on('mouseout', function(){});
http://plnkr.co/edit/2BCVxQ5n07Rd9GYIOz1c?p=preview
Create another selection for the texts:
var bubblesText = svg.selectAll('.bubbleText')
.data(nodes, function(d) {
return d.id;
});
bubblesText.enter().append('text')
.attr("text-anchor", "middle")
.classed('bubble', true)
.text(function(d) {
return d.name
})
And move them inside the tick function.
Here is the updated plunker: http://plnkr.co/edit/UgDjqNhzbvukTWU6J9Oy?p=preview
PS: This is a very generic answer, just showing you how to display the texts. This answer doesn't deal with details like size or transitions, which are out of the scope of the question and that you'll have to implement yourself.

d3js attach object to svg

I am creating a very long div containing hundreds of svg lines created by the following method:
function visualizeit(ORFdata,max) {
var browser = d3.select("#viewer")
.append("svg")
.attr("width", max/10)
.attr("height",'50%');
//Add svg to the svg container
for (orf in ORFdata) {
var line = browser.append("svg:line");
var object = ORFdata[orf]
line.datum(object)
line.attr("id", 'mygroup'+orf)
line.attr("x1", function(d){ return ORFdata[orf]["start"]/10})
line.attr("x2", function(d){ return ORFdata[orf]["stop"]/10})
line.attr("y1", function(d){ if (ORFdata[orf]["strand"] == "+1") {return 50} else {return 10}})
line.attr("y2", function(d){ if (ORFdata[orf]["strand"] == "+1") {return 50} else {return 10}})
line.style("stroke", "rgb(6,120,155)")
line.style("stroke-width", orf)
line.on('mouseover', function(d){console.log(d3.select("#mygroup"+orf).datum())})
}
}
However, when I do a mouseover on no matter what line I only get the data back from the last element. At first I thought it was due to 'mygroup' so I added a counter to it +orf but it somehow still erases my older stored data.
When I look in the created html code a svg seems correct by ID at least.
<line id="mygroup50" x1="103356.7" x2="103231.1" y1="10" y2="10" style="stroke: #06789b; stroke-width: 50px;"></line>
But somewhere the link goes awfully wrong...
How I fixed it so far...
var svgContainer = d3.select("body").append("svg")
.attr("width", max/10)
.attr("height", '50%');
//Add svg to the svg container
var lines = svgContainer.selectAll("line")
.data(ORFdata)
.enter()
.append("line")
.attr("x1", function(d){ return d.start/10})
.attr("y1", function(d){ if (d.strand == "+1") {return 65} else {return 10}})
.attr("x2", function(d){ return d.stop/10})
.attr("y2", function(d){ if (d.strand == "+1") {return 65} else {return 10}})
.attr("stroke-width","25")
.attr("stroke",function(d) {if (d.strand == "+1") {return 'green'} else {return 'red'}})
.on('mouseover', function(d) {console.log(d.start)})
}
You're creating a bunch of closures in a loop. Each of the functions you create have the variable orf in their closure scope but your loop is changing the value of orf. By the time the function runs when the mouse over event fires, orf has its final value so therefore your #mygroup + orf selection will always pick up the last element.
Here's a good page on closures that has a section detailing the pitfalls of closures in a loop: http://conceptf1.blogspot.ca/2013/11/javascript-closures.html.
In D3 you can get around this problem by using data joins instead of an external loop. Here's a good tutorial that should help to understand how this works:
http://bost.ocks.org/mike/join/
You need to create different event handlers for each line object, what I mean is store those line ojects them in an associated array or something. This way you are probably overwriting each time.
If you could provide a jsfiddle or something I would be happy to test this theory out for you...

How to display names next to bubbles in d3js motion chart

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>

Using on click & selectAll to remove existing data

I have a set of randomly plotted diamonds, squares & circles on my canvas. I have one of each lined in a straight line which is created by my go variable. I wish to use the onclick function upon this variable to filter or make the shapes disappear depending on which parameter I give it. e.g. squares will only show squares on the canvas etc.
So far I have started with this basic example:
.on("click", function(d){ if (d.shape == 'square') { return alert('success') ;} })
I then moved onto this:
.on("click", function(d){ if (d.shape =='circle') { return d3.selectAll(".node").filter(function(d) {return d.country === 'USA'} ) } ;})
When I have applied that, it doesnt result to any errors or actions. I'm pretty sure I'm going in the right direction, just would like some help getting there
http://jsfiddle.net/Zc4z9/19/
Thanks, in advance!
You are doing nothing with your selection. If you need to hide it just add .style("display", "none")
.on("click", function(d){
if (d.shape =='circle') {
d3.selectAll(".node")
.filter(function(d) {return d.country === 'USA'} )
.style("display", "none");
}
})

Categories