【问题标题】:d3 break line graph if no datad3 如果没有数据则折线图
【发布时间】:2021-04-14 15:37:08
【问题描述】:

我正在使用 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)); 但这没有任何作用...任何帮助将不胜感激

【问题讨论】:

  • 您的数组中有d.y == null 的数据点吗?如果不是,如何知道缺少某个值的尺度?
  • @AndrewReid 所说的:dc.js 只会显示它收到的数据。它不知道“应该”那里有哪些数据。 Here is a related question,区别在于你想用null而不是0来填充。
  • 啊,这很有道理...@Gordon 我如何将它合并到我的代码中?
  • 这是一个假组,因此您可以使用该函数包装您的组,然后再将其传递给图表。如果您无法弄清楚,请编辑您的问题并添加足够的内容以重现您目前所拥有的内容(最好是 fiddle、bl.ock 或其他笔记本,或至少是示例数据)。如果你这样做,我会发布一个答案。
  • @Gordon 我用我的代码的样子编辑了我的问题 -- 如果你需要其他任何东西,请告诉我!

标签: javascript d3.js dc.js crossfilter linegraph


【解决方案1】:

正如我们在 cmets 中所讨论的,重要的问题是 dc.js 只会绘制它接收到的数据。它不知道是否缺少数据,因此我们需要填写nulls 以便在该行中绘制空白。

我链接到a previous question,其中的数据是时间戳。那里的答案使用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() 和底层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 但它目前依赖于禁用这些点的交互性,因此它们不会消失。

【讨论】:

  • 忘记回复但非常感谢!这完美地工作,正是我所需要的
猜你喜欢
  • 2013-08-15
  • 2015-01-24
  • 2015-09-06
  • 1970-01-01
  • 2018-09-24
  • 2017-10-17
  • 2022-01-23
  • 1970-01-01
  • 2012-12-27
相关资源
最近更新 更多