How to add an array of annotations to a d3 barchart? - javascript

I've managed to draw a barchart (it's inverted I know :)) for each year of data by reading in the CSV data and then using d3.nest() to group the data for each date per year, see block here or blockbuilder here.
However I'm am now trying to append notes from my annotations array to each chart and I'm stuck.
One approach I'm trying is to selectAll(".charts") and to append my nested annotations array i.e. annotationsPerYear. But I'm finding it difficult to join my annotationsPerYear key with my charts and then to iterate and append the notes for each year onto the correct chart. Any ideas?

You can rely on nested selections for this. You're already using nested selection with this bit:
svg.selectAll(".bar")
.data(function(d) {return d.values;})
The above binds different data to each of the 3 SVGs created earlier. It does this by calling the function(d) 3 times, and returns a different d.values each time.
You can do a similar thing to bind and create a different set of annotations in each SVG. You need a function (passed to data()) that returns the applicable annotations per chart, but this time you don't have something pre-computed like d.values. Instead, you have to extract the applicable annotations out of annotations array, using filter():
svg.selectAll(".annotation")
.data(function(d) {
// return annotations whose key matches d's key
return annotations.filter(function(a) { return a.key == d.key; });
})
Then you can use enter() and append() as you've done for the bars to create the text and position it. I'm not sure how you intend to lay it out, but altogether you want something like this:
svg.selectAll(".annotation")
.data(function(d) {
return annotations.filter(function(a) { return a.key == d.key; });
})
.enter()
.append("text")
.attr("class", "annotation")
.attr("x", function(d) { return x(d.xPos); })
.attr("y", function(d) { return d.yPos; })
.text(function(d) { return d.note; })
See:
Updated block
Updated blockbuilder

Related

d3.js insert line path by "datum" or line (data)

I was trying to plot a time-pressure line chart.
The data is an array of objects, named "res"
[
{Time: ,
Psi:
},
...
]
I defined the x, y axis, and line function like these
var x = d3.scaleTime().domain(d3.extent(res, d => d.Time)).range([0, width]),
y = d3.scaleLinear().domain([0,d3.max(res, d=>d.psi)]).range([height, 0]),
var line = d3.line()
.x(function(d) { return x(d.Time) })
.y(function(d) { return y(d.psi) });
Every thing was very standard set-up.
When I insert line element to the chart, I found two ways to insert them.
Method 1, with "datum"
svg.append("path")
.datum(res) //"datum"
.attr("class", "line")
.attr("d", line);
Method 2, with by line(res)
svg.insert("path")
.attr("class", "line")
.attr("d", line(res)); //line(res), like a function
Both methods work, just wondering are there any difference between these two methods?
Thanks,
The difference between the two methods is that by method one, you have assigned res as the "datum" object of the node. That means that if you were to store it in a variable - or I think even if you would re-select them (not sure though) - you should be able to reliably access the current value using .attr('...', function(d) { });. That can be useful if you want to do stuff to it, like animations or styling, and the value might update often - so it's a hassle to carry it around.
Other than that, there is no real difference. One of the things I like to use .datum() for is when I have a container for every shape and I want to add a node to every container, then it might be useful to use container.select('text').datum((d) => d) to feed the datum object from the container to its text child.

d3 data manipulation before plotting

Good evening everybody,
I am working for the first time on a visualization using D3. Basically I want to plot a bar chart that plots means of groups.
For example:
d3.csv("sample.csv", function(d) {
return {
"currency" : +d["year"],
"month" : +d.month,
"spendings" : +d.spendings
}
})
.then(function(data) {
chart_group.selectAll(".bar")
.data(sample.filter(function(d) { return d["currency"] === temp_curr; }))
.enter()
.append("rect")
.attr("class", "bar")
It is easily possible to create a new object containing the dynamic data i want. Group by curreny and mean the spending.
Is it possible to replace the .data(sample) by another object I created. Somehow the plot is always empty changing it. If it is not. How can I get the mean as y value?
.attr("y", function(d) { return y(d.spendings); })
I tried many things including nesting and d3.mean but there are always errors messages complaining about lists instead of single values. I totally get that but don't know how to do the trick.
Can someone help me?
Try .data(data.filter since data is the var name provided to the function containing your d3 code

Getting color as a JSON input to a d3 bullet chart

I am following the following D3 bullet chart example, trying to modify it a bit so that the different colors of the ranges are also included in the JSON: http://www.d3noob.org/2013/07/introduction-to-bullet-charts-in-d3js.html. The reason for the change is that I need the colors to be dynamic and depend on various things.
This exists one other place in the forum, but old and unsolved. I should add that I am a total newbie to d3, and don't have a lot of JavaScript experience in general.
Here is the JSON I use. "rangecolor" will in the future be an array of different colors, as there are several ranges, but for simplicity I attempt only with one color to begin with.
{
"title":"Memory Used",
"subtitle":"MBytes",
"ranges":[256,512,1024],
"rangecolor": "red",
"measures":[768],
"markers":[900]
}
Now, getting an idea of how to use it, I looked at the working example for title:
var title = svg.append("g")
.style("text-anchor", "end")
.attr("transform", "translate(-6," + height / 2 + ")");
title.append("text")
.attr("class", "title")
.text(function(d) { return d.title; });
The problem is that I cannot get the following to work:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return d.rangecolor; });
The following does work:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return "red"; });
And I can also extract the rangecolor value to the title:
title.append("text")
.attr("class", "title")
.text(function(d) { return d.rangecolor; }); //works - title is now "red"
My approach might be misguided, so any help on how to best include color ranges to the JSON and using it would be much appreciated.
The problem is that when you select all bullets, there is no data bound to them so d is undefined here:
d3.selectAll(".bullet .range.s0")
.style("fill", function(d) { return d.rangecolor; });
Why? You did not perform a data join like this:
d3.selectAll('.something').data(somethingData)
.style('fill', function (d) { // d is defined });
You should wonder why it works for the title on the contrary. This is because when you do this:
var title = svg.append("g");
title inherits data from the svg selection. See Mike Bostock explanation. In fact I was myself not aware of this behaviour, I prefer performing data joins explicitly.
I don't know the overall structure of your code, but you might apply you rangecolor properties with data inheritance (as for title) or refactor to use explicit data joins.
I have talked with some experienced developers that state that the tutorial I am following is not a good one. It is a bit messy, and I am trying to find something cleaner. Troubleshooting has been difficult in this regard, and
The exact issue presented here was solved by using d3.selectAll(".bullet .range.s0").data(data).

Using translate function on more than data

At the moment I'm using .filter to specific the type of data I would like in my data set. However, when it comes to positioning that specific, it comes through overlapping of course. For example:
.filter(function(d) {
return d.Country === 'Russia';
})
.attr("transform", function(d,i) {
return "translate(-180,50)";
});
This of course just makes all that specific data go in one place over one another. I was therefore wondering if I could make multiple transform specified by the filter that I have. I attempted to do something like this:
.attr("transform", function(d,i) {
return if (d.name === 'Amy') { "translate(-180,50)"; }
})
Heres an example of my json etc.. http://jsfiddle.net/xwZjN/38/
If anyone could point me into the right direction that would be great

d3.js: How to add a data key?

I'm learning D3.js and trying to get my head around data keys used with streamgraphs. I would like to adapt the official streamgraph example:
...so that each path has an explicit data key, and so that the mouseover logs the data key.
The official example adds paths as follows:
var area = d3.svg.area()
.x(function(d) { console.log('x', d.data); return d.x * w / mx; })
.y0(function(d) { return h - d.y0 * h / my; })
.y1(function(d) { return h - (d.y + d.y0) * h / my; });
vis.selectAll("path")
.data(data0)
.enter().append("path")
.style("fill", function() { return color(Math.random()); })
.attr("d", area);
I tried adapting the code as follows, but I'm not sure how to change the structure of data0 (currently an array of arrays) to achieve what I want:
vis.selectAll("path")
.data(data0, function(d) { return d.name }) // Add key function
.enter().append("path")
.style("fill", function() { return color(Math.random()); })
.attr("d", area)
.on("mouseover", function (d,i) {
console.log("mouseover", d.name); // Log name property on mouseover
});
As it stands, without my having made any changes to the structure of data0, it unsurprisingly does not work. How can I add a name property to data0 without also messing up the area and .data() functions?
UPDATE: To be a bit clearer: the D3 docs say that the area function is expecting a two-dimensional array. So if I change data0 from a two-dimensional array, to an array of objects, each with a name key and a data key, how can I also change what I pass to area?
The data in the example doesn't have a "name" property, so you would need to add that to the data to use it. The data keys you refer to are used when merging/updating data, i.e. you have drawn some paths already and then update (some of them). The .data() function will try to figure out what data is updated and what data is new. If that doesn't work for you, you can use the data key to help it, i.e. in your case tell it that things with the same name are the same data.
If what you mean by data keys are "data legends", then you might want to take a look at the following examples where I've completely separated the placement of magnitudes, legend bullets and legend text in different areas of the charts.
Multiple D3 Pie Charts Mixed In With HTML Layout Constructs
Multiple D3 Horizontal Bar Charts Mixed In With HTML Layout Constructs
In each of the examples, you'll clearly see how the data is labeled, structured, passed in, and used.
I also tied them together through mouseover and mouseout events so that mousing over or out of any element causes all elements in a set to change color simultaneously.
I hope this helps.
Frank

Categories