score:0

When Mike said you will spend most of your time massaging the data, I guess he wasn't kidding. Well, I ended up writing a rather longish algorithm that interpolates missing dates in the dataset. It works by extracting all the current dates into an array, converting them to millisecond time, iterating over the dates in pairs and filling in days equal in number to the difference between the pairs (while loops in getMissingDates() function), then building a JSON string with them, finally merging in this new JSON string with the original input data and then sorting in ascending order.

Here's what I came up with:

d3.json("/users/" + user_id + "/workouts/analyze.json", function(error, response) {
      data = response;

deriveMissingDates();

//
function deriveMissingDates() {
  date_array = getCurrentDates();
  missingDates = getMissingDates(date_array);
  new_json = buildJSONFromMissingDates(missingDates);
  new_data = mergeMissingDates(new_json);
  sortJSON(data);
}

// Get all current dates in dataset
// @param date_array. 
function getCurrentDates(date_array) {
var date_array = [];
for (i = 0; i < data.length; i++) {
  var json_object = " {\"date\":\"" + data[i].date + "\", \"load_volume\":\"" + data[i].load_volume + "\"},"
  date_array.push(json_object.slice(10,20));
}
return date_array;
}

// Interpolates missing dates.
// @param arr. Array of current dates in original data set.
function getMissingDates(arr) {
    var predptr = 0, leadptr = 1, missingDates = []; // initialize predecessor pointer, lead pointer, and missing dates array
    
    while (true) {
      if (predptr == arr.length) break;
      var firstDate = new Date(arr[predptr]).getTime();
      var secondDate = new Date(arr[leadptr]).getTime();
      var currentDate = firstDate + ((24 * 60 * 60 * 1000) * 2);
      var d = new Date(currentDate);
      
      while (currentDate <= secondDate) {  // Push missing dates onto array.
        var d = new Date(currentDate);
        missingDates.push(d.getFullYear() + '-' + ('0' + (d.getMonth()+1)).slice(-2) + '-' + ('0' + d.getDate()).slice(-2));
        currentDate += (24 * 60 * 60 * 1000); // add one day
      }         
    predptr++;
    leadptr++;
    }
    return missingDates;    
}

// Builds JSON string from missingDates array.
// @param arr. Array with each missing date.
function buildJSONFromMissingDates(arr) {
    json = ""
    for (i = 0; i < arr.length; i++) {
        json += "{\"date\":" + "\"" + arr[i] + "\",\"stub\":" + true + ",\"load_volume\":" + 200 + "},";
    }
    json = json.slice(0,json.length-1);
    json = "[" + json + "]";
    json = $.parseJSON(json);
    return json;
}

// Concatenate missingDates array with original input data
// @param new_json. New JSON string built from missing date values.
function mergeMissingDates(new_json) {
    data = data.concat(new_json);
    var content = [];
    for (i=0; i < data.length; i++) {
        content += "{" + data[i].date + "," + data[i].load_volume + "},";
    }
    return data;
}

// Sort new JSON dataset in ascending order.
// @param data. New JSON data with missing date objects.
function sortJSON(data) {
    for (i = 0; i < data.length; i++) {
        data[i].date = new Date(data[i].date).getTime(); // convert dates to millisecond time
    }
    
    data.sort(function(a,b) { return parseInt(a.date) - parseInt(b.date) });
}

Then I hooked into the stub boolean value to apply different styles, like so:

.style("fill", function(d) { 
            if (d.stub == true) {
                return "#dddddd"
            } else {
                return "#00e0fe"
            }});

JSON output (with dates in milliseconds and stub height of 200):

[{"date":1366416000000,"load_volume":400},{"date":1366502400000,"stub":true,"load_volume":200},{"date":1366588800000,"stub":true,"load_volume":200},{"date":1366675200000,"load_volume":400},{"date":1366761600000,"load_volume":400},{"date":1366848000000,"stub":true,"load_volume":200},{"date":1366934400000,"stub":true,"load_volume":200},{"date":1367020800000,"stub":true,"load_volume":200},{"date":1367107200000,"load_volume":1732},{"date":1367193600000,"stub":true,"load_volume":200},{"date":1367280000000,"load_volume":400}]

And here is the result:

enter image description here

I'll probably try and refactor the code to see if I can make it more concise, but I just wanted to get it working first.

score:1

I think you're correct in trying to tackle this in the massage-the-data phase. This is what I came up with:

First, create an object with the date as the key (assumine your array above is var inputData) which you can use for lookup later:

inputData.forEach(
  function(d){
    d.date = new Date(d.date).setHours(0)
  }
);

var data = inputData.reduce(function(o,d){
  o[+d.date] = d.load_volume;
  return o;
}, {});

Then, make your date scale for the x axis:

var extent = d3.extent(inputData, function(d){
    return d.date;
});

var x = d3.time.scale().range([0,chartWidth]).domain(extent);

You can now use the output of your scale to create an array of data which is suitable for the chart you had in mind:

var chartData = x.ticks(d3.time.days).map(function(d){
  return data[+d] ? {date: d, stub: false, value: data[+d]} : {date:d, stub: true, value: 10};
});

output:

[{"date":1366383600000,"stub":false,"value":400},{"date":1366470000000,"stub":true,"value":10},{"date":1366556400000,"stub":true,"value":10},{"date":1366642800000,"stub":false,"value":400},{"date":1366729200000,"stub":false,"value":400},{"date":1366815600000,"stub":true,"value":10},{"date":1366902000000,"stub":true,"value":10},{"date":1366988400000,"stub":true,"value":10},{"date":1367074800000,"stub":false,"value":1732},{"date":1367161200000,"stub":true,"value":10},{"date":1367247600000,"stub":false,"value":400}]

Adjust the value of the stub object as you like in the above function to get the height you want. You can use the stub boolean to set a class on each rect to change the color between blue or gray.


Related Query

More Query from same tag