D3.js create complex bar chart from values ​​in object

The code below will create a histogram of a single value in an array of objects called "data". How can I use a collection of data to create a stacked bar chart?

set margins

 var w = 700;
 var    h = 500;
 var margin = {
    top: 58,
 bottom: 120,
    left: 60,
    right: 40
};
var width = w - margin.left - margin.right;
var height = h - margin.top - margin.bottom;

      

define data

 data =[
 { name: "Subway", carbs: "480", fat: "400", protein: "120", sum: "1000"}, 
 { name: "Steak & Potatos", carbs: "900", fat: "350", protein: "200", sum:    "1450"}, 
 { name: "Sausage", carbs: "50", fat: "350", protein: "80", sum: "480"} 
 ];

      

I want the bar height to be the sum of the values ​​for carbohydrate, fat, protein, but each value has a different color. define x

 var x = d3.scale.ordinal()
    .domain(data.map(function(e){
        return e.name;
    }))
    .rangeBands([0,width]);

      

define y. This is where I think I need help

 var y = d3.scale.linear()
    .domain([0, d3.max(data, function(d,i){

        return parseInt(d.carbs) ;
    })])
    .range([height, 0]);

      

The above will give me a bar with one value (carbohydrates). But what I really want to do is create a complex histogram. Any help would be greatly appreciated.

Added after below the answer "z is your color scheme here, which will decide how your rectangles look"

 var z = d3.scale.ordinal()
.range(["#ff0", "#f00", "#0ff"])
.domain(["carbs", "fat", "protein"]);

      

This is a graph function that draws a chart

 function plot(params){

    this.append('g')
    .call(params.gridlines)
    .classed('gridline', true)
    .attr('transform','translate(0,0)');

    this.selectAll('bar')
    .data(params.data)
    .enter()
    .append('rect')
    .classed('bar', true)
    .attr('x', function(d,i){
        return x(d.name)
    })
    .attr('y',function(d,i){
        return y(d.carbs);
    })
    .attr('width', function(d){
        return x.rangeBand();
    })
    .attr('height', function(d,i){
        return height - y(d.carbs)
    })
    .style('fill',function(d,i){
        return ordinal_color_scale(i);
    });

this.selectAll('.bar_label')
        .data(params.data)
        .enter()
            .append('text')
            .classed('bar_label', true)
            .attr('x', function(d,i){
                return x(d.name) + (x.rangeBand()/2);
            })
            .attr('dx', 0)
            .attr('y', function(d,i){
                    return y(d.carbs);
                })
            .attr('dy', -6)
            .text(function(d){
                return d.carbs;
            });

        this.append('g')
            .classed('x axis', true)
            .style('fill', '#ffd000')
            .attr('transform','translate('+ 0 +','+ height +')')
            .call(params.axis.x)
                .selectAll('text')
                .style('text-anchor','end')
                .attr( 'dx', -8)
                .attr('dy',8)
                .attr('transform','translate(0,0) rotate(-45)');

        this.append('g')
            .classed('y axis', true)
            .style('fill', '#ffd000')
            .attr('transform','translate(0,0)')
            .call(params.axis.y);
}

plot.call(chart, {
    data: data,
    axis: {
        x: x_axis,
        y: y_axis
    },
    gridlines: y_gridlines
});

      

I don't understand how to draw the z variable in bars in the chart.

+2


source to share


1 answer


Your domain for y is really what is wrong here. All variables need to be considered in the range, not just carbohydrates. In other words, your function max

should consider the sum of all properties.

var y = d3.scale.linear()
    .domain([0, d3.max(data, function(d,i){
        return parseInt(d.sum);
})])
.range([height, 0]);

      

This is because at the end your bars will be the same as the sum of all your properties (since this is a pivot histogram)

Then you will need to use the function stack

when you draw your elements <rect>

. Each bar will contain 3 <rect>

items (one for carbs, fat and protein stacked one on top of the other). You haven't posted any code for this, but it will most likely look like this:

// z is your color scale here which will decide what your rectangles look like
var z = d3.scaleOrdinal()
    .range(["#<colorForCarbs>", "#<colorForFat>", "#<colorForProtein>"])
    .domain(["carbs", "fat", "protein"]);

g.selectAll(".serie")
.data(stack.keys(["carbs", "fat", "protein"])(data))
.enter().append("g")
  .attr("class", "serie")
  .attr("fill", function(d) { return z(d.key); })
.selectAll("rect")
.data(function(d) { return d; })
.enter().append("rect")
  .attr(class, 'bar')
  .attr("x", function(d) { return x(d.data.name); })
  .attr("y", function(d) { return y(d[1]); })
  .attr("height", function(d) { return y(d[0]) - y(d[1]); })
  .attr("width", x.bandwidth());

      

"Serial" in the example represents, for example, all rectangles that are associated with "carbohydrates" (as opposed to a stack, which will contain 3 rectangles for carbohydrates, fat and protein).



This example should give you everything you need in case you are missing something: http://bl.ocks.org/mbostock/3886208

EDIT

With updated question, this is the section of code where you should use z scale

this.selectAll('bar')
    .data(params.data)
    .enter()
    .append('rect')
    .classed('bar', true)
    .attr('x', function(d,i){
        return x(d.name)
    })
    .attr('y',function(d,i){
        return y(d.carbs);
    })
    .attr('width', function(d){
        return x.rangeBand();
    })
    .attr('height', function(d,i){
        return height - y(d.carbs)
    })
    .style('fill',function(d,i){
        return ordinal_color_scale(i);
    });

      

Use instead

var stack = d3.stack(); 
g.selectAll(".serie")
    .data(stack.keys(["carbs", "fat", "protein"])(data))
    .enter().append("g")
    .attr("class", "serie")
    .attr("fill", function(d) { return z(d.key); })
    .selectAll("rect")
       .data(function(d) { return d; })
       .enter()
       .append("rect")
           .attr("x", function(d) { return x(d.data.name); })
           .attr("y", function(d) { return y(d[1]); })
           .attr("height", function(d) { return y(d[0]) - y(d[1]); })
           .attr("width", x.rangeBand());

      

+1


source







All Articles