在基于时间的条形图中填充区域,其中没有相应的数据

时间:2013-05-02 21:52:14

标签: date svg d3.js bar-chart

我有一个条形图,显示JSON数据,其中x轴的时间刻度设置为1天,y轴为线性刻度。

以下是我的代表性数据示例:

[{"date":"2013-04-20","load_volume":400},{"date":"2013-04-23","load_volume":400},{"date":"2013-04-24","load_volume":400},{"date":"2013-04-28","load_volume":1732},{"date":"2013-04-30","load_volume":400}]

以下是该数据的图表:

enter image description here

我想要做的是为数据集中的缺失日期键绘制“stub”rect元素,以清楚地表明该日期没有相应的值,如下图所示:

enter image description here

我应该怎么做?

我正在考虑尝试在javascript中使用.elementFromPoint()函数选择元素,如果在xAxis的指定点没有rects,那么继续绘制一个“stub”rect元素,但我不确定会工作,我想知道在D3中是否有更简单的方法来实现这一点。

2 个答案:

答案 0 :(得分:1)

我认为你在按摩数据阶段试图解决这个问题是正确的。这就是我想出的:

首先,创建一个以日期为键的对象(上面的数组为var inputData),您可以在以后查找该对象:

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;
}, {});

然后,为x轴制作日期比例:

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

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

您现在可以使用比例的输出来创建适合您所考虑的图表的数据数组:

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};
});

输出:

[{"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}]

在上面的函数中根据需要调整存根对象的值,以获得所需的高度。您可以使用stub布尔值在每个矩形上设置一个类来更改蓝色或灰色之间的颜色。

答案 1 :(得分:0)

当迈克说你将花费大部分时间来按摩数据时,我猜他不是在开玩笑。好吧,我最后写了一个相当长的算法,在数据集中插入缺少的日期。它的工作原理是将所有当前日期提取到一个数组中,将它们转换为毫秒时间,成对迭代日期,并填充数量相等的天数(对于getMissingDates()函数中的循环),然后构建一个带有它们的JSON字符串,最后将这个新的JSON字符串与原始输入数据合并,然后按升序排序。

以下是我提出的建议:

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) });
}

然后我连接到stub布尔值以应用不同的样式,如下所示:

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

JSON输出(日期以毫秒为单位,存根高度为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}]

结果如下:

enter image description here

我可能会尝试重构代码,看看我是否可以让它更简洁,但我只想让它先工作。