I am attempting to get my head around using bound data with d3.js. I'm following the documentation and am now a little confused.
I want to produce donut charts with radii that vary depending on the data. I am comfortable producing the arcs to make up a donut using an array, but am having a hard time working out how to pass along a size parameter with the data binding of the arc. For example, if the data bound to the parent of the arc is something like {size: 20, cont: [1, 7]}, how can I bind the first element of the array as well as the size element? I have a fiddle attempting to show what I am talking about. In that example, the two donuts should be different sizes. I have commented out the kind of thing I suspect should be going on on line 14.
I have tried variations on:
var arcs = donuts.selectAll(".arc")
.data(function(d) { var temp = [];
temp.push(d.cont);
temp.push(d.size);
return temp; })
.enter()
.append("g")
.attr("class", "arc");
But it is clearly not producing what I expect.
The problem here isn't really the data inheritance, but the fact that you're passing the original data to a layout and then only the result of that to your drawing functions. The pie layout does store the original datum in the .data member of the result, but you're only passing it part of the original data.
The "proper" thing to do would be to refactor your data structure such that you can pass it in as-is and use the pie layout's .value() function to tell it how to access the data. Then you can directly access the original data.
There's however a quicker solution -- you can simply use the indices that are passed to your function to index into the original array. The code for this would look like this.
.attr("d", function(d, i, j) { return arc.outerRadius(dataset[j].size)(d); })
Note that you need two indices here because you have nested data -- i would be the index within your array of values for a single pie chart, whereas j denotes the index of the element at the level above that. Updated jsfiddle here.
Related
For some reason, these two code blocks behave differently for their children on the first run:
var date = this.svg.selectAll(".date")
.data(data);
date
.enter()
...
and
var date = this.svg.selectAll(".date")
.data(data)
.enter()
...
In the first example, the subsequent date.selectAll(".bar").data(d => d.animal).enter; has no items in .enter, whereas in the second example, there are items (but the subsequent updates don't work correctly). The even stranger thing is that if I just run the broken block twice, both of my problems are fixed.
When I use the variable, the _groups property is an empty array with the right length (placeholders?). When I chain functions without the variable, the _groups property is an array with the expected elements bound to data. I expected the binding to be done by the end of this block either way, but it's not.
jsfiddle
Side note: if you can help with the animation to start at the bottom only on .enter, that would be incredible
Selections in d3 v4 are immutable. This means that the subselections (to create the rectangles) are:
1: Never operating on the Entered elements
let date = svg.selectAll(".date").data(data);
// date selection/subselections don't contain entered elements
date.enter();
// date selection/subselections STILL don't not contain entered elements
2: Operating on only the Updated parent elements the first time, which are none
let date = svg.selectAll(".date").data(data).enter();
// date selection contains entered, but not updated elements
Solution
I had to reassign the variable used, and merge the parent selection, even though I wasn't doing anything with the Updates from it. Personally I find this pattern to be a little more confusing than v4, and the documentation not giving a great example of this pitfall. It's also awkward to need to assign the variable before/during the .enter just so that you can merge in the Updated elements.
var date = svg.selectAll(".date")
.data(data);
date = date
.enter()
.append("g")
.attr("class", "date")
.attr("transform", d => `translate(${xScale0(d.date)},0)`)
.merge(date);
// date is now a selection that contains both the entered and updated elements
Fixed bars: https://jsfiddle.net/vvag4ycs/4/
Fixed bars & animation: https://jsfiddle.net/vvag4ycs/5/
I have a chord diagram in d3 that looks something like this:
The color of each group is calculated base on a property of the source data (before being transformed to a matrix) and is looked up from the index of each group with something like this:
var groupPath = group.append("path")
.attr("id", function(d, i) { return "group" + i; })
.attr("d", arc)
.style("fill", function(d, i) { return MySourceData[i].inArea ? "blue" : "red"; });
Now what I'd like to do is sort the chords so that all the blues are together and all the reds are together, but there doesn't seem to be a simple way to do that. I know you provide a function to sortGroups (and sortSubGroups for that matter), but it seems the arguments passed to the function are only the values of the chords and I would need to have the indexes to be able to figure out if they should be grouped together or not.
Is there any simple way to sort the groups based on some criteria that is ultimately derived from the index?
It seems the best way to achieve this is to simply order my source data first before I transform it into a matrix. This works fine if the data isn't going to change (which, in my case it wasn't), but if you had data that might change and in changing might end up needing to be resorted, this would be a real pain since you'd need to reorder the matrix.
I am very new to D3. I have a large JSON array that I am pulling into D3 via AJAX to make bar graphs. The array is used to generate multiple slides in a Bootstrap carousel, where each slide is an element of the array (i below) with corresponding sub elements. I have run into issues setting up x and y scale's by extracting directly from this large array so I have put the necessary values in separate arrays (although this obviously has not solved the problem yet).
These arrays, which are the same length as the number of slides, are maxDefectCounts() and numberOfBars(). Note, they are not associative arrays/JSON, they are data that I have extracted from the JSON.
I would like the height of the largest bar in each slide to be equal to maxDefectCounts[i] where i is the current slide. I would like the width of the bar to be numberOfBars[i] / width of SVG that the bar chart is bound to.
For the y-scale I have tried the following with no luck. I'm not sure if you can bind a separate data element to a scale function (note that I am using j instead of I in the function below so as not to be confused with i, which is the slide number)
var y = d3.scale.linear()
.range([height,0])
.data(maxDefectCounts)
.enter()
.domain([0,function(d,j){return d[j];}]);
For width what I am currently doing is iterating through the different bars in d.binSummary and using a fixed value of barWidth (this is for testing). Since each slide has a different number of bars, I would like this to be proportional to numberOfBars[i] instead of this fixed value
var bar = paretoBox.selectAll("g")
.data(function(d) {return d.binSummary;})
.enter()
.append("g")
.attr("transform",function(d,j) {
return "translate(" + j * barWidth + ",0)";
});
Any advice would be much appreciated, thank you in advance.
I identified a workaround. Since independent values are needed for each slide, I built a loop that takes the original JSON as an input and builds a slide for each element. This allows the extraction of the needed information. A similar approach is used here with regard to building multiple charts with a single input array.
I'm using http://bl.ocks.org/mbostock/1283663
and I'm trying to change the following code
d3.json("readme.json", function(root) {
hierarchy.nodes(root);
x.domain([0, root.value]).nice();
down(root, 0);
});
to a JSON.parse (some data). I don't have a problem pulling the JSON data but I am totally confused about what is being setup in the rest of the d3.json process with the hierarchy.node(root), x.domain and down(root)
You're probably going to have to read a little more documentation before having a working understanding of what's going in this function. This is a tricky example to start with; I'm just going to walk you through what my process of trying to understand would look like. Going line by line:
d3.json("readme.json", function(root) {
This loads the referenced json file and calls function with it. 'root' starts out equal to the json file.
hierarchy.nodes(root);
Looking through the code, we find where hierarchy is declared:
var hierarchy = d3.layout.partition()
.value(function(d) { return d.size; });
So hierarchy is some kind of layout and hierarchy.nodes will add some useful attributes to root that will make it easier to graph.
x.domain([0, root.value]).nice();
searching for "x" in the example we find x = d3.scale.linear().range([0, w]). Tt appears that x is a linear scale. Basically, the x function will transform values in the domain - [0, root.value] to the range [0, w]. w is the width of the svg. root.value is a little trickier. The node page says
value - the node value, as returned by the value accessor
But what is the value accessor? The initial declaration of indicates it has something todo with root's 'size' attribute, but what? At this point, the documentation starts to get pretty confusing so you might want to pop open the debugger and see exactly what the value attribute of root and root's children looks like.
down(root, 0);
The down function is unique to the example and well commented. Try reading through it while referencing the documentation and see if you can figure it out.
I have created a multi line chart with a simple animation. In the beginning there are no data and after clicking a button new values are emulated and the line "move" left. The move is animated using a "shift".
The problem occurs when the lines "fill" the whole graph area (that means there are y values for all x values) and then the lines are animated in a different way. It looks like the y values are animated on a curve, not slided to the left.
The animation works good for both axes:
svg.selectAll("g .x.axis")
.transition()
.duration(500)
.ease("linear")
.call(xAxis);
svg.selectAll("g .y.axis")
.transition()
.duration(500)
.ease("linear")
.call(yAxis);
And not for lines (this code helped me a lot)
svg.selectAll("g .city path")
.data(processedData).transition().duration(500)
.ease("linear")
.attr("d", function(d, i) { return line(d.values); })
.attr("transform", null);
The Fiddle is accessible here.
Thanks for help.
The problem is that you're deleting data when there is too much. The way d3 matches data to existing data (when you call the .data() function) is by index. That is, the first element in the array that you pass to .data() matches the first bound data element, regardless of what the data actually looks like.
What happens in your case is that as soon as you start deleting data, the individual data points are updated instead of shifted. That's why you're seeing the "squiggle" -- it's updating each data point to its new value, which is the value the data point to the right had before.
With the code you currently have, this is hard to fix because you are not matching the data for individual lines explicitly. I would recommend that you have a look at nested selections which allow you to draw multiple lines and still explicitly match the data for individual ones. The key is to use the optional second argument to .data() to supply a function that tells it how to match the data (see the documentation). This way you can tell it that some data points disappeared and the other ones should be shifted.
you can get around this problem in 2 step.
in function update() : redraw you .data() with the new point at the end but without remove the first old point (with animation), like that each key is the same before and after transition.
at the end of function update() : you can remove the old value and redraw .data() without animation.