Multiple bar chart segments in d3.js - javascript

I'm currently looking at this bar chart example.
http://jsfiddle.net/enigmarm/3HL4a/13/
In my own version I've been using a json array as the data and is structured like so.
[{"irish":"154187","non_irish":"309638","area":"Connacht"},
{"irish":"588725","non_irish":"2182932","area":"Ireland"},
{"irish":"180755","non_irish":"1017491","area":"Leinster"},
{"irish":"189395","non_irish":"672660","area":"Munster"},
{"irish":"64388","non_irish":"183143","area":"Ulster"}]
My question is how would I apply both data.irish and data.non_irish to the "y" attribute and generate bars for both as currently I can't work out how to apply both because the "y" attribute will only take one in its parameter.
var bar = svg.selectAll("rect")
.data(data)
.enter().append("g")
.attr("class", "bar");
var bartwo =bar.append("rect")
.attr("class", "bar")
.style("fill", function(d,i) {
return colours[i];
})
.attr("x", function(d,i) { return x(data[i].area); })
.attr("y", function(d,i) { return y(data[i].irish); })
.attr("width", barWidth)
.attr("height", function(d,i) { return height - y(data[i].irish); })
Any help would be truely appreciated.

This dual bar-chart might help you: http://nvd3.org/examples/multiBarHorizontal.html. It emphasizes on the contrast of the two dimensions as well as comparison of multiple groups in one dimension (E.g the number of Irish speakers in different areas).

Related

D3 Button Input To Change Data (Stacked Bar Chart)

I'm working on a project where I'm making multiple D3 stacked bar charts. The idea is that when a button is pressed, the plot will reload with a different dataset, similar to the code that is shown here.
My issue is with modifying this code to make the bar charts stacked. I'm not too familiar with the update functionality in D3 (I've never learned about it), so I've been trying to just append more "rect" objects to the "u" variable. It will load in correctly the first time (with all the "rect" objects where I'd expect), but whenever the update method is recalled on a button click all that gets drawn is the second iteration of the append "rect" calls. If anyone knows how to work this code into stacked bar chart functionality, I'd greatly appreciate it.
For reference, this is what I've been trying
u
.enter()
.append("rect") // Add a new rect for each new elements
.merge(u) // get the already existing elements as well
.transition() // and apply changes to all of them
.duration(1000)
.attr("x", function(d) { return x(d.group); })
.attr("y", function(d) { return y(d.value1); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.value1); })
.attr("fill", "#69b3a2")
u
.enter()
.append("rect") // Add a new rect for each new elements
.merge(u) // get the already existing elements as well
.transition() // and apply changes to all of them
.duration(1000)
.attr("x", function(d) { return x(d.group); })
.attr("y", function(d) { return y(d.value2 + d.value1); })
.attr("width", x.bandwidth())
.attr("height", function(d) { return height - y(d.value1); })
.attr("fill", "#69b3a2")

D3 not updating label

I have a map and a matching legend on my website. As the user selects different values from a select list, the map is updated and in the same function, the legend should be updated with new values. As the map actualization works properly, the values of the legend stay the same even in the console are logged the right values if I log the variables.
This is the function that draws the legend:
color_domain = [wert1, wert2, wert3, wert4, wert5];
ext_color_domain = [0, wert1, wert2, wert3, wert4, wert5];
console.log(ext_color_domain);
legend_labels = ["< "+wert1, ""+wert1, ""+wert2, ""+wert3, ""+wert4, "> "+wert5];
color = d3.scale.threshold()
.domain(color_domain)
.range(["#85db46", "#ffe800", "#ffba00", "#ff7d73", "#ff4e40", "#ff1300"]);
var legend = svg.selectAll("g.legend")
.data(ext_color_domain)
.enter().append("g")
.attr("class", "legend");
var ls_w = 20, ls_h = 20;
legend.append("rect")
.attr("x", 20)
.attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 0.7);
legend.append("text")
.attr("x", 50)
.attr("y", function(d, i){ return height - (i*ls_h) - ls_h - 4;})
.text(function(d, i){ return legend_labels[i]; });
console.log(legend_labels); //gives the right legend_labels but doesn't display them correctly
};
Sadly even the map is updated with new colors they're colored with the old thresholds. This is the way the map is colored:
svg.append("g")
.attr("class", "id")
.selectAll("path")
.data(topojson.feature(map, map.objects.immoscout).features)
.enter().append("path")
.attr("d", path)
.style("fill", function(d) {
return color(rateById[d.id]);
})
This is tough to answer without a complete, working code sample but...
You are not handling the enter, update, exit pattern correctly. You never really update existing elements, you are only re-binding data and entering new ones.
Say you've called your legend function once already, now you have new data and you do:
var legend = svg.selectAll("g.legend")
.data(ext_color_domain)
.enter().append("g")
.attr("class", "legend");
This re-binds the data and computes an enter selection. It says, hey d3, what data elements are new? For those new ones, you then append a g. Further:
legend.append("rect")
.attr("x", 20)
.attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 0.7);
Again, this is operating on those newly entered elements only. The ones that already existed on the page aren't touched at all.
Untested code, but hopefully it points you in the right direction:
// selection of all enter, update, exit
var legend = svg.selectAll("g.legend")
.data(ext_color_domain); //<-- a key function would be awesome here
legend.exit().remove(); //<-- did the data go away? remove the g bound to it
// ok, what data is coming in? create new elements;
var legendEnter = legend.enter().append("g")
.attr("class", "legend");
legendEnter.append("rect");
legendEnter.append("text");
// ok, now handle our updates...
legend.selectAll("rect")
.attr("x", 20)
.attr("y", function(d, i){ return height - (i*ls_h) - 2*ls_h;})
.attr("width", ls_w)
.attr("height", ls_h)
.style("fill", function(d, i) { return color(d); })
.style("opacity", 0.7);
legend.selectall("text")
...
There's some really great tutorials on this; and it's confusing as hell, but it's the foundation of d3.
An example that helps you get started with updating d3 (d3, v4):
const line = svg.selectAll('line').data(d3Data); // binds things
line.exit().remove(); // removes old data
line.enter()
.append('line') // add new lines for new items on enter
.merge(line) // <--- this will make the updates to the lines
.attr('fill', 'none')
.attr('stroke', 'red');

D3 white tick marks (grid lines) are drawing over text, how to correct?

I am drawing a simple horizontal bar graph in D3.
I'm breaking the bars into smaller rectangles with long, white tick marks.
But my tick marks are slicing through some text.
I suspect I am drawing elements in the wrong order, somehow.
What do you think?
How to make gridlines slice only the bars, not the text?
You can see how it looks tacky, whole thing is here:
http://bl.ocks.org/greencracker/4378b817d4d5393c4e37
Here's the key part, I think:
...
var bar = svg.selectAll("g.bar") // create the bars
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(0," + y(d.name) + ")"; });
bar.append("rect") // draw the bars
.attr("width", function(d) { return x(d.value); })
.attr("height", y.rangeBand())
.style("opacity", 0.5);
svg.append("g") // draw my x axis, which includes the white tickmarks
.attr("class", "x axis")
.call(xAxis);
bar.append("text") // then attach text to each bar
.attr("class", "value")
.attr("x", function(d) { return x(d.value); })
.attr("y", y.rangeBand() / 2)
.attr("dy", ".35em")
.attr("text-anchor", function (d) {
if (d.name === "Business, Management, Marketing") {return "end"}
else {return "start"} ;}) // correct for "business" being very long
.attr("dx", function (d) {
if (d.name === "Business, Management, Marketing") {return -3}
else {return 3} ;}) // correct for "business" being very long
.text(function(d) { return (d.name); });
(Yes, I asked a similar question before, but this one's a little different and I can't figure it out. Transition on axis -- losing grid lines (ticksize), how to transition in correct order?)
your suspicions about drawing elements in the proper order are correct.
What's currently happening is:
1) Append <g class="bar"> for new data.
var bar = svg.selectAll("g.bar")
.data(data)
.enter().append("g")
2) Append a rectangle to the above group.
bar.append("rect")
3) Draw the x-axis, which is placed after (i.e. over) of (1).
svg.append("g")
.attr("class", "x axis")
.call(xAxis);
4) Append/nest text to the group defined in (1).
bar.append("text")
Since the text is nested with the group and the group is inserted into the DOM before the x-axis, the tickmarks are showing up over the text.
However if we do something as simple as say moving the x-axis call before the bar call, the ticks won't show up because they'll be drawn underneath the bars. So unfortunately you'll need to split up the grouping of rectangle and text.
This also means you'll need define a transform/translate for the text labels:
svg.selectAll("text.value")
.data(data)
.enter().append("text")
.attr("class", "value")
.attr("transform", function(d) {
return "translate("+ x(d.value)+5 + "," + (12+y(d.name)) + ")";
})
.attr("text-anchor", function (d) {
if (d.name === "Business, Management, Marketing") {return "end"}
else {return "start"} ;}) // correct for "business" being very long
.attr("dx", function (d) {
if (d.name === "Business, Management, Marketing") {return -3}
else {return 3} ;}) // correct for "business" being very long
.text(function(d) { return (d.name); });
The above should work for all labels except for Business, Management, Marketing, because its translate is at 5800.
Hope this helps!

Normal range for d3.js diagram usind domain method

Here a link with fantastic description of sparklines implementation using d3.js
I'm doing something similar but I need to define a normal range for each sparkline, like here on mockup :
This grey bar is hard coded now. The meaning of this bar is to represent "green" range of data. For example from 0 to 1. Everything that is less or more then this range should be not in that bar.
I have such domain in my js file:
y.domain(
d3.extent(data, function (d) {
return d.y;
})
and this is how i`m drawing my greybar:
var baseline = graph.append("rect")
.attr("x", 0)
.attr("y", y(2))
.attr("width", 100)
.attr("height", y(3))
.attr("class", "rect_");
But when I'll give for this block of code my normal range [0,1]
var baseline = graph.append("rect")
.attr("x", 0)
.attr("y", y(0))
.attr("width", 100)
.attr("height", y(1))
.attr("class", "rect_");
I'm getting this :
I'm afraid it`s complete incorrect way , need someone help, thanx!

D3 - how to deal with JSON data structures?

I'm new to D3, and spent already a few hours to find out anything about dealing with structured data, but without positive result.
I want to create a bar chart using data structure below.
Bars are drawn (horizontally), but only for user "jim".
var data = [{"user":"jim","scores":[40,20,30,24,18,40]},
{"user":"ray","scores":[24,20,30,41,12,34]}];
var chart = d3.select("div#charts").append("svg")
.data(data)
.attr("class","chart")
.attr("width",800)
.attr("height",350);
chart.selectAll("rect")
.data(function(d){return d3.values(d.scores);})
.enter().append("rect")
.attr("y", function(d,i){return i * 20;})
.attr("width",function(d){return d;})
.attr("height", 20);
Could anyone point what I did wrong?
When you join data to a selection via selection.data, the number of elements in your data array should match the number of elements in the selection. Your data array has two elements (for Jim and Ray), but the selection you are binding it to only has one SVG element. Are you trying to create multiple SVG elements, or put the score rects for both Jim and Ray in the same SVG element?
If you want to bind both data elements to the singular SVG element, you can wrap the data in another array:
var chart = d3.select("#charts").append("svg")
.data([data])
.attr("class", "chart")
…
Alternatively, use selection.datum, which binds data directly without computing a join:
var chart = d3.select("#charts").append("svg")
.datum(data)
.attr("class", "chart")
…
If you want to create multiple SVG elements for each person, then you'll need a data-join:
var chart = d3.select("#charts").selectAll("svg")
.data(data)
.enter().append("svg")
.attr("class", "chart")
…
A second problem is that you shouldn't use d3.values with an array; that function is for extracting the values of an object. Assuming you wanted one SVG element per person (so, two in this example), then the data for the rect is simply that person's associated scores:
var rect = chart.selectAll("rect")
.data(function(d) { return d.scores; })
.enter().append("rect")
…
If you haven't already, I recommend reading these tutorials:
Thinking with Joins
Nested Selections
This may clarify the nested aspect, in addition to mbostock's fine answer.
Your data has 2 degrees of nesting. You have an array of 2 objects, each has an array of ints. If you want your final image to reflect these differences, you need to do a join for each.
Here's one solution: Each user is represented by a group g element, with each score represented by a rect. You can do this a couple of ways: Either use datum on the svg, then an identity function on each g, or you can directly join the data on the g. Using data on the g is more typical, but here are both ways:
Using datum on the svg:
var chart = d3.select('body').append('svg')
.datum(data) // <---- datum
.attr('width',800)
.attr('height',350)
.selectAll('g')
.data(function(d){ return d; }) // <----- identity function
.enter().append('g')
.attr('class', function(d) { return d.user; })
.attr('transform', function(d, i) { return 'translate(0, ' + i * 140 + ')'; })
.selectAll('rect')
.data(function(d) { return d.scores; })
.enter().append('rect')
.attr('y', function(d, i) { return i * 20; })
.attr('width', function(d) { return d; })
.attr('height', 20);
Using data on the group (g) element:
var chart = d3.select('body').append('svg')
.attr('width',800)
.attr('height',350)
.selectAll('g')
.data(data) // <--- attach directly to the g
.enter().append('g')
.attr('class', function(d) { return d.user; })
.attr('transform', function(d, i) { return 'translate(0, ' + i * 140 + ')'; })
.selectAll('rect')
.data(function(d) { return d.scores; })
.enter().append('rect')
.attr('y', function(d, i) { return i * 20; })
.attr('width', function(d) { return d; })
.attr('height', 20);
Again, you don't have to create these g elements, but by doing so I can now represent the user scores differently (they have different y from the transform) and I can also give them different styles, like this:
.jim {
fill: red;
}
.ray {
fill: blue;
}

Categories

Resources