AlwaysALearner
AlwaysALearner

Reputation: 43947

D3 - Shrink Linear Scale Range

I'm trying to create a very simple stacked horizontal bar chart from a simple dataset containing 2 values.

  1. 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.

  2. 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

Answers (1)

tarulen
tarulen

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 :

  • a rect starting at d.y0=0 must have x=100
  • a rect with d.y=0 must have width=0
  • a rect with d.y0=71000 (around half the sum) must have x=250 (middle of the drawing)
  • a rect with d.y=71000 must have width=150 (half the width of the drawing)

=> 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

Related Questions