d3 v4 select behaves differently when using a var - d3.js

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/

Related

d3 multiple different graticules

I am trying to create a "proper" version of the logo at IFMMS Logo
using the graticule feature of d3.js.
At https://gist.run/?id=22361573b05b541ac9799037116aea8d you'll find the current state of the code - specifically version https://gist.run/?id=22361573b05b541ac9799037116aea8d&sha=dd6aef5c64e3ac4c1c917fd5e3c7bbf4b91c75a8
In a loop i am trying to set the graticule steps for different scales of the logo draft.
var lonsteps=6+col*2;
var latsteps=10;
var title='IFMMS Logo';
createLogo(title,id,cx,cy,scale,15,lonsteps,latsteps,debug);
in the "createLogo" function i am using these steps as outlined in the answer:
https://stackoverflow.com/a/19033063/1497139
I get
instead of
so instead of having 6/8/10 steps for the longitudinal grid lines I get three times 6 steps. It seems as if the first setting "overrides" all others although i assign different values.
What is causing this and how can it be fixed?
If you pass a parameter (svgid) to a function use it and don't use the global variable (id) with the same value.
function createLogo(title,svgid,cx,cy,scale,strokeScaleFactor,lonsteps,latsteps,debug) {
// ...
ggraticule.selectAll("path.feature"+svgid)
.data(graticule.lines)
.enter().append("path")
.attr("class", "feature"+svgid)
.attr("d", path);
// ...
}
One indication of the problem can be that your scale is not working.
The reason a HTML ID should be unique.
fix it by making the graticule defs id unique and use this new id.
var ggraticule=defs.append("g")
.attr("id","graticule-"+svgid)
.attr("class", "graticule")
.attr("stroke-width",scale/strokeScaleFactor);
// ...
use(svg,"graticule-"+svgid);
You still have a problem with the background, choose a color and see the effect.
In D3 v5+ you can use the step method on your geoGraticules like so:
var graticules = d3.geoGraticule().step([15, 15])
The default step is 10° and 10° for longitude and latitude.

D3 update tick values without updating the domain

I have an x-axis with long tick names, and I want to provide their abbreviations as tick values. Long tick names are used to set the domain.
axisElement = d3.axisBottom(scale)
.tickValues(tickValues); // these are long names when axis is first drawn
chart.append("g")
.attr("class", "bottomAxis")
.attr("transform", "translate(0, " + height + ")")
.call(axisElement);
I first draw the axis with proper long names which looks like this, and then try to update the ticks using axis.tickValues() function in D3.
updateTicks(tickValues) {
chart.select(".bottomAxis").call(this.axisElement.tickValues(tickValues));
}
But when D3 draws the ticks, it uses the old domain values in scale (which are the long names) and messes up the positioning of abbreviated names. It looks like this. I also get the following error:
Error: <g> attribute transform: Expected number, "translate(NaN,0)".
Which, I believe, is saying that it tried to translate the tick according to old domain values, but found abbreviated value so it can't translate.
I tried changing the scale's domain to abbreviated names, and then D3 positions them appropriately.
My question is, is it possible to change tickValues without changing the domain values?
You can do it like the following. You need to call axisBottom on your svg element and then use tickFormat to set the abbreviated text values. I have included a small example in the fiddle to show you. In the example, I am initially setting the long tick values first by default and then changing it to the abbreviated ones like you want to do.
var abbr = ["ll1", "ll2", "ll3", "ll4"];
svg
.call(d3.axisBottom(x).tickFormat(function(d, i) {
return abbr[i];
}));
JSFiddle - https://jsfiddle.net/tkw07c96/

Setting up Scale Domain and Bar Width for multiple bar charts in one page

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.

Using fields in inherited bound data

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.

D3 multi line chart - strange animation

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.

Resources