I am able to update my chart by selecting and updating individual attributes but not through enter, update, exit.
This code seems to work just fine:
var chart = d3.select(svg).select("svg");
chart.selectAll("rect").data(data)
.attr("x", function(d, i) {
return i*bar_width;
})
.attr("y", function(d) {
return height - Math.abs(y(d) / 2) - height / 2 + 2;
})
.attr("height", function(d) {
return Math.abs(y(d));
})
.attr("width", bar_width);
However, the below code (where I use enter, update, exit) does not work:
var chart = d3.select(svg).select("svg");
chart.selectAll("rect").data(data)
.enter().append("rect") //Enter
.attr("x", function(d, i) { //Update
return i*bar_width;
})
.attr("y", function(d) {
return height - Math.abs(y(d) / 2) - height / 2 + 2;
})
.attr("height", function(d) {
return Math.abs(y(d));
})
.attr("width", bar_width);
chart.selectAll("rect").exit().remove(); //Remove
I wonder why? FYI: I am using d3 v5.
Related
I'm doing a visual project to show natural disaster in 1900-2018 using d3. I want add an interactive action that one can choose the first year and last year to show.
Originally I create the picture as the following:
d3.csv("output.csv", rowConventer, function (data) {
dataset = data;
var xScale = d3.scaleBand()
.domain(d3.range(dataset.length))
.range([padding, width - padding])
.paddingInner(0.05);
var yScale = d3.scaleLinear()
.domain([0,
d3.max(dataset, function (d) {
return d.AllNaturalDisasters;
})])
.range([height - padding, padding])
.nice();
stack = d3.stack().keys(["Drought", "Earthquake", "ExtremeTemperature", "ExtremeWeather", "Flood", "Impact", "Landslide", "MassMovementDry", "VolcanicActivity", "Wildfire"]);
series = stack(dataset);
gr = svg.append("g");
groups = gr.selectAll("g")
.data(series)
.enter()
.append("g")
.style("fill", function(d, i) {
return colors(i);
})
.attr("class", "groups");
rects = groups.selectAll("rect")
.data(function(d) { return d; })
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(i);
})
.attr("y", function(d) {
return yScale(d[1]);
})
.attr("height", function(d) {
return yScale(d[0]) - yScale(d[1]);
})
.attr("width", xScale.bandwidth())
.append("title")
.text(function (d) {
var rect = this.parentNode;// the rectangle, parent of the title
var g = rect.parentNode;// the g, parent of the rect.
return d.data.Year + ", " + d3.select(g).datum().key + "," + (d[1]-d[0]);
});
d3.select("button")
.on("click", choosePeriod);
I have simplified some code to make my question simple. At the last row, I add an event listener to achieve what I described above. And the update function is choosePeriod. Now it is as following:
function choosePeriod() {
firstYear = parseInt(document.getElementById("FirstYear").value);
lastYear = parseInt(document.getElementById("LastYear").value);
d3.csv("output.csv", rowConventer, function (newdata) {
dataset = newdata;
series=stack(dataset);
groups.data(series);
groups.selectAll("rect")
.data(function (d) {
return d;
})
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScales(i);
})
.attr("y", function(d) {
return yScales(d[1]);
})
.attr("height", function(d) {
return yScales(d[0]) - yScales(d[1]);
})
.attr("width", xScales.bandwidth())
.append("title")
.text(function (d) {
var rect = this.parentNode;// the rectangle, parent of the title
var g = rect.parentNode;// the g, parent of the rect.
return d.data.Year + ", " + d3.select(g).datum().key + "," + (d[1]-d[0]);
});
groups.selectAll("rect")
.data(function (d) {
return d;
})
.exit()
.remove();
})
}
The change of dataset is achieved by rowConventer, which is not important in this question. Now the functionchoosePeriod is not running as envisioned! Theenter and the exit and update are all not work well! The whole picture is a mess! What I want is, for instance, if I input the firstYear=1900 and the lastYear=2000, then the picture should be updated with the period 1900-2000 to show. How can I achieve it?
I am unfamiliar the arrangement of the entire structure, I mean, at some place using d3.select() by class or id instead of label is better, right?
It looks like you've dealt with the enter and the exit selections. The only bit you're missing is the update selection, which will deal with the rectangles that already exist and don't need adding or removing. To do this copy your update pattern but just remove the enter().append() bit, e.g.:
groups.selectAll("rect")
.data(function (d) {
return d;
})
.attr("x", function(d, i) {
return xScales(i);
})
.attr("y", function(d) {
return yScales(d[1]);
})
.attr("height", function(d) {
return yScales(d[0]) - yScales(d[1]);
})
.attr("width", xScales.bandwidth())
.append("title")
.text(function (d) {
var rect = this.parentNode;// the rectangle, parent of the title
var g = rect.parentNode;// the g, parent of the rect.
return d.data.Year + ", " + d3.select(g).datum().key + "," + (d[1]-d[0]);
})
This is the final updated code. In a update function that takes a dataset object this code will render a real time legend. Other chart elements can be initialized when the script is loaded and the enter, update and exit selections for those belong in the update function as well. Maybe it is useful. Was for me. I think I can add this to any chart. Working fiddle
var legendG = svg.selectAll(".legend")
.data(donut(dataset), function (d) {
return d.data.label;
})
var legendEnter = legendG.enter();
legendEnter = legendEnter.append("g")
.attr("class", "legend");
//Now merge Enter and Update and adjust the location
legendEnter.merge(legendG)
.attr("transform", function (d, i) { return "translate(" + (width
- 350) + "," + (i * 15 + 20) + ")"; })
.attr("class", "legend");
legendG.exit().remove();
// Apend only to the enter selection
legendEnter.append("text")
.text(function (d) {
return d.data.label;
})
.style("font-size", 12)
.attr("y", 10)
.attr("x", 11);
//Now merge Enter and Update the legend color
legendEnter.merge(legendG)
.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("fill", function (d, i) {
console.log('index: ' + i + ' ' + d.data.label)
var c = color(i);
return c;
});
You should update the transform attribute of the update and enter selection together after merging the selection:
var legendG = svg.selectAll(".legend")
.data(donut(dataset), function (d) {
return d.data.label;
})
var legendEnter= legendG.enter().append("g")
.attr("class", "legend");
//Now merge Enter and Updatge and adjust the location
legendEnter.merge(legendG)
.attr("transform", function (d, i) { return "translate(" + (width - 500) + "," + (i * 15 + 20) + ")"; })
.attr("class", "legend");
legendG.exit().remove();
// Apend only to the enter selection
legendEnter.append("rect")
.attr("width", 10)
.attr("height", 10)
.attr("fill", function (d, i) {
return color(i);
});
...
Updated fiddle from previous post:fiddle
I'm trying to add some space/padding for a nvd3 multi bar chart. "groupSpacing" is not what I need, since it only adds space between groups. I'll need space between each bar inside group. I found one link in github support. Can you post any solution or tweak?
I also found a d3 example of grouped bar chart. Any help in this example also very helpful to me.
Thanks.
I have draw a d3 group barchart:
fiddle
You can adjust the groupSpacing by change the code on line 56:
var groupSpacing = 6;
Technically i just achieve it by change the width of each rects' width:
var barsEnter = bars.enter().append('rect')
.attr('class', 'stm-d3-bar')
.attr('x', function(d,i,j) {
return (j * x1.rangeBand() );
})
.attr('y', function(d) { return y(d.y); })
.attr('height', function(d) { return height - y(d.y); })
.attr('width', x0.rangeBand() / barData.length - groupSpacing )
.attr('transform', function(d,i) {
return 'translate(' + x0(d.x) + ',0)';
})
.style("fill", function(d, i, j) {
return color(data[j].key);
});
Hope it helps you understand how you can achieve it in d3.
I minus the number of group spacing from the "width" attribute also. I found that the x-axis label looks a little off after I did that so I add the (group spacing / 2) to the "x" attribute. Here is the example of my code.
var groupSpacing = 15;
var rect = groups.selectAll("rect")
.data(function (d) { return d; })
.enter()
.append("rect")
.attr("x", function (d) { return x(d.x) + (groupSpacing / 2) ; })
.attr("y", function (d) { return y(d.y0 + d.y); })
.attr("height", function (d) { return y(d.y0) - y(d.y0 + d.y); })
.attr("width", x.rangeBand() - groupSpacing)
I want to update my bar chart when the value of the chart get changes.
I get data for the chart from restful services.
I don't want to delete an entire chart and then redraw; instead I wanted to update the existing bar values with the new values.
1.Each and Every time the number of rows is getting changed when i get input from webservice for dataset.
2.I Can manage to update the bars height value when the number of rows is equal.But it contain more number of rows than before i have the problem facing unexpected output.
I submitted the sample code.//i just use this code from stack overflow site.
function mf(){
data = d3.range(10).map(next);
var rect= chart.selectAll("rect")
.data(data)
.attr("x", function(d, i) { return x(i) - .5; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d.value); });
rect.enter()
.append("svg:rect")
.attr("x", function(d, i) { return x(i) - .5; })
.attr("y", function(d) { return h - y(d.value) - .5; })
.attr("width", w)
.attr("height", function(d) { return y(d.value); });
rect.exit().remove();
}
First time using d3.js and am experimenting with using this example: http://bl.ocks.org/christophermanning/1734663 in a site I'm making, but I want the fill of the shapes to be images instead of a purple color.
I think the relevant code is inside the update function as follows:
path = path.data(d3_geom_voronoi(vertices))
path.enter().append("path")
// drag node by dragging cell
.call(d3.behavior.drag()
.on("drag", function(d, i) {
vertices[i] = {x: vertices[i].x + d3.event.dx, y: vertices[i].y + d3.event.dy}
})
)
.style("fill", function(d, i) { return color(0) })
path.attr("d", function(d) { return "M" + d.join("L") + "Z"; })
.transition().duration(150).style("fill", function(d, i) { return color(d3.geom.polygon(d).area()) })
path.exit().remove();
So first I comment out..
.style("fill", function(d, i) { return color(d3.geom.polygon(d).area()) })
..so my image fill wont be overwritten on update, and then I need to replace..
.style("fill", function(d, i) { return color(0) })
..with "something", but before I replace it I define the image by adding..
var image = svg.append("image")
.attr("xlink:href", "http://lorempixel.com/g/400/400/")
.attr("x", 0)
.attr("y", 0)
.attr("width", 400)
.attr("height", 400)
.attr("id", "fillImage");
..before the update function, and then I've tried replacing the fill code with stuff like:
.style("fill", function(d, i) { return "url('#fillImage')" })
or
.style("fill", function(d, i) { return "url(#fillImage)" })
or
.style("fill", "url('#fillImage')")
or
.style("fill", "url(#fillImage)")
or all the same except using "attr" instead of "style", as well as not using an id but the image varible, so: image, image[0], image[0][0], image[0][0].outerHTML. But nothing seems to work, so any ideas?