简体   繁体   中英

d3 break line graph if no data

I am using dc to create a line graph where capacity is on the y-axis and week is on the x-axis. For weeks, the range is 1-52, but there is no data from weeks 2-40. I only have data for week 1 and 41-52, but my line graph is still creating a line when there is no data:

在此处输入图像描述

How do I get it so the line graph will break if there are no values? So it wouldn't be one connected line. Here is my code for reference

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

This is how results would look like

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

I have tried to add .defined(d => { return dy;= null; }); and .defined(d =>.isNaN(d;value)); but that didn't do anything... Any help will be greatly appreciated

As we discussed in the comments, the important problem is that dc.js will only draw the data it receives. It doesn't know if data is missing, so we will need to fill in the null s in order to draw gaps in the line.

I linked to a previous question , where the data is timestamps. The answer there uses a d3 time interval to generate the missing timestamps.

However, your data uses integers for keys (even though it represents weeks), so we will need to change the function a little bit:

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. This function takes a group, the value to fill, and a stride, ie the desired gap between entries.
  2. It reads the current data, and generates the desired keys using d3.range .
  3. It walks both arrays, adding any missing entries to a copy of the group data.
  4. If there are any leftover entries from the original group data, it appends it.
  5. If there are any remaining targets, it generates those.

Now we wrap our original group using this function, creating a "fake group" :

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

and pass it to the chart instead:

.group(filledGroup);

One weakness of using LineChart.defined() , and the underlying d3.line.defined , is that it takes two points to make a line. If you have isolated points, as week 1 is isolated in your original data, then it won't be shown at all.

In this demo fiddle , I have avoided the problem by adding data for week 2.

具有非隔离不相交点的屏幕截图

But what about isolated dots?

I was curious how to solve the "isolated dots problem" so I tried showing the built-in dots that are usually used for a mouseover effect:

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 but it currently relies on disabling the interactivity of those dots so they don't disappear.

以点为孤立的数据

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM