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.
Related
I would like to create a split difference bar chart using d3 like the attached image. I have 2 input arrays one for y-axis labels and one for data as below:
data = [[35,90], [60,45], [80,90], [95,90]]
months = ["Jan - 20", "Feb - 20", "Mar - 20", "Apr -20"]
I tried my hands in d3-observable. Feel free to fork this observable.I am stuck at getting the nested data rendered properly with scale. Any help would be much appreciated.
Thanks
You can indeed avoid SVG and use vanilla HTML to draw a chart like that. Of course, this doesn't mean you can't use d3 to make your life easier - it has methods to simplify the handling of scales, date formatting and the creation of elements which isn't in any way restricted to SVG. For example, you can create the first bar chart and its label like so:
d3.select("#first-bar-column")
.selectAll("div")
.data(data)
.enter()
.append("div")
.style("width", d => `${xScale(+d.A)}px`)
.classed("first-bar", true)
.append("p")
.classed("label", true)
.text(d => d.A);
Have a look at this fiddle for the full example. If you want any "special effects" to apply to the entire row on hover / click, you'd need to assign some common class based on the element index: .attr("class", (d, i) => `row${i}`)
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.
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.
I'm using Mike Bostock's example as a template and building on it. My bar chart here.
After transition to stacked version, I am unable to get the y position of the bars. Bars of higher height overshadow the smaller ones. Most likely because of the valueOffset attribute of the stack. I am stuck on this issue for few days now.
Changes from Mike's example:
removed group labels in stacked chart
new y-axis y2 on linear scale. The domain for this axis is from 0 to the maximum of all the sums of values in each year which is 141.
defined new stack stack_year for relative positions of the bars.
Relevant code:
// y2 definition
y2.domain([0, d3.max(dataByGroup_year, function(d) { return d.year_wise_sum; })]).range([height, 0]);
// calculates sum of all wins per year
dataByGroup_year.forEach(function(d) {
var order = d.values.map(function(d) { return d.value; });
d.year_wise_sum = d3.sum(order);
});
function transitionStacked() {
var t = svg.transition().duration(750),
g = t.selectAll(".group").attr("transform", "translate(0," + y0(y0.domain()[0]) + ")");
g.selectAll("rect").attr("x", function(d) { return x(d.year); })
.attr("y", function(d) { return height - y2(d.valueOffset); })
.attr("height", function(d) { return height - y2(d.value); });
g.selectAll(".group-label").text("");
}
y0 is the ordinal scale used for multiple charts. y1 is the linear scale used for each chart in multiple charts.
Full HTML code at github
Data used: input file. I disabled tips for each bar.
Update: JSFIDDLE
Any help is much appreciated! Thank you
There were a number of issues here, which I've fixed up in this fiddle: http://jsfiddle.net/henbox/wL9x6cjk/4/
Part of the problems was the data itself (as per my comment above). There were some repeated values, which was causing issues when calculating the valueOffset correctly (using the d3.layout.stack)
I've also made some changes to how the y and attribute for each rect are calculated in the transitionStacked function. I changed what you had:
.attr("y", function(d) {
return height - y2(d.valueOffset);
})
to:
.attr("y", function (d) {
return y2(d.value + d.valueOffset) - height;
})
Note that you need to sum the d.value and d.valueOffset, before applying the scaling, to calculate the top left corner position of the rect. Additionally, you don't need to recalculate the x attribute value since this doesn't change between the two chart views, so I removed it
I also removed the call to stack_year(dataByGroup_year);. You don't need to build the stack layout here, just to calculate the maximum sum per year.
Finally I also tidied up the y-axis positioning a bit so there's enough space for the x-axis labels, and simplified the positioning of group elements in the stacked view. I also moved the x-axis to be appended to svg rather than group, which simplified positioning of elements
I'm a complete noop to D3 and partly SVG, so I got a few basic questions.
First off, my code in question can be viewed at http://dotnetcarpenter.github.io/d3-test/ and I've used Simple Pie Chart example with D3.js and Pie Chart Update, II as examples to get a running start.
As you can see, the animation gets skewed in the end when the low path values switch to the higher values. This is obviously not what I want. I think I'm getting the order of calculations wrong but I'm not sure what to do. I'm using the code from the last example:
function change() {
//...
path.transition().duration(750).attrTween("d", arcTween); // redraw the arcs
}
// where arcTween is
function arcTween(a) {
var i = d3.interpolate(this._current, a);
this._current = i(0);
return function(t) {
return arc(i(t));
};
}
Another issue is placing labels on the sectors. I've put the update stuff in the change function and is able to read out and only render them if the value is between 0 and 100. I can't however place them in any way. Looking at the first example, I figure that I could do something like this:
text.data(data)
.text(setText)
.attr("transform", function (d) {
// we have to make sure to set these before calling arc.centroid
d.innerRadius = 0;
d.outerRadius = radius;
return "translate(" + arc.centroid(d) + ")";
})
.attr("text-anchor", "middle") //center the text on it's origin
Where text is a d3 selection and arc is: d3.svg.arc().outerRadius(radius)
But I get "Unexpected value translate(NaN,NaN) parsing transform attribute." warning in Firefox and the labels are written on top of each other.
I appreciate any help and hints. Thanks!
I finally figured it out.
Maintain sector order throughout an animation.
You'd be forgiven for thinking that object contancy had something do with it. I did. But it turns out to be much simpler than that.
Every pie chart is by default sorted by value. If you don't want to sort by value but e.g. by data list order, you just have to disable sorting.
var pie = d3.layout.pie() // get a pie object structure
.value(function(d) { // define how to get your data value
return d.value; // (based on your data set)
})
.sort(null); // disable sort-by-value
Positioning labels according to your chart
Basically, you need to calculate your label positions depending on the type of chart or graph, your trying to connect them to. In my case, it's a pie chart. So if I want d3 to help with the calculations, I need to tell centroid the inner and outer radius and, most importantly to my issue, the start and end angles. The latter was missing from my code. Getting these values is as simple as, calling our pie layout above with our dataset and then do a transform.
Note that you don't have to call .data() again if you created the SVG with d3 and already supplied your data wrapped in .pie() structure. That is, that you didn't select any existing SVG from your page.
var svg = d3.select("svg")
// do stuff with your svg
var pie = d3.layout.pie()
// set stuff on your layout
var text = svg.selectAll("text")
.data(pie(dataset)) // where dataset contains your data
.attr("transform", function(d) {
return "translate(" + arc.centroid(d) + ")";
});
I have to give credit to Philip Pedruco for helping me along the way.
Bonus info
Use viewBox if you want to position your SVG cross browser, not transform/translate.
// move pie to center
d3.select("svg").attr("viewBox", -radius + ","+ -radius +"," + size + "," + size)