d3 如果没有数据则折线图

d3 break line graph if no data

我正在使用 dc 创建一个折线图,其中容量在 y 轴上,周在 x 轴上。对于周,范围是 1-52,但没有第 2-40 周的数据。我只有第 1 周和第 41-52 周的数据,但当没有数据时,我的折线图仍在创建一条线:

如何获取它以便在没有值时折线图会中断?所以它不会是一条连接线。这是我的代码供参考

let chart = dc.lineChart("#chart");
let ndx = crossfilter(results);
            
let weekDimension = ndx.dimension(function (d) {
     return d.week = +d.week;
});
                
function reduceAdd(p, v) {
    ++p.count;
    p.total += v.capacity;
    p.average = p.total / p.count;
    return p;
}

function reduceRemove(p, v) {
    --p.count;
    p.total -= v.capacity;
    p.average = p.count ? p.total / p.count : 0;
    return p;
}

function reduceInitial() {
    return { count: 0, total: 0, average: 0 };
} 

let capacityGroup = weekDimension.group().reduce(reduceAdd, reduceRemove, reduceInitial);
            
chart.width(360)
    .height(200)
    .margins({ top: 20, right: 20, bottom: 50, left: 30 })
    .mouseZoomable(false)
    .x(d3.scale.linear().domain([1, 52]))
    .renderHorizontalGridLines(true)
    .brushOn(false)
    .dimension(weekDimension)
    .valueAccessor(function (d) {
        return d.value.average;
    })
    .group(capacityGroup);

dc.renderAll('chart');

这就是 results 的样子

{month : "1", capacity: "48"}
{month : "1", capacity: "60"}
{month : "42", capacity: "67"}
{month : "42", capacity: "60"}
{month : "43", capacity: "66"}
{month : "44", capacity: "52"}
{month : "45", capacity: "63"}
{month : "46", capacity: "67"}
{month : "47", capacity: "80"}
{month : "48", capacity: "61"}
{month : "48", capacity: "66"}
{month : "49", capacity: "54"}
{month : "50", capacity: "69"}

我已经尝试添加 .defined(d => { return d.y != null; });.defined(d => !isNaN(d.value)); 但没有做任何事情...任何帮助将不胜感激

正如我们在评论中讨论的那样,重要的问题是 dc.js 只会绘制它接收到的数据。它不知道数据是否丢失,所以我们需要填写 null 以在行中绘制空白。

我链接到 , where the data is timestamps. The answer there uses a d3 time interval 以生成丢失的时间戳。

但是,您的数据使用整数作为键(即使它代表周),因此我们需要稍微更改一下函数:

function fill_ints(group, fillval, stride = 1) { // 1
    return {
      all: function() {
        var orig = group.all();
        var target = d3.range(orig[0].key, orig[orig.length-1].key, stride); // 2
        var result = [];
        for(var oi = 0, ti = 0; oi < orig.length && ti < target.length;) {
          if(orig[oi].key <= target[ti]) {
            result.push(orig[oi]);
            if(orig[oi++].key === target[ti])
              ++ti;
          } else {
            result.push({key: target[ti], value: fillval});
            ++ti;
          }
        } // 3
        if(oi<orig.length) // 4
          Array.prototype.push.apply(result, orig.slice(oi));
        if(ti<target.length) // 5
          result = [...result, ...target.slice(ti).map(t => ({key: t, value: fillval}))];
        return result;
      }
    };
}
  1. 此函数采用一个组、要填充的值和一个跨度,即条目之间所需的间隙。
  2. 它读取当前数据,并使用d3.range生成所需的密钥。
  3. 它遍历两个数组,将任何缺失的条目添加到组数据的副本中。
  4. 如果原始组数据中有任何剩余条目,则会附加它。
  5. 如果有任何剩余目标,它会生成这些目标。

现在我们使用这个函数包装我们原来的组,创建一个 "fake group":

const filledGroup = fill_ints(capacityGroup, {average: null});

并将其传递给图表:

.group(filledGroup);

使用 LineChart.defined(), and the underlying d3.line.defined 的一个弱点是需要两个点才能形成一条线。如果您有孤立点,因为第 1 周在您的原始数据中是孤立的,那么它根本不会显示。

this demo fiddle 中,我通过添加第 2 周的数据避免了这个问题。

但是孤立点呢?

我很好奇如何解决“孤立点问题”,所以我尝试显示通常用于鼠标悬停效果的内置点:

chart.on('pretransition', chart => {
    const all = chart.group().all();
  isolated = all.reduce((p, kv, i) => {
    return (kv.value.average !== null &&
    (i==0 || all[i-1].value.average == null) &&
      ((i==all.length-1 || all[i+1].value.average == null))) ?
      {...p, [kv.key]: true} : p;      
  }, {});
  chart.g().selectAll('circle.dot')
    .filter(d => isolated[d.data.key])
    .style('fill-opacity', 0.75)
    .on('mousemove mouseout', null)
})

This works 但它目前依赖于禁用这些点的交互性,这样它们就不会消失。