Anshul Verma
Anshul Verma

Reputation: 1091

D3.js Bar Chart

HTML

<div id="searchVolume"></div>  

CSS

#tooltip {
    position: absolute;
    width: 50px;
    height: auto;
    padding: 10px;
    background-color: white;
    -webkit-border-radius: 10px;
    -moz-border-radius: 10px;
    border-radius: 10px;
    -webkit-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
    -moz-box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
    box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.4);
    pointer-events: none;
}
#tooltip.hidden {
    display: none;
}
#tooltip p {
    margin: 0;
    font-family: sans-serif;
    font-size: 12px;
    line-height: 16px;
}
.indent{
    padding-left: 5px;
}

rect {
    -moz-transition: all 0.3s;
    -webkit-transition: all 0.3s;
    -o-transition: all 0.3s;
    transition: all 0.3s;
}
rect:hover{
    fill: orange;
}
.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}
.axis text {
    font-family: sans-serif;
    font-size: 11px;
}  

Script

var margin = {top: 25, right: 40, bottom: 35, left: 85},
                w = 500 - margin.left - margin.right,
                h = 350 - margin.top - margin.bottom;
var padding = 10;

var colors =    [ ["Morning", "#F64BEE"],
                  ["Midday", "#25B244"],
          ["Afternoon", "#2BA3F4"],
          ["Evening","#FD7680"]];

var dataset = [
                { "Morning": 1400000, "Midday": 673000, "Afternoon": 43000, "Evening":50000},
                { "Morning": 165000, "Midday": 160000, "Afternoon": 21000, "Evening":23000 },
                {"Morning": 550000, "Midday": 301000, "Afternoon": 34000, "Evening":43000},
        {"Morning": 550320, "Midday": 351000, "Afternoon": 24000, "Evening":38000},
        {"Morning": 55000, "Midday": 3010, "Afternoon": 24000, "Evening":43054},
        {"Morning": 750000, "Midday": 401000, "Afternoon": 84000, "Evening":42100},
        {"Morning": 578000, "Midday": 306000, "Afternoon": 54000, "Evening":43400},
                            ];

var xScale = d3.scale.ordinal()
                .domain(d3.range(dataset.length))
                .rangeRoundBands([0, w], 0.05); 
// ternary operator to determine if global or local has a larger scale
var yScale = d3.scale.linear()
                .domain([0, d3.max(dataset, function(d) { return Math.max(d.Morning,d.Midday,d.Afternoon,d.Evening);})]) 
                .range([h, 0]);
var xAxis = d3.svg.axis()
                .scale(xScale)
                .orient("bottom");
var yAxis = d3.svg.axis()
                .scale(yScale)
                .orient("left")
                .ticks(5);




var commaFormat = d3.format(',');

//SVG element
var svg = d3.select("#searchVolume")
            .append("svg")
            .attr("width", w + margin.left + margin.right)
            .attr("height", h + margin.top + margin.bottom)
            .append("g")
            .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Graph Bars
var sets = svg.selectAll(".set") 
    .data(dataset) 
    .enter()
    .append("g")
    .attr("class","set")
    .attr("transform",function(d,i){
         return "translate(" + xScale(i) + ",0)";
     })
    ;

sets.append("rect")
    .attr("class","Morning")
    .attr("width", xScale.rangeBand()/4)
    .attr("y", function(d) {
        return yScale(d.Morning);
    })
    .attr("x", xScale.rangeBand()/4)
    .attr("height", function(d){
        return h - yScale(d.Morning);
    })
    .attr("fill", colors[0][1])
    .append("text")
   .text(function(d) {
        return commaFormat(d.Morning);
   })
   .attr("text-anchor", "middle")
   .attr("x", function(d, i) {
        return xScale(i) + xScale.rangeBand() / 4;
   })
   .attr("y", function(d) {
        return h - yScale(d.Morning) + 14;
   })
   .attr("font-family", "sans-serif") 
   .attr("font-size", "11px")
   .attr("fill", "black")
    ;

sets.append("rect")
    .attr("class","Midday")
    .attr("width", xScale.rangeBand()/4)
    .attr("y", function(d) {
        return yScale(d.Midday);
    })
    .attr("height", function(d){
        return h - yScale(d.Midday);
    })
    .attr("fill", colors[1][1])
    .append("text")
    .text(function(d) {
        return commaFormat(d.Midday);
    })
    .attr("text-anchor", "middle")
    .attr("x", function(d, i) {
        return xScale(i) + xScale.rangeBand() / 4;
    })
    .attr("y", function(d) {
        return h - yScale(d.Midday) + 14;
    })
    .attr("font-family", "sans-serif") 
    .attr("font-size", "11px")
    .attr("fill", "red")
    ;

  sets.append("rect")
    .attr("class","Afternoon")
    .attr("width", xScale.rangeBand()/4)
    .attr("y", function(d) {
        return yScale(d.Afternoon);
    })
    .attr("height", function(d){
        return h - yScale(d.Afternoon);
    })
    .attr("fill", colors[2][1])
    .append("text")
    .text(function(d) {
        return commaFormat(d.Afternoon);
    })
    .attr("text-anchor", "middle")
    .attr("x", function(d, i) {
        return xScale(i) + xScale.rangeBand() / 4;
    })
    .attr("y", function(d) {
        return h - yScale(d.Afternoon) + 14;
    })
    .attr("font-family", "sans-serif") 
    .attr("font-size", "11px")
    .attr("fill", "red")
    ; 

  sets.append("rect")
    .attr("class","Evening")
    .attr("width", xScale.rangeBand()/4)
    .attr("y", function(d) {
        return yScale(d.Evening);
    })
    .attr("height", function(d){
        return h - yScale(d.Evening);
    })
    .attr("fill", colors[3][1])
    .append("text")
    .text(function(d) {
        return commaFormat(d.Evening);
    })
    .attr("text-anchor", "middle")
    .attr("x", function(d, i) {
        return xScale(i) + xScale.rangeBand() / 4;
    })
    .attr("y", function(d) {
        return h - yScale(d.Evening) + 14;
    })
    .attr("font-family", "sans-serif") 
    .attr("font-size", "11px")
    .attr("fill", "red")
    ;
// xAxis
svg.append("g") // Add the X Axis
    .attr("class", "x axis")
    .attr("transform", "translate(0," + (h) + ")")
    .call(xAxis)
        ;
// yAxis
svg.append("g")
    .attr("class", "y axis")
    .attr("transform", "translate(0 ,0)")
    .call(yAxis)
    ;
// xAxis label
svg.append("text") 
    .attr("transform", "translate(" + (w / 4) + " ," + (h + margin.bottom - 5) +")")
    .style("text-anchor", "middle")
    .text("Keyword");
//yAxis label
svg.append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 0 - margin.left)
        .attr("x", 0 - (h / 4))
        .attr("dy", "1em")
        .style("text-anchor", "middle")
        .text("Searches");

// Title
svg.append("text")
        .attr("x", (w / 2))
        .attr("y", 0 - (margin.top / 2))
        .attr("text-anchor", "middle")
        .style("font-size", "16px")
        .style("text-decoration", "underline")
        .text("Weekly Consumption");


// add legend   
var legend = svg.append("g")
        .attr("class", "legend")
        //.attr("x", w - 65)
        //.attr("y", 50)
        .attr("height", 100)
        .attr("width", 100)
        .attr('transform', 'translate(-20,50)');

var legendRect = legend.selectAll('rect').data(colors);

legendRect.enter()
    .append("rect")
    .attr("x", w - 65)
    .attr("width", 10)
    .attr("height", 10);

legendRect
    .attr("y", function(d, i) {
        return i * 20;
    })
    .style("fill", function(d) {
        return d[1];
    });

var legendText = legend.selectAll('text').data(colors);

legendText.enter()
    .append("text")
    .attr("x", w - 52);

legendText
    .attr("y", function(d, i) {
        return i * 20 + 9;
    })
    .text(function(d) {
        return d[0];
    });  

D3 Fiddle

In the above fiddle, I have attempted to make a bar chart using d3.js library. I am stuck on following basic things which shouldn't even take much time. I am not able to grasp the functions of d3. You can play around with the fiddle and any help would be hugely beneficial:
1. I want four different bars grouped on a single x value. Like for '0' there would be four of them unlike the current one where it is merging everything into two.
2. Change the content of x-axis from numbers to days like from Monday to Friday.
3. For y-axis, I am trying to display the values like, instead of 20000, it should show 20k and the bar should recognise that while dynamically creating it. Is this possible?

Any help would be greatly beneficial. I couldn't figure it out.

Upvotes: 0

Views: 300

Answers (1)

torresomar
torresomar

Reputation: 2229

The main issue is that you start joining your data correctly but afterwards you start doing some manual stuff that could have been solved by using the data function to join the data into your child elements. Let me explain:

First of all we will need two x axis scales, one to hold our days domain and the other one will hold our time domain using as rangeRoundBands the days scale.

var day_scale = d3.scale.ordinal()
  .domain(d3.range(dataset.length))
  .rangeRoundBands([0, w], 0.05);

var time_scale = d3.scale.ordinal();

time_scale.domain(['Morning', 'Midday', 'Afternoon', 'Evening'])
  .rangeRoundBands([0, day_scale.rangeBand()]);

Let's tackle the x axis formatting while we are setting up our scales. I created an array of days and within our tickFormat function lets return the value in the array based in the index of the data we passed.

var days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
var day_axis = d3.svg.axis()
  .scale(day_scale)
  .orient("bottom")
  .tickFormat(function(d,i) {
    return days[i];
  });

Now the y axis formatting, we can solve this by using a d3.formatPrefix more info here

var prefix = d3.formatPrefix(1.21e9);
var searches_axis = d3.svg.axis()
  .scale(searches_scale)
  .orient("left")
  .ticks(5)
  .tickFormat(function(d) {
        var prefix = d3.formatPrefix(d);
        return prefix.scale(d) + prefix.symbol;
  });

Now lets skip our svg and axis config, to get to the data join issue:

var day_groups = svg.selectAll(".day-group")
  .data(dataset) // join data to our selection
  .enter().append("g")
  .attr("class", function(d, i) {
    return 'day-group day-group-' + i;
  })
  .attr("transform", function(d, i) {
    // position a g element with our day_scale and data index
    return "translate(" + day_scale(i) + ",0)";
  });

With our day-groups positioned correctly now we are able to append our time data.

var times_g = day_groups.selectAll(".time-group")
  .data(function(d) {
    // this is the tricky part, we are creating an array of
    // objects with key (time...'Morning', 'Midday', 'Afternoon', 'Evening')
    // and value (the value of the time)
    // in order to create a time group for each time event
    return Object.keys(d).map(function(key) {
      return {
        key: key,
        value: d[key]
      }
    });
  })
  .enter().append("g")
  .attr("class", function(d) {
    return 'time-group time-group-' + d.key;
  })
  .attr("transform", function(d) {
    // use our time scale to position
    return "translate(" + time_scale(d.key) + ",0)";
  });

Now lets add our rects!

var rects = times_g.selectAll('.rect')
  .data(function(d) {
    // use as data our object
    return [d];
  })
  .enter().append("rect")
  .attr("class", "rect")
  .attr("width", time_scale.rangeBand()) // get width of rect based in our time_scale
  .attr("x", function(d) {
    return 0; // returning 0 since the group is in charge of positioning
  })
  .attr("y", function(d) {
    return searches_scale(d.value); // use our y_scale
  })
  .attr("height", function(d) {
    return h - searches_scale(d.value); // use our y_scale
  })
  .style("fill", function(d) {
    return colors[d.key]; // map colors by using an object
  });

Mapping color object:

var colors = {
  "Morning":"#F64BEE",
  "Midday": "#25B244",
  "Afternoon": "#2BA3F4",
  "Evening": "#FD7680"
};

If you have any doubt of how this works here is the updated jsfiddle: https://jsfiddle.net/706gsjfg/3/ (I removed certain things, but you can add them later I guess)

Upvotes: 2

Related Questions