D3js v3 to v4 migraiton merge - javascript

I'm having some problem when i try to migrate my d3js graphs from version 3 to version 4. I have solved a lot o issue, but i'm not understandig how "merge" working. In this code data-title attribute is not setted in V4 and contextmenu is not working.
What's the problem? I don't understand how merge is working. Can someone explain me how to fix this code and why i have to fix it in this way since i have more graphs to fix.
var slice = self.svg.select(".slices").selectAll("path.slice")
.data(pie(new_node_data), key);
slice.enter()
.insert("path")
.attr("class", "slice").attr("fill", function(d2) { return color(d2.data.name);} )
.merge(slice) //<-- merge back in update selection
.transition().duration(1000).attrTween("d", tweenIn);
slice.attr('data-title',function(d){
return d.data.period + ' ' + d.data.name;
})
slice.on("contextmenu", function(d,i){
console.log(d.data);
self.context_menu(d.data, i, false);
});
self.attach_graph_items_click_event_handler(slice, false, true);

You are missing something simple, after your merge you don't save the combined update + enter back to a variable (you are missing the slice =):
slice = slice.enter() //<-- SAVE IT TO A VARIABLE
.insert("path")
.attr("class", "slice").attr("fill", function(d2) { return color(d2.data.name);} )
.merge(slice)
....
Here's it broken down with comments:
// this is your update selection
// slice is a selection of all the things being updated
// in your collection path.slice
var slice = self.svg.select(".slices").selectAll("path.slice")
.data(pie(new_node_data), key);
// .enter returns the enter selection
// all the things that are being added
slice = slice.enter()
.insert("path")
.attr("class", "slice").attr("fill", function(d2) { return color(d2.data.name);} )
// this merges the update slice with the enter slice
.merge(slice)
// this is now operating on enter + update
.transition().duration(1000).attrTween("d", tweenIn);

Related

d3 remove and exit are not removing items properly in a stacked bar

I have a stacked bar chart that can be updated and filtered using a dropdown menu. When the chart first renders I don't have issues but when I uncheck an option, the rect does not get removed.
I checked the array after the removal and it does get removed from the array. The issue here is that the rect gets shifted to the far left. The x-axis does get updated and the text associated with that objects gets removed.
My feeling is that I have a problem with the selection, but I am confused because I have rect and g that represents the series for each object. I'm not sure if I am selecting the right object when I am removing elements.
My code: Stacked bar chart with filter
Here is how the chart looks like after removing one item:
As you can see the rect is still there, but shifted to the left.
I have included the important part of my code. Any feedback even about how to make my code work on Plunker is appreciated
d3.selectAll("input").on("change", function() {
var paragraphID = d3.select(this).attr("id");
paragraphID = String(paragraphID);
if (this.checked) {
if (paragraphID === 'A') {
data.push({
production_company: "A",
Pass: 50,
Fail: 65,
total: 11
});
}
} else {
data = data.filter(d => d.production_company !== paragraphID);
}
xScale_production.domain(data.map(function(d) {
return d.production_company;
}));
yScale_production.domain([0, d3.max(series_production, d => d3.max(d, d => d[1]))]);
var group = svg_stack_production.selectAll("g").data(series_production);
var mbars = group.selectAll("rect").data(d => d);
mbars.enter()
.append("rect")
.attr("x", width)
.attr("y", d => yScale_production(d[1]))
.attr("width", xScale_production.bandwidth())
.merge(mbars)
.attr("x", (d, i) => xScale_production(d.data.production_company))
.attr("y", d => yScale_production(d[1]))
.attr("width", xScale_production.bandwidth())
.attr("height", d => yScale_production(d[0]) - yScale_production(d[1]));
group.selectAll("g")
.exit()
.remove();
mbars.selectAll("rect").exit().remove()
});
No need to select elements before calling exit, they are already in selection. Just call:
group.exit().remove();
mbars.exit().remove();
instead of:
group.selectAll("g").exit().remove();
mbars.selectAll("rect").exit().remove();
I solved the issue, there were two issues here. First, I needed to clone the original data and then apply the filtering on it as #Michael Rovinsky suggested.
Second, series_production needed to be updated after filtering the data.
here is the link for the working code.
Working code

getting and using data from CSV file d3 v5

I am trying to create a map visualization using d3.
I have gotten the map to work and am trying to add points on the map.
Depending on some other data in the CSV file (inspection results) I want certain points to have a different color.
I can add the points using this code, but I cannot get the colors to come out correctly (there should be red ones, but all of them are green). I think it is a problem with how I'm trying to get the data out, or just a problem with my JavaScript in general. Any help would be appreciated. Thank you!
function colorr(d) {
if (d == 0) {
return "red";
} else {
return "green";
}
}
var dataset = []
// load data about food
// https://stackoverflow.com/questions/47821332/plot-points-in-map-d3-javascript
// https://groups.google.com/forum/#!topic/d3-js/AVEa7nPCFAk
// https://stackoverflow.com/questions/10805184/show-data-on-mouseover-of-circle
d3.csv('data6.csv').then( function(data) {
// don't know if this actually does anything...
dataset=data.map(function(d) { return [+d["InspectionScore"],+d["Longitude"],+d["Latitude"]];});
g.selectAll('circle')
.data(data)
.enter()
.append('circle')
.attr("cx",function(d) { return projection([d.Longitude,d.Latitude])[0]; }).merge(g)
.attr("cy",function(d) { return projection([d.Longitude,d.Latitude])[1]; }).merge(g)
.attr("r", .4)
.attr("fill", d3.color(colorr( function(d) { return d.InspectionScore } ) ));
});
This can be resolved by changing the last line to:
.attr("fill", d => d3.color(colorr(d.InspectionScore))));
The reason this works is that d3's attr allows you to take the attribute value from a function. In this case you want to transform the data element to either red or blue. This is what the arrow function does in the above code. It is equivalent to :
.attr("fill", function(d) {
return d3.color(colorr(d.InspectionScore));
})
To get a deeper understanding of how D3 works, you can check the tutorials.

d3 v4: merge enter and update selections to remove duplicate code

I understand that merge can be used to combine enter and update selections in d3 v4, as in the simple example here: https://bl.ocks.org/mbostock/3808218.
I have a scatter plot in which multiple variables are displayed on a shared x-axis, for different groups selected by a dropdown box. When a new group is selected, the overall set of datapoints is updated, with points for each variable added like this:
.each(function(d, i) {
var min = d3.min(d.values, function(d) { return d.value; } );
var max = d3.max(d.values, function(d) { return d.value; } );
// Join new data with old elements
var points = d3.select(this).selectAll("circle")
.data(d.values, function(d) { return (d.Plot); } );
// Add new elements
points.enter().append("circle")
.attr("cy", y(d.key))
.attr("r", 10)
.style("opacity", 0.5)
.style("fill", function(d) { return elevColor(d.Elevation); })
.merge(points) //(?)
.transition()
.attr("cx", function(d) { return x((d.value-min)/(max-min)); });
// Remove old elements not present in new data
points.exit().remove();
This whole piece of code is largely duplicated for the overall enter selection and again in the overall update selection (as opposed to the individual variables), which seems less than ideal. How would merge be used to to remove this duplicated code?
The full example is here: http://plnkr.co/edit/VE0CtevC3XSCpeLtJmxq?p=preview
I'm the author of the solution for your past question, which you linked in this one. I provided that solution in a comment, not as a proper answer, because I was in a hurry and I wrote a lazy solution, full of duplication — as you say here. As I commented in the same question, the solution for reducing the duplication is using merge.
Right now, in your code, there is duplication regarding the setup of the "update" and "enter" selections:
var update = g.selectAll(".datapoints")
.data(filtered[0].values);
var enter = update.enter().append("g")
.attr("class", "datapoints");
update.each(function(d, i){
//code here
});
enter.each(function(d, i){
//same code here
});
To avoid the duplication, we merge the selections. This is how you can do it:
var enter = update.enter().append("g")
.attr("class", "datapoints")
.merge(update)
.each(function(d, i) {
//etc...
Here is the updated Plunker: http://plnkr.co/edit/MADPLmfiqpLSj9aGK8SC?p=preview

D3: How to conditionally bind SVG objects to data?

I have here an array of objects that I'm visualising using D3. I bind each object to a group element and append to that an SVG graphic that depends on some object property, roughly like this:
var iconGroups = zoomArea.selectAll("g.icons")
.data(resources)
.enter()
.append("g")
var icons = iconGroups.append(function(d){
if(d.type == "Apple"){
return appleIcon;
}else if(d.type == "Orange"){
return orangeIcon;
})
etc. Now I'd like to extend some of those icons with an additional line. I could add a line element for each data point and set them visible only where applicable, but since I want to add them only for say one out of a hundred data points, that seems inefficient. Is there a way to bind SVG lines to only those objects where d.type == "Apple"?
I would create separate selections for icons and lines, this way:
var iconGroups = zoomArea.selectAll('g.icons')
.data(resources);
iconGroups
.enter()
.append('g')
.classed('icons', true);
iconGroups.exit().remove();
var icons = iconGroups.selectAll('.icon').data(function(d) {return [d];});
icons
.enter()
.append(function(d) {
if(d.type === 'Apple'){
return appleIcon;
}else if(d.type === 'Orange'){
return orangeIcon;
}
}).classed('icon', true);
icons.exit().remove();
var lines = iconGroups.selectAll('.line').data(function(d) {
return d.type === 'Apple' ? [d] : [];
});
lines
.enter()
.append('line')
.classed('line', true);
lines.exit().remove();
.exit().remove() is added just because I add it always to be sure that updates work better. :)
Maybe the code is longer than .filter() but I use the following structure all the time and it's easier to scale it.
edit: apropos comment - If you need to pass indexes, you should pass them in binded data:
var iconGroups = zoomArea.selectAll('g.icons')
.data(resources.map(function(resource, index) {
return Object.create(resource, {index: index})
}));
(Object.create() was used just to not mutate the data, you can use _.clone, Object.assign() or just mutate it if it does not bother you)
then you can access it like:
lines.attr("x1", function(d){ console.log(d.index);})
You could add a class to the icons to be selected (e.g. appleIcon), and use that class in a selector to add the lines.
Use d3 filter.
selection.filter(selector)
Filters the selection, returning a new selection that contains only the elements for which the specified selector is true.
Reference: https://github.com/mbostock/d3/wiki/Selections#filter
Demo: http://bl.ocks.org/d3noob/8dc93bce7e7200ab487d

Making a d3 chart update with new data

I have an example of a chart on jsFiddle which has multiple groups of multiple lines. It draws successfully, but I would like to be able to transition to new sets of data.
The example should update with new data after 4 seconds. Although the chart gets called (and outputs something in the console), the lines aren't updated.
I've tried lots of variations based on existing, simpler examples, with no luck. I suspect its the nested data based on this SO answer that's confusing me even more than usual.
SO insists I have some code in the answer, so here's where I assume I need to add/change something:
svg.transition().attr({ width: width, height: height });
g.attr('transform', 'translate(' + margin.left +','+ margin.right + ')');
var lines = g.selectAll("g.lines").data(function(d) { return d; });
lines.enter().append("g").attr("class", "lines");
lines.selectAll("path.line.imports")
.data(function(d) { return [d.values]; })
.enter().append("path").attr('class', 'line imports');
lines.selectAll('path.line.imports')
.data(function(d) { return [d.values]; })
.transition()
.attr("d", function(d) { return imports_line(d); });
// repeated for path.line.exports with exports_line(d).
The problem is the way you're determining the top-level g element that everything is appended to:
var g = svg.enter().append('svg').append('g');
This will only be set if there is no SVG already, as you're only handling the enter selection. If it exists, g will be empty and therefore nothing will happen. To fix, select the g explicitly afterwards:
var g = svg.enter().append('svg').append('g').attr("class", "main");
g = svg.select("g.main");
Complete demo here.

Categories