D3.js, Need click event in d3.js - javascript

I am trying to make a bubble chart, in that if i click on a bubble, the title of the bubble should appear in the console. I tried some ways, but was not successful.
d3.json("deaths.json",
function (jsondata) {
var deaths = jsondata.map(function(d) { return d.deaths; });
var infections = jsondata.map(function(d) { return d.infections; });
var country = jsondata.map(function(d) { return d.country; });
var death_rate = jsondata.map(function(d) { return d.death_rate; });
console.log(deaths);
console.log(death_rate);
console.log(infections);
console.log(country);
console.log(date);
//Making chart
for (var i=0;i<11;i++)
{
var f;
var countryname=new Array();
var dot = new Array();
dot = svg.append("g").append("circle").attr("class", "dot").attr("id",i)
.style("fill", function(d) { return colorScale(death_rate[i]); }).call(position);
//adding mouse listeners....
dot.on("click", click());
function click()
{
/***********************/
console.log(country); //i need the title of the circle to be printed
/*******************/
}
function position(dot)
{
dot .attr("cx", function(d) { return xScale(deaths[i]); })
.attr("cy", function(d) { return yScale(death_rate[i]); })
.attr("r", function(d) { return radiusScale(infections[i]); });
dot.append("title").text(country[i]);
}
}
});
I need the title of circle to be printed
Please help!!!

You had the good idea by using the on("click", ...) function. However I see two things:
The first problem is that you don't call the function on the click event but its value. So, you write dot.on("click", click()); instead of dot.on("click", click);. To understand the difference, let's imagine that the function click needs one argument, which would for example represent the interesting dot, what would it be? Well, you would write the following:
dot.on("click", function(d){click(d)})
Which is equivalent (and less prone to errors) to writing:
dot.on("click", click)
Now, the second point is that, indeed you want to pass the node as an argument of the function click. Fortunately, with the on event, as I used in my example, the function click is called with the argument d which represents the data of dot. Thus you can now write:
dot.on("click", click);
function click(d)
{
console.log(d.title); //considering dot has a title attribute
}
Note: you can also use another argument by writing function click(d,i) with i representing the index in the array, see the documentation for more details.

If you have a title on your data,
dot.on('click' , function(d){ console.log(d.title); });

Related

D3 Line altered after a zoom

I am a beginner in D3 and JavaScript development, i have succeed to generate a multi-line graph ( generated with an JS object) with interactivity. Every control work except that zoom alter line form.
I have tried to change data quantity and i don't know which part is guilty of that behavior. It appears after a given time, the line doesn't change and stay the same.
These pictures show this latter effect :
capture with the right line:
capture with altered line:
If anyone have any idea for resolve that.
[edit] i make another pass on my code and the problem seems linked to path update. i add these blocks of code if anyone see something wrong.
function line_gen(num){
graph['lineFunction'+num] = d3.line()
.defined(function(d) {return !isNaN(d[graph.dict[num]]) })
.x(function(d) { return graph.xScaleTemp(d[graph.dict[0]]);})
.y(function(d) { return graph.yScaleTemp(d[(graph.dict[num])])})
.curve(d3.curveMonotoneX);
}
function updateData() {
var t;
function data_join(num){
if(!(graph.oldFdata.length)){
graph.dataGroup.select(".data-line"+graph.opt.name+num)
.transition()
// .duration(graph.spd_transition!=-1?graph.spd_transition:0)
.attr("d", graph['lineFunction'+num]((graph.Fdata)));
}else{
let update = graph.dataGroup.select(".data-line"+graph.opt.name+num)
.data(graph.Fdata);
update.enter()
.append("path")
.attr("d",graph['lineFunction'+num]);
update.exit().remove();
// graph.dataGroup.select(".data-line"+graph.opt.name+num)
// .transition()
// .duration(graph.spd_transition!=-1?graph.spd_transition:0)
// .attrTween('d',function(d){
// var previous =d3.select(this).attr('d');
// var current =graph['lineFunction'+num]((graph.Fdata));
// return d3.interpolatePath(previous, current);
// });
}
}
for (var i = 1; i < graph["keys_list"].length; i++) {
if(graph[("lineFunction" + i)]==null){
indic_gen(i);
indicGrp(i);
}
data_join(i);
}
for (var i = 1; i < (Object.keys(graph.Fdata[0]).length); i++) {
/* update_tooltip(i); */
set_tooltip(i);
}
}
I have resolved my problem by rerendering it once more. Not the best solution but it's works.

How to call backbone.model's this inside d3.drag context

I have the following backbone model with a d3.drag functionality. I cannot call the model's this inside the d3's context.
I came across with solutions for similar questions by defining a variable model=this and calling by model.draw.. but how can I add it inside d3's drag?
DataMapper.Models.Anchor = Backbone.Model.extend({
defaults: {
//...
},
initialize : function(){
d3.select("#anchor").call(this.dragAnchor); //make the #anchor draggable
},
dragAnchor: d3.drag()
.on("start", function (d) {
console.log("something"); //it prints
var thisDragY = this.drawSomething(a,b,c);
// this.drawSomething is not a function
// because inside d3.drag(), 'this' refers to #anchor
// what I want to refer is the model
})
.on("drag", function (d) {})
.on("end", function (d) {}),
drawSomething: function (parent, cx, cy) {
//code
}
});
Is there a way to use underscore's bind to achieve my desired goal? Link to a useful article.
The solution was found by a team member - to call the drag as a function.
DataMapper.Models.Anchor = Backbone.Model.extend({
defaults: {
//...
},
initialize : function(){
d3.select("#anchor").call(this.dragAnchor()); //make the #anchor draggable
},
dragAnchor: function(){
var self=this;
return d3.drag()
.on("start", function (d) {
console.log("something"); //it prints
var thisDragY = self.drawSomething(a,b,c);
})
.on("drag", function (d) {})
.on("end", function (d) {}),
drawSomething: function (parent, cx, cy) {
//code
}
});

d3 append() with function argument

This works:
// A
d3.select("body").selectAll(".testDiv")
.data(["div1", "div2", "div3"])
.enter().append("div")
.classed("testDiv", true)
.text(function(d) { return d; });
The following snippet is identical except that the argument for append, instead of
being "div" as above, is a function(d) that simply returns "div":
// B
d3.select("body").selectAll(".testDiv")
.data(["div1", "div2", "div3"])
.enter().append(function(d) { return "div"; })
.classed("testDiv", true)
.text(function(d) { return d; });
B, however, does not work, and instead returns the error message
"Uncaught TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not
of type 'Node'."
How is "div" as an argument for append() different from function(d) { return "div"; }?
The short answer is if you are giving append a function as an argument the function must return a DOM element. The documentation of the append method states:
The name may be specified either as a constant string or as a function that returns the DOM element to append.
The following is a valid use of append with a function as an argument:
.append(function() { return document.createElement('div');});
As the code below does not return a DOM element it would be considered invalid.
.append(function() { return 'div';});
The reason for this may be seen in the source code:
d3_selectionPrototype.append = function(name) {
name = d3_selection_creator(name);
return this.select(function() {
return this.appendChild(name.apply(this, arguments));
});
};
function d3_selection_creator(name) {
function create() {
var document = this.ownerDocument, namespace = this.namespaceURI;
return namespace ? document.createElementNS(namespace, name) : document.createElement(name);
}
function createNS() {
return this.ownerDocument.createElementNS(name.space, name.local);
}
return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
}
As you can see if typeof name === "function" (near the bottom) is true the create or createNS functions are never called. As appendChild only accepts a DOM element the function given to append must be a DOM element.
If the purpose for adding a function within the .append() is to have a conditional statement, then you have to make sure to have a return for every case.
You can't choose to append a div only sometimes. A DOM element has to always be returned otherwise you will get the same error.
For example this will fail:
.append(function(d) {
if(sometimesTrue) {
return document.createElement('div');
}
});
An else {} is required to return some other DOM element for the .append() to work properly.
This is something I saw with D3.v3 so not sure if handled better in newer versions. Hope that helps someone.

Simple function does not work within if-statement

I have a somewhat nested JSON file:
{
"name": "1370",
"children": [
{
"name": "Position X",
"value": -1
},
{...}
]
"matches": [
{
"certainty": 100,
"match": {
"name": "1370",
"children": [
{
"name": "Position X",
"value": -1
},
{...}
]
}
}
]
}
I want to display it using a modified Collapsible Tree. I want to display the "match" and "certainty" when hovering the corresponding node. I've used the simple tooltip example for this.
Now I have something like this:
var nodeEnter = node.enter().append("g")
...
.on("mouseover", function(d) {
if (d.matches) {
return tooltip.style("visibility", "visible")
.text( function(d) { return d.name; } );
}
} )
...
;
I'm just using d.name for testing. I want to write a more complex function later. But that doesn't work at all. I get a tooltip, but it's empty (or contains the default value). The point which I don't understand is, that the following works:
if (d.matches) {
return tooltip.style("visibility", "visible")
.text( d.name );
}
Therefore it seems to me, that a function doesn't work at this point. What am I doing wrong?
The mistake you are making is that in your call to jQuery's .text() method, you are passing in a function, but what you want to pass in is the return value of that function. In order to do this, you simply need to invoke the function you are passing in with the argument it expects:
var nodeEnter = node.enter().append("g")
...
.on("mouseover", function(d) {
if (d.matches) {
return tooltip.style("visibility", "visible")
.text( function(d) { return d.name; }(d) );
}
} )
...
;
notice how the function is invoked using (d) after it is declared
The tooltip in the linked example does not have any data associated with it. Hence, if you try to use the text function with an accessor, it cannot get any data.
My guess is that you do not want to take data from the tooltip, but instead work with the data passed by D3 to your mouseover event:
var nodeEnter = node.enter().append("g")
...
.on("mouseover", function(d) { // <-- This is the data passed by D3, associated to your node.
if (d.matches) {
var newName = computeNameFromData(d);
return tooltip.style("visibility", "visible")
.text( newName ); // <-- Just pass a string here.
}
} )
...
;

Function chaining

I am struggling to understand how this JavaScript code work. I am learning JS, and not exposed to a dynamic, functional language before. So, I visualize function calls in bit procedural, hierarchical order. With d3.js, one can draw svg elements as explained here
var dataset = [ 5, 10, 15, 20, 25 ];
d3.select("body").selectAll("p")
.data(dataset)
.enter()
.append("p")
.text("New paragraph!");
Let’s change the last line:
.text(function(d) { return d; });
Check out what the new code does on this demo page.
Whoa! We used our data to populate the contents of each paragraph, all thanks to the magic of the data() method. You see, when chaining methods together, anytime after you call data(), you can create an anonymous function that accepts d as input. The magical data() method ensures that d is set to the corresponding value in your original data set, given the current element at hand.
This magic, mentioned above is what I fail to understand. "d" is not a global variable, as if I change to another (c) name, it still works. So, the data method may be setting the value for the anonymous fn.
But, typically(with my limited reading) chaining is possible because the current function returns an object, on which the next method can be invoked. In the above case, how data method knows whether a text ("New paragraph!") is passed by the user, otherwise pass the data to the anonymous fn. The doubt is, the text method is down the line and data() is already executed. How the data is passed to the anonymous function?
thanks.
Digging into d3.js internals shows the following result for text function:
d3_selectionPrototype.text = function(value) {
return arguments.length < 1
? this.node().textContent : this.each(typeof value === "function"
? function() { var v = value.apply(this, arguments); this.textContent = v == null ? "" : v; } : value == null
? function() { this.textContent = ""; }
: function() { this.textContent = value; });
};
In case the supplied argument is a function, the following code gets executed:
this.each(function() {
var v = value.apply(this, arguments); // executing function provided
this.textContent = v == null ? "" : v;
});
Function each is declared as:
d3_selectionPrototype.each = function(callback) {
for (var j = -1, m = this.length; ++j < m;) {
for (var group = this[j], i = -1, n = group.length; ++i < n;) {
var node = group[i];
if (node) callback.call(node, node.__data__, i, j); // this is the line you are interested in
}
}
return this;
};
so on each invocation it supplies an element from this. And, getting down to it, this is populated by data function invocation.
Well, I have never used d3 before, but this is what I understand.
d is the data object (I would call it data instead of d had set in the data() method.
So what does the text() method does? Will it will call the function and use it's output, something like this:
function text (callback) {
var theText;
if (typeof callback === "function") {
theText = callback(dataset);
} else {
theText = callback;
}
// does something more
}
So, if callback is a function call it, and use its return value as text.
Then, what I'm guessing, is that if the function is an array, it will call the text method for each element in the array.
Something like this...
function text(callback) {
var theText;
if (typeof callback === "function") {
theText = callback(dataset);
} else {
theText = callback;
}
if (theText instanceof Array) { // this is not the best way to check if an object is an array, I'll come back to this later. I'm sorry.
for (var i=0, len=theText.length; i<len; i++) {
text(theText[i]);
}
} else {
// do something else
}
// do something more
}
please take into account that this would be a really simple version of what really happens.
If it's not clear enough please let me know.

Categories