I want to create a chart like this example:
Population pyramid
But without the sliding; no need to adjust year or anything. This is roughly the dataset I have:
category,subcategory,benchmark,completes,difference
household income,"Less than $30,000",33.7,27.4,6.3
household income,"$30,000 to $74,000",31.6,36.3,4.7
household income,"$75,000 to $124,999",20.3,22.4,2.1
household income,"$125,000 Plus",14.2,13.9,0.3
I want to have a transparent bar for the benchmark, and a transparent bar for the completes (so that the disparity between them can be seen)
Where I have problems is putting two rects in each 'g' container
// G containers for subcategory bars
subcat = svg.selectAll('.subcat')
.data(data)
.enter().append('g')
.attr('class','subcat')
.attr('transform', function(d) {
return 'translate(' + x(d.subcategory) + ', 0)';
});
subcat.selectAll('.bar')
.data(data)
.enter().append('rect')
.attr('class','bar')
.attr('width', x.rangeBand())
.attr('y', function(d) { return y(d.benchmark); })
.attr('height', function(d) { return height - y(d.benchmark); });
I don't have a very good understanding of the data() method of a selection, hence my problem. I have read about keys, entries and rollups and I think that might be a possible place to look for a solution.
Most of the D3 examples I have worked with use one piece of data for each column, so the data binding makes sense. In this case, I want to put two bars in each column so the data binding process is unclear to me.
Any help would be appreciated.
Related
I'm trying to make a chart of sleeping sessions that looks something like this:
https://i.stack.imgur.com/Wsnha.png
I currently have only been able to get it to draw one rectangle per data point. I'm wondering what the syntax would be to have it draw multiple rectangles associated with a single row/data point since each data point contains an array of sleeping sessions from that day.
Here is what my code currently looks like:
var start;
var end;
var rectGrp = svg
.append("g")
.attr('transform', 'translate(' +padding.left+','+padding.top+')')
.selectAll("rect")
.data(sleepArr)
.enter()
//I want to create one of these rectangles for all the sleeping sessions in that day
.append("rect")
.attr("x", (d) => {
start = d.sessions[0].startTime;
end = d.sessions[0].endTime;
return xScale(start);
})
.attr("width", (d) => {
start = d.sessions[0].startTime;
end = d.sessions[0].endTime;
var width = end-start;
return xScale(width);
})
.attr("y", (d) => yScale(d.date))
.attr("height", yScale.bandwidth())
.attr("fill", (d) => colors[getDayOfWeek(d.date)])
.append("title")
.text((d) => d.date);
I realize that I probably don't have a good understanding of how to use D3 yet and any help would be greatly appreciated.
With the enter/update/exit cycle one item in the data array should be bound to one element in the DOM. The enter selection is used to ensure we create one element for every item for which there is already not one corresponding element in the DOM. In your case the number of rectangles you want to draw is not the same as the number of people in your data array. The approach you have is great for a flat data structure, but we need to make nested selectAll statements in order to properly represent your data.
First, each item in sleepArr represents one person, so lets create one parent g for every person:
var sleepers = svg
.append("g")
.attr('transform', 'translate(' +padding.left+','+padding.top+')')
.selectAll("rect")
.data(sleepArr)
.enter()
.append("g")
.attr("transform", (d,i)=>...
We probably want to do the vertical positioning here as all children of each parent g elements are the same.
Second, we want to create child rectangles representing sleep sessions. We want one rectangle per session. The data array for these rectangles is the sessions property of the datum bound to each parent g, so we pass that to the data method:
sleepers.selectAll("rect")
.data(function(d) return d.sessions; }))
.enter()
.append("rect")
.attr("x", ...
...
For each parent g, representing a single person, we select all child rectangles of each g, bind data, and enter the required rectangles in the appropriate g.
This way we keep a one to one relationship between data and elements representing them: one g per person, one rect per session. The items in sleepArr are bound to the parent g elements, and the items in the child data array are bound to the rects.
As a newbie to D3.js, this is a tricky problem that bothers me for a while. I am plotting a series of bars using the SVG rect elements, the associated object is (defined outside d3.csv() function)
var bars = g.selectAll(".bar rect")
.data(data)
.enter()
.append("rect")
.attr("class", "bar rect");
.attr("x", function(d) { return x(d.date)})
.attr("y", function(d) { return y(max(d.open, d.close));})
.attr("width", 2*tickWidth/3)
.attr("height", function(d) { return y(min(d.open, d.close)) - y(max(d.open, d.close));})
.attr("fill", function(d) { return d.open > d.close ? "red" : "green" ;});
However, the data is loaded using d3.csv() function from a file. The other settings, such as x, y, etc are rather standard as in this example. Now, because I wanted to add one more line on this chart and the range of y axis is changed accordingly. For this, I need to re-map the range for the y axis.
Here comes the problem. First of all, it appears a bit difficult for me to get the data from the above bar chart. I used the data() method to acquire the data of the above bars object (inside d3.csv() function while plotting the other line) but it gave me an empty array. Second, I am not sure how I can associated the newly set y range to the bars object without calling again the data() method, since each time I fetch the newly mapped y range to the bar object, it is plotted again.
I definitely lack a good understanding of the d3's philosophy of separating style and data. Your help will be highly appreciated.
So I'm developing a Sankey diagram, using D3's Sankey API, and I'm trying to figure out how to change the color of the bands, or cords, going to and from the nodes. An example of what I'm trying to do can be found here:
http://tamc.github.io/Sankey/
I want to be able to individually choose each band and choose that individual band's color. I can't find any documentation for D3's Sankey API so I have no idea how to actually pull this off. I tried the setColors function that I found by searching through the code of the Sankey in the link that I provided. However, that doesn't seem to work with my code. I started my Sankey off using this code as a base:
http://tamc.github.io/Sankey/examples/simple.html
Can someone please give me an idea of how to change the color of a band using this as a reference?
P.S. If someone could also fill me in on how to change the color of a node, as well, that would be great!
The example you've linked to uses a different API on top of the Sankey plugin. I'll explain for this example. The Sankey plugin doesn't draw the visual elements, it only computes their positions, so you're free to set the colors as you like.
The relevant code for the links in the example is this:
var link = svg.append("g").selectAll(".link")
.data(energy.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
To change the color, simply set either a different class or set stroke explicitly:
.style("stroke", "red")
This can of course be a function as well so that you can set different colors for different paths. The nodes are similar:
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); })
In the example, the fill color is set based on the name -- you can adjust this as you like.
I have several bars I'd like to draw and allow the user to use a brush to select a portion of a bar. The code is simple.
I have a Fiddle setup at:
http://jsfiddle.net/N32CS/
I'm not sure if it is my scale's that are wrong or the brushes themselves...
currentG.append("g")
.attr("id", "g_" + val.curNum)
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", yScale(arrayData[i].curNum))
.attr("height", 10);
It explains the problem I'm having of the user being able to at times drag outside the area of one bar or being limited to the area of another bar.
Thanks!
I updated your code to work as intended:
http://jsfiddle.net/N32CS/2/
var brushG = currentG.append("g")
.attr("id", "g_" + val.curNum)
.attr("class", "x brush");
var brush = d3.svg.brush();
brushG.datum({brush: brush});
...
brush.on("brushstart", function (p) {
d3.selectAll(".x.brush")
.filter(function(d) { console.log(d, d.brush != brush);return d.brush != brush; })
.each(function(d) { d3.select(this).call(d.brush.clear()) });
})
Basically I'm storing the brush function as data on each of the brush groups.
when you start brushing it clears the brushes for all the other bars and not it's own.
This is a pretty common thing, where it really helps to get used to binding data to the elements. If you bind stuff rather than keeping around global variables you can do everything with d3 selections and callbacks!
I am fairly new to D3 and I am trying create a modified version of the icicle chart here. I need to add labels above the chart to specify the hierarchy level name
( not the name of each partition but what column name represents the level in the hierarchy).
I have the names in an array and I have tried to add them to the chart before I bind the actual hierarchical data but I cannot seem to get the labels appear above that chart.
I am not sure whether I need to reduce the amount of vertical space the chart takes up or I need to move the labels for each hierarchy level
var levels=["LEV 1", "LEV 2", "LEV 3", "LEV 4"];
vis.selectAll("g").append('g')
.data(levels).enter()
.append("text")
.attr("dy", ".55em")
.attr('y', 5)
.attr('x', function(d,i){
return (i+1)* (w / levels.length) ;})
.attr('text-anchor', 'start')
.attr("class", "pHeader")
.text(function(d) { return d; });
any help would be greatly appreciated
It seems to me your problem is just a matter of leaving room for a margin when you set the size of your plotting area.
This tutorial should help:
D3 Margin Convention
You might also want to look at using a D3 axis with an ordinal scale for spacing out your level labels.