I have multiple CSV files that looks like this:
name,state,x,y
Anderson,VIC,34,765
Martin,VIC,55,345
James,NSW,46,129
Zoe,QLD,63,76
I'm using this data with a barchart, and have it loaded in so that all data is shown. I'm currently toying with the idea of when a user clicks a dropdown menu, only certain values will show. For example, if they click VIC, only data with a state of VIC will be shown. But I'm confused about how I can segregate the data like that? I thought my code below would work, but it doesn't:
svg.selectAll("mybar")
.data(data, function(d) { return d.state["vic"];})
.enter()
.append("rect")
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.y); })
.attr("fill", "blue")
The ideal output is that return d.state["..."]; is updated depending on the state that has been selected in the dropdown menu.
You must have to add filter with data like.
data.filter(function(d){
return d.category == category;
})
AS
d3.select("body")
.selectAll("div.h-bar")
.data(data.filter(function(d){
return d.category == category;
}))
.select("span")
.text(function (d) {
return d.category;
});
Here is the working example https://bl.ocks.org/fabiomainardi/00fd581dc5ba92d99eec
Related
I am new to D3. I am creating an interactive visualization. I want to update the data based on user's input and re-render (not-re-draww everything) the visualization.
Code for creating visualization:
cals = d3.select("body").select("svg").selectAll("g")
.data(yearlyData) //I want to update this "yearlyData"
.enter()
.append("g")
.attr("id","svgGroup")
.attr("transform",function(d,i){
return "translate(0,"+(yOffset+(i*(height+calY)))+")";
})
dataRects = cals.append("g")
.attr("id","dataDays")
.selectAll("dataDays")
.data(function(d){
return d.values;
})
.enter()
.append("rect")
.attr("id",function(d) {
return format(d.date)+":"+d.DEPARTURE_DELAY;
})
.attr("stroke","#ccc")
.attr("width",cellSize)
.attr("height",cellSize)
.attr("x", function(d){return xOffset+calX+(d3.time.weekOfYear(d.date) * cellSize);})
.attr("y", function(d) { return calY+(d.date.getDay() * cellSize); })
.attr("fill", function(d) {
if (d.DEPARTURE_DELAY<breaks[0]) {
return colours[0];
}
for (i=0;i<breaks.length+1;i++){
if (d.DEPARTURE_DELAY>=breaks[i]&&d.DEPARTURE_DELAY<breaks[i+1]){
return colours[i];
}
}
if (d.DEPARTURE_DELAY>breaks.length-1){
return colours[breaks.length]
}
})
I tried to use
dataRects.data(newData) And
d3.select("body").select("svg").selectAll("g").data(newData);
in the Onclick() function, but these don't work. The source data is not changed. Can I get some help?
Thanks!
Plunker: https://next.plnkr.co/edit/17t5ujwC71IK3PCi
Why is following not adding a "test" label to all my polygons?
/* NOT Working code */
groups.selectAll('.path_placeholder')
.enter()
.append('text')
.text("test")
Update
.enter() wasn't required as mentioned by Xavier. Removing it showed "test" for all nodes. But why then its not working when I do provide data and use enter() as following:
groups.selectAll('.path_placeholder')
.data(groupIds, function(d) {
return d;
})
.enter()
.append('text')
.text(function(d){
console.log(d);
return d;
})
I am trying to be able to show label for each of my polygon and for now just trying to add a dummy label to each of them.
Your problem here is the paths is a <path> selection, not a <g> one:
paths = groups.selectAll('.path_placeholder')
.data(groupIds, function(d) { return +d; })
.enter()
.append('g')
.attr('class', 'path_placeholder')
.append('path')//this makes the selection pointing to <path> elements
.attr('stroke', function(d) { return color(d); })
.attr('fill', function(d) { return color(d); })
.attr('opacity', 0);
Because of that, when you do...
groups.selectAll('.path_placeholder')
.data(groupIds, function(d) {
return d;
})
.enter()
//etc...
... your "enter" selection is empty, because you already have data associated to that paths selection.
Besides that, it makes little sense using a proper "enter" selection for the texts, since the data is the same data bound to the groups.
Solution: the solution here, which is the idiomatic D3 for this situation, is creating an actual <g> selection.
We can do that by breaking the paths selection, and giving it another name:
pathGroups = groups.selectAll('.path_placeholder')
.data(groupIds, function(d) {
return +d;
})
.enter()
.append('g')
.attr('class', 'path_placeholder');
Then you can just do:
paths = pathGroups.append('path')
.attr('stroke', function(d) {
return color(d);
})
.attr('fill', function(d) {
return color(d);
})
.attr('opacity', 0)
texts = pathGroups.append('text')
.text(function(d) {
return d;
});
Here is the forked Plunker: https://next.plnkr.co/edit/31ZPXIvSI287RLgO
I am using d3.js to render a grouped bar chart and I am looking to animate transition the bars - (show/hide different series) when clicking on the legend.
from this.
to this
perhaps also changing the scale
http://jsfiddle.net/0ht35rpb/202/
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color)
.on("click", function(d) {
console.log("d", d);
});
Some bar transition code
bars.transition()
.attr("id", function(d){ return 'tag'+d.state.replace(/\s|\(|\)|\'|\,+/g, '');})
.attr("x", function(d) { return x(d.state); })
.attr("width", x.rangeBand())
.attr("y", function(d) {return y(d.value); })
.attr("height", function(d) { return height - y(d.value); });
bars.exit().remove();
Other grouped bar chart references.
https://bl.ocks.org/mbostock/3887051
https://plnkr.co/edit/JUaLXmeCvHh0zUmrKClQ?p=preview
http://jsfiddle.net/ramseyfeng/8790t2vk/
There are a few ways to go through this. You could easily use an enter/update/exit cycle, though this is a little complex when compared to typical use of the cycle because of the nested elements and the need to set keys to ensure smooth transitions between chart states.
In this situation, it may be easier to simply use an array to hold bars that are to be filtered out, hide those bars, update the scales to not use those keys' values, and update the remaining bars.
This requires an onclick event for each legend item. When clicked, in our clicked function we manage the array of filtered out (filtered) items like so, where d is the datum associated with the legend rectangle:
// add the clicked key if not included:
if (filtered.indexOf(d) == -1) {
filtered.push(d);
// if all bars are un-checked, reset:
if(filtered.length == keys.length) filtered = [];
}
// otherwise remove it:
else {
filtered.splice(filtered.indexOf(d), 1);
}
Then we can update the scales (we need the all the keys that are not in the filtered array for the domain of the x1 scale, hence the newKeys variable):
var newKeys = [];
keys.forEach(function(d) {
if (filtered.indexOf(d) == -1 ) {
newKeys.push(d);
}
})
x1.domain(newKeys).rangeRound([0, x0.bandwidth()]);
y.domain([0, d3.max(data, function(d) { return d3.max(keys, function(key) { if (filtered.indexOf(key) == -1) return d[key]; }); })]).nice();
Then we can select our rectangles, filter by whether they should be hidden or shown, and update accordingly:
var bars = svg.selectAll(".bar").selectAll("rect")
.data(function(d) { return keys.map(function(key) { return {key: key, value: d[key]}; }); })
// filter out bars:
bars.filter(function(d) {
return filtered.indexOf(d.key) > -1;
})
.transition()
.attr("x", function(d) {
return (+d3.select(this).attr("x")) + (+d3.select(this).attr("width"))/2;
})
.attr("height",0)
.attr("width",0)
.attr("y", function(d) { return height; })
.duration(500);
// update persistent bars:
bars.filter(function(d) {
return filtered.indexOf(d.key) == -1;
})
.transition()
.attr("x", function(d) { return x1(d.key); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("width", x1.bandwidth())
.attr("fill", function(d) { return z(d.key); })
.duration(500);
This solution could be made a little bit more "d3-ish" with the enter/update/exit cycle, but as our elements are relatively fixed in number, this is not as useful as in many other situations.
Here is the above code in action:
https://bl.ocks.org/andrew-reid/64a6c1892d1893009d2b99b8abee75a7
And as noted in the comments, you also need to update the axis, not just the scale. To do so, I added a class to the y scale to allow easy selection when updating the chart:
svg.select(".y")
.transition()
.call(d3.axisLeft(y).ticks(null, "s"))
.duration(500);
In reference to D3 Sankey (https://bost.ocks.org/mike/sankey/), I wished to inquire about the way to color the nodes using some categorical variable in JSON file.
Link to the JSON file being used in the actual code:
https://bost.ocks.org/mike/sankey/energy.json
My question therefore is that if I provide some info pertaining to the category of "name":
{"name":"Solar PV", "category":"A"},
{"name":"Solar Thermal","category":"A"},
{"name":"Solar","category":"B"},
{"name":"Tidal","category":"C"},
{"name":"UK land based bioenergy","category":"A"},
{"name":"Wave","category":"A"},
{"name":"Wind","category":"B"}
Then what changes should be made to the source code to color the nodes according to the category.
P.S: Not an expert in D3/JS, please pardon my limited skills if this question appears too naive.
Thanks
In the linked example you'll see this block of code:
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
Replace the .style("fill" line with:
.style("fill", function(d) { return d.color = color(d.category); })
In Bostock's code, this is the line that colours the rectangles:
.style("fill", function(d) {
return d.color = color(d.name.replace(/ .*/, ""));
})
So, just change that to:
.style("fill", function(d) {
return d.color = color(d.category);
})
I have a multiline graph that displays 10 series of data, I am trying to get the lines to update with new data but for some reason I can't get that happening.
The transition with the new data is working for the points on the lines so I assume I am not selecting the right elements but for the life of me I can't figure out where my mistake is.
At one point I had one line changing which indicated it was only updating from the first index of the data array.
Any insight would be appreciated:
Initial Series creation-
var series = svg.selectAll(".series")
.data(seriesData)
.enter().append("g")
.attr("class", "series");
series.append("path")
.attr("id", function (d) {
return d.name;
})
.attr("stay", "false")
.attr("class", "line")
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
Update function:
This is where I am stuck, the points respond to the new data but the paths do not.
series.data(newseriesData);
series.selectAll("path")
.attr("id", function (d) {
return d.name;
})
.attr("d", function (d) {
d.line = this;
return line(d.values);
})
.attr("opacity", ".2")
.on("click", function () {
fadeOuts(this);
})
.style("stroke", function (d) {
return strokeCol;
})
.style("stroke-width", "4px")
.style("fill", "none");
series.selectAll(".point")
.data(function (d) {
return d.values;
})
.transition()
.attr("cx", function (d) {
return x(d.label) + x.rangeBand() / 2;
})
.attr("cy", function (d) {
return y(d.value);
})
.style("fill", function (d) {
return color(d.name);
})
.style("stroke", "grey")
.style("stroke-width", "2px")
.on("mouseover", function (d) {
showPopover.call(this, d);
})
.on("mouseout", function (d) {
removePopovers();
})
Yes this is a university project, this is the last piece of work in a solid 50+ hour effort on this and I'd just like to get it knocked out.
The short answer is that instead of series.selectAll("path") you should use series.select("path"). Remember that series is already a selection, and the subselection is done for each element in it. You've appended exactly one element to each of the selection, so .select() is fine and no .selectAll() is required.
The main difference this makes is that .select() inherits the data from the parent selection, while .selectAll() doesn't -- when doing .selectAll() the data is simply not updated and therefore no change occurs.