Reputation: 43947
I'm trying to create a very simple stacked horizontal bar chart from a simple dataset containing 2 values.
I want to display the dataset values on the left and right of the bars respectively. The <svg>
is 500px wide and I would like
the chart to be displayed from 100 to 400 so that I can accomodate <text>
before and after the bars. For this I'm trying to shrink the range
from [0, 500] to [100, 400] but result is not as expected.
I would also like to draw the chart without using layout.stack() so that I can use a simple dataset like [66852, 7550]
Here is a Fiddle
//Width and height
var w = 500;
var h = 60;
//Original data
//var dataset = [66852, 7550]
//changed to below to support layout.stack()
var dataset = [ [{y:66852}], [{y:7550}] ];
//Set up stack method
var stack = d3.layout.stack();
//Data, stacked
stack(dataset);
//Set up scales
var xScale = d3.scale.linear()
.domain([0, d3.max(dataset, function(d) {
return d3.max(d, function(d) {
return d.y0 + d.y;
});
})
])
.range([100, 400]);
var yScale = d3.scale.ordinal()
.domain(d3.range(dataset[0].length))
.rangeRoundBands([0, h], 0.05);
//Easy colors accessible via a 10-step ordinal scale
var colors = d3.scale.category10();
//Create SVG element
var svg = d3.select(".chart")
.attr("width", w)
.attr("height", h);
// Add a group for each row of data
var groups = svg.selectAll("g")
.data(dataset)
.enter()
.append("g")
.style("fill", function(d, i) {
return colors(i);
});
// Add a rect for each data value
var rects = groups.selectAll( 'rect' )
.data( function( d ) { return d; } )
.enter()
.append("rect")
.attr("x", function(d, i) {
return xScale(d.y0);
})
.attr("y", function(d, i) {
console.log('y: '+yScale(i));
return yScale(i);
})
.attr("width", function(d) {
console.log('width: '+xScale(d.y));
return xScale(d.y);
})
.attr("height", 10);
Upvotes: 1
Views: 465
Reputation: 2100
The problem is that, despite its name, your linear xScale
is not linear (the image of 0 is 100). So when you use return xScale(d.y);
for the width, you're adding 100 to the width of every rectangles (in mathematical terms, the function used is xScale(z) = 0.00211 * z + 100 : this is affine, not linear).
Edit, examples :
=> It is not possible to use directly the same scale for width and x position. So either you use different functions to compute width and x, or you cheat with an additional transform
for x (see my original solution below).
The easiest solution is to have a range
of [0,300]
(instead of [100,400]
), and put the drawing area within a g
with .attr("transform", "translate(100,0)")
to center the plot.
Second option (sounds like the one you'd prefer): use xScale(d.y0+d.y) - xScale(d.y0)
for the width (instead of just xScale(d.y)
). This directly computes the distance between left and right endpoints, i.e. it gives the width. It should work with any scale (not necessarily linear).
As for your second question, I don't really know another general way of doing this (other than writing your own drawing procedure, it won't be too hard). But you can easily translate a simple array into a layout-friendly one:
var dataset = [66852, 7550]
var layoutDataset = dataset.map(function(d){ return [{y:d}] });
Upvotes: 2