Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multi-layered comparison histogram

Tags:

d3.js

I need to build a histogram using the d3.js library. The histogram should look like the one below. The main objective of this histogram is to compare two sets of results. The results that are shown in the diagram below represent data gathered over the same days of the month, but in two different months (say 1st - 6th of January (green) and February (blue)).

The histogram that I currently have basically shows the correct data, just that it does not overlay them (or even show them side-by-side). Instead it shows them ordered by the date, the results representing January first (to the left), and the ones representing February second (to the right). What should I do in order to have them show side-by-side?

Edit: I work in an Angular environment, and I use a prebuilt directive for showing the graph. The code for the directive is lcoated at https://github.com/fullscale/dangle/blob/master/dist/dangle.datehisto.js

Desired result:

desired chart

Current result:

current chart

like image 300
Adrian Marinica Avatar asked Jun 08 '26 15:06

Adrian Marinica


1 Answers

The issue here is that you are using a time scale when it should really be linear for what you are trying to do. You don't want your x offset to increase based on the actual date/time, but instead just on the date.getDate() portion of the date. Assuming d.time represents the same thing as new Date().getTime(), then you can change your scale to linear and use just the day plus an offset for the month to determine your x values. This will require you to build some form of legend to indicate the months though.

First change the scale we are using:

// create x,y scales (x is inferred as time)
// var x = d3.time.scale()
//    .range([0, width]);
//
// Use linear scale since we really care about the day portion of the date/time
var x = d3.scale.linear()
      .range([0, width]);

Then calculate our month and day ranges:

// Get the range of months so we can use the month
// to offset the x value for overlay
var monthExtent = d3.extent(data,function(d) { 
            var date = new Date(); 
            date.setTime(d.time.getTime()); 
            return date.getMonth(); 
        });

// Get the range of days for the graph
// If you always want to display the whole month
// var dateExtent = [0,31]
//
// Otherwise calculate the range
var dateExtent = d3.extent(data,function(d) {  
            var date = new Date(); 
            date.setTime(d.time.getTime()); 
            return date.getDate(); 
        });

Then set the x domain to our day range:

// recalculate the x and y domains based on the new data.
 // we have to add our "interval" to the max otherwise 
 // we don't have enough room to draw the last bar.
 //
 //x.domain([
 //    d3.min(data, function(d) { 
 //        return d.time;
 //    }), 
 //    d3.max(data, function(d) { 
 //        return d.time;
 //    })
 //]);


 // Our x domain is just the range of days 
 x.domain(dateExtent);

Add a color scale to differentiate between months:

// Set up a color scale to separate months
 var color = d3.scale.category10();

Now, change the x attribute to use the day value plus an offset for the month to create the overlay. I used 20 pixels here but you could easily change it to a percentage of teh bar width instead. Then add a fill attribute using the month and color scale so that each month gets it's own color.

bars.enter()
    .append('rect')
        .attr('class', 'histo rect ')
        .attr('cursor', 'pointer')
        .attr('x', function(d) {
            // Extract the day portion of the date/time
            // and then offset the rect by it's month value
            var date = new Date();
            date.setTime(d.time.getTime());
            return x(date.getDate()) + (date.getMonth() - monthExtent[0]) * 20; 
        })
        .attr("fill",function(d) { 
            var date = new Date();
            date.setTime(d.time);
            return color(date.getMonth()); 
        })
        .attr("y", function(d) { return height })
        .attr('width', barWidth)
        .transition()
            .delay(function (d,i){ return i * 0; })
            .duration(500)
                .attr('height', function(d) { return height - y(d.count); })
                .attr('y', function(d) { return y(d.count); });

Finally, you'll probably have to change how the barWidth is calculated to make sure there is the right amount of space between each day. Hopefully this helps!

like image 166
lamflam Avatar answered Jun 11 '26 19:06

lamflam



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!