I am just starting out with javascript and have a problem understanding this piece of (svg)code below which uses javascript for defining it's x co-ordinate and radius. I understand how data is bound etc. But my question is - For the function which takes two arguments : d and i, where is it defined that the first argument to the function is the dataset and the second is a counter for the circle, ie 0 for the first circle, 1 for the second and so on.
var dataset = [ 5, 10, 15, 20, 25 ];
var circles = svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle");
circles.attr("cx", function(d, i) {
return (i * 50) + 25;
})
.attr("cy", h/2)
.attr("r", function(d) {
return d;
});
Thanks.
This is d3 and so the d3 documentation defines what the function expects
If value is a constant, then all elements are given the same attribute value; otherwise, if value is a function, then the function is evaluated for each selected element (in order), being passed the current datum d and the current index i, with the this context as the current DOM element.
Related
I am learning D3.js and curious on the chaining of methods
This script works:
var data = [32, 57, 112, 250]
var svg = d3.select("svg")
svg.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 30 })
.attr("r", function(d) { return Math.sqrt(d); })
But this script results in nothing:
var data = [32, 57, 112, 250]
var circles = d3.select("svg").selectAll("circle");
circles.data(data);
var circlesEnter = circles
.enter()
.append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 30})
.attr("r", function (d) { return Math.sqrt(d)})
I don't see the different effects on these two different approaches. Can anyone tell me the difference between these?
Thanks in advance!
The issue is that selection.data() doesn't modify an existing selection, it returns a new selection:
[selection.data] Binds the specified array of data with the selected elements,
returning a new selection that represents the update selection: the
elements successfully bound to data. Also defines the enter and exit
selections on the returned selection, which can be used to add or
remove elements to correspond to the new data. (from the docs)
Also,
Selections are immutable. All selection methods that affect which
elements are selected (or their order) return a new selection rather
than modifying the current selection. However, note that elements are
necessarily mutable, as selections drive transformations of the
document! (link)
As is, circles contains an empty selection of circles (size: 0) with no associated data array. Because it is immutable, calling circles.data(data) won't change that selection, and circles.enter() will remain empty. Meanwhile the selection created by circles.data() is lost as it isn't assigned to a variable.
We can chain methods together as in the first code block of yours because the returned selection in the chain is a new selection when using .data(), .enter(), or selectAll(). Each method in the method chain uses the selection returned by the previous line, which is the correct one.
In order to break .data() from the chain, we would need to create a new intermediate selection with selection.data() to access the enter selection:
var circles = d3.select("svg").selectAll("circle");
var circlesData = circles.data(data);
var circlesEnter = circlesData
.enter()
...
var data = [32, 57, 112, 250]
var circles = d3.select("svg").selectAll("circle");
var circlesData = circles.data(data);
var circlesEnter = circlesData
.enter()
.append("circle")
.attr("cy", 60)
.attr("cx", function(d, i) { return i * 100 + 30})
.attr("r", function (d) { return Math.sqrt(d)})
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>
But this would be a bit of an odd approach.
I'm having troubles in understanding how to get each D3 object in a selection to apply a transition.
Consider the follwoing code (here on jsfiddle):
var svg = d3.select('svg');
var dataSet = [10, 20, 30, 40];
var circle = svg.selectAll('circle')
.data(dataSet)
.enter()
.append('circle')
.attr("r",function(d){ return d })
.attr("cx", function(d, i){ return i * 100 + Math.random()*50 })
.attr("cy",50)
.attr("fill",'red')
;
circle.each(function(d,i) {
this
.transition()
.duration(1000)
.attr("cx",this.cx+100);
})
My use of this is wrong. I've also tried with d3.select(this) but I get the dom object corresponding to D3 object.
I'm unable to get the D3 object to apply transition.
The missing part is that you can supply a function to .attr('cx', function (d,i) { ... }) when using a transition, and inside that function you can access the cx attribute using this.getAttribute('cx').
Of course, you also want to make sure to turn it into a number using parseInt(), otherwise it will do string concatenation (because JS, sigh).
So change your final line to:
circle.transition().duration(1000).attr('cx', function(d, i) {
return parseInt(this.getAttribute('cx')) + 100;
});
I am reading the code from http://bl.ocks.org/diethardsteiner/3287802
But I dont understand why and how the mouse-up piece of code works:
var arcs = vis.selectAll("g.slice")
.data(pie)
.enter()
.append("svg:g")
.attr("class", "slice")
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.on("click", up)
;
...
function up(d, i) {
updateBarChart(d.data.category, color(i));
updateLineChart(d.data.category, color(i));
}
I can see that "up" is a mouse-event handler, but what are the "d" and "i" here?
I mean, how does it know what variable it need to pass on as the function Argument when we are calling "on("click", up)? It seems that "d" and "i" are refering to the data associated with "g.slice" and its index, but istn't a mouse-up Event handle supposed to take an Event object as Default Argument?
Moreover, regarding the "d.data.category", I dont see any data of such structure in the code, although there is a dataset variable declared. But how come that "d.data" would refer to the data of a Person in the dataset?
Thank you guys!!!
For someone that has knowledge of JavaScript but is not familiar with D3, this seems strange indeed, but these arguments (or parameters) are already expected by D3:
When a specified event is dispatched on a selected node, the specified listener will be evaluated for each selected element, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element.
These are the famous 3 arguments when you use a function in a D3 selection:
the function is evaluated for each selected element, in order, being passed the current datum (d), the current index (i), and the current group (nodes), with this as the current DOM element.
So, when you do something like this in a D3 selection:
function(d,i,n){
You have the 3 arguments:
d, named like this for "datum", is the datum of the element.
i, for "index", is the index of the element;
n is the group of the element.
Of course, you can name them anything you want ("foo", "bar", "a", "r2d2" etc...), the important here is just the order of the arguments.
Here is a demo to show you this, click the circles:
var width = 400,
height = 150;
var data = [3,19,6,12,23];
var scale = d3.scaleLinear()
.range([10, width - 10])
.domain([0,30]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var circles = svg.selectAll("circle").data(data)
.enter()
.append("circle")
.attr("r", 8)
.attr("fill", "teal")
.attr("cy", 50)
.attr("cx", function(d) {
return scale(d)
})
.on("click", up);
var axis = d3.axisBottom(scale);
var gX = svg.append("g")
.attr("transform", "translate(0,100)")
.call(axis);
function up(d,i){
alert("datum is " + d + "; index is " + i);
}
<script src="https://d3js.org/d3.v4.min.js"></script>
Regarding the d.data.category, it's all well commented in the code: dataset has both "category" and "measure", and it's bound to the SVG. When you use d3.layout.pie() on dataset, it returns an array of objects like this:
{"data": 42, "value": 42, "startAngle": 42, "endAngle": 42, "padAngle": 42}
That's where the d.data comes from.
This one has got to be easy but I can't for the life of me figure out why it's not working.
I've got some D3 code to plot some circles. I've nested arrays of six numbers inside a single variable (called 'dataset'). I'm trying to access those values to use a a y-value for the circle.
var width = 600;
var height = 400;
var dataset = [[16.58, 17.90, 17.11, 17.37, 13.68, 13.95], [20.26,1 3.40, 18.63, 19.28, 20,92, 18.95], [16.32, 23.16, 21.05, 28.16, 23.68, 23.42], [31.32, 30.80, 29.37, 28.16, 32.37, 27.63], [41.32, 39.74, 29.37, 35.00, 35.53, 30.00], [25.83, 38.27, 43.33, 45.83, 44.17, 41.25]];
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d, i) {
return i * 100 + 50;
})
.attr("cy", function(d, i) {
for (var j = 0; j<6; j++){
//console.log(i,j);
return d[i][j]; //THIS IS WHERE I'M FAILING
}
})
.attr("r", 15);
So the x values are just 100 px intervals (I basically want each set in its own column). The y value of each circle should then be the j'th term in the i'th array. So why doesn't d[i][j] return that?
I've got a console.log statement commented out. If I un-commennt that everything logs just as I would expect, but the double bracket notation is clearly not accessing the numbers. If I go straight to the console in the browser and type dataset[0][1], it returns '17.90', so why doesn't it work in this implementation?
So confused.
Thanks
I've prepared a small fiddle to illustrate the problem here
I'm having an issue using d3's exit function to remove elements from the dom.
Say I have an array of 10 elements:
var data = [1 ,4, 5, 6, 24, 8, 12, 1, 1, 20]
I use this data to create a simple horizontal bar chart using d3
d3.selectAll('rect')
.data(data)
.enter()
.attr("class", "rectangle")
.attr("stroke", "black")
.attr("stroke-width","1px")
.attr("fill","none")
.attr("x", 0)
.attr("y", function(d, i) { return 25 * i; } )
.attr("width", function(d) { return 22 * d; } )
.attr("height", "20");
Now after a short delay I'd like to prune my dataset so all that I have left is
var truncatedData = [4,5]
d3.selectAll('rect')
.data(truncatedData )
.exit()
.transition()
.delay(3000)
.remove();
Data is removed successfully but it still shows the first two elements 1,4 instead of 4,5.
How can I remove all but [4,5] from the dom?
By default, d3 matches elements given to .data() by their index and not value. That is, giving it a two-element array in the second call means that the first two elements from the first call (indices 0 and 1) are retained.
To fix this issue in your case, give a function to match elements to the second call to .data(), i.e.
.data([5, 6], function(d) { return(d); })
Fixed jsfiddle here.