I am following this example to add a tooltip to my circles, displayed on a map.
var tooltip = d3.select("body")
.append("div")
.attr("id", "mytooltip")
.style("position", "absolute")
.style("z-index", "10")
.style("visibility", "hidden")
.text("a simple tooltip");
Then Ive got this mouseover
// callbackfunction preparing the data
// then
var feature = g.selectAll("circle")
.data(data.features)
.enter()
.append("circle")
//...
feature.on("mouseover",function(d) {
d3.select(this)
.transition()
.ease("elastic")
.duration(500)
.attr('r', function (d){
return (d.features.xy);
})
d3.select("#mytooltip")
.style("visibility", "visible")
.text(function(d) {
console.log(d.features.xy)
return (d.features.xy)
})
That does not display the value of xy.
Output of console.log is:
TypeError: undefined is not an object (evaluating 'd.xy')
The Problem is obviously that with the d3.select("#mytooltip") statement I enter the var tooltip to which my data.features... is not bound to. How do I bind the circles to the mouseover (which are created in var feature = g.selectAll("circle"), after calling d3.select?
The .data function is expecting an array, to be distributed among several elements ("data" is plural). If you want to give a single "piece of data" to a single element (namely, your tooltip), you need the .datum function:
tooltip.datum(myData)
Alternatively, you can do:
tooltip.data([myData])
In your original code, since you don't have the tooltip variable (nor, for that matter, myData), you can insert it in the mouseover event:
(...)
d3.select("#mytooltip")
.datum(d)
.style("visibility", "visible")
(...)
Another option: you can draw the tooltip directly, without binding any data to it:
d3.select("#mytooltip")
.style("visibility", "visible")
.text(d.features.xy);
Here d still refers to the data of the object you are mouseover-ing, so this should work just as well.
Related
I am working with d3 and javascript and I am new to them. I am trying to pass a variable inside anonymous function for click but I cant get it to work.Here is an example:
var someVariable=xyz;
var mapModel=someObj;
svg.append("g")
.style("display","table")
.style("margin","0 auto")
.data(topojson.feature(mapModel, mapModel.objects[objdisplay]).features)
.enter()
.append("path")
.attr("id", function(d) { return d.id; })
.attr("d", this.path)
.on("click",function(d){
alert(someVariable + d.id)
});
I need to access somevariable inside anonymous function for click but can't seem to get it working.This question might have been asked before but can someone help me in going in the right direction.Thanks
You can't access 'someVariable' inside your click function this way .
Try this...
var someVariable=xyz;
svg.append("g")
.style("display","table")
.style("margin","0 auto")
.enter()
.append("path")
.data([{'someVariable':someVariable}])
.on("click",function(d){
alert(d.someVariable + d.id)
});
So I have a visualization and I'm trying to use d3.tip() - https://github.com/Caged/d3-tip/blob/master/docs/initializing-tooltips.md#d3tip
This is my code-
this.svg = d3.select(".timechart").append("svg")
.attr("width", this.width + this.margin.left + this.margin.right)
.attr("height", this.height + this.margin.top + this.margin.bottom)
.append("g")
.attr("transform", "translate(" + this.margin.left + "," + this.margin.top + ")");
svg.selectAll('.point')
.data(newData)
.enter()
.append("svg:circle")
.attr("cx", function(d,i){
var date = d["date"].match(/(\d+)/g);
date = new Date(date[2], date[0], date[1]);
return xScale(date);
})
.attr("cy", function(d,i){
var quantitySold = yScale(d["quantity-sold"]);
return quantitySold;
})
.attr("fill", "red")
.attr("r", 4)
.on("mouseover", function(d){
tooltip.show();
})
.on("mouseout", function(d){
tooltip.hide();
});
var tooltip = d3.tip()
.attr("class", "tooltip")
.offset([0,5])
.html(function(d){
console.log(d);
return "<strong> 20 </strong>";
});
svg.call(tooltip);
The console.log(d) gives me undefined, when it should give me the datum.
Why?
I also realize - I'm not sure what code I should post here to help - just let me know what would be useful.
The tooltip library that you're using (d3.tip) creates a single html tooltip for the entire visualization. The data for a particular element is passed to the tooltip using the tooltip's .show(d,i) method.
This example from the plug-in's creator shows how it is supposed to work. In particular, note that the show and hide methods are given directly as parameters to the .on(event, function) method of the rectangle selection:
svg.selectAll(".bar")
/* ... */
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
When the event occurs, d3 will therefore call these methods and pass the data object to them as a parameter.
In contrast, in your code:
.on("mouseover", function(d){
tooltip.show();
})
.on("mouseout", function(d){
tooltip.hide();
});
d3 will pass the data to your anonymous function, but you do not pass it on to the show/hide functions. So the data is undefined when the tooltip's show function tries to set the html content of the tooltip.
If you find that all confusing still, you might appreciate this write-up about passing functions as parameters.
Finally, although it isn't your main problem right now, you should be defining the tooltip before assigning its functions to an event handler. If you tried to do .on('mouseover', tooltip.show) before defining tooltip, you would get an error. You only avoided it by wrapping that function call in another function.
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...
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/