如何定义 dc.js 中缺失值的处理?

How to define handling of missing values in dc.js?

我是 dc.js 的新手,我设法创建了一些条形图和一个线系列图。

如果我点击我的条形图来过滤我的年龄值,

不存在的值似乎被视为零值而不是 NaN 或 null。 如果我 select 第一个和最后一个值,但不是中间值,我会 期望有两个点和一条连接它们的直线。

=>如何告诉 dc.js 不显示 未通过过滤器的值?

我试图从

调整我的值访问器
.valueAccessor(d => d.value)

.valueAccessor(d => d.value>0?d.value:null)

.valueAccessor(d => d.value>0?d.value:NaN)

但这并没有帮助。缺失值仍然回落到零。

我想我需要以某种方式为我的折线图应用 d3.js defined 函数,但不知道如何:How do I get d3 to treat missing values as null instead of zero?

示例代码(请在 运行 代码段后使用 "Full page" 选项。否则您可能只会看到一些警告:

<html>

 <head>

  <title>dc demo</title>

  <meta http-equiv='content-type' content='text/html; charset=UTF8'>

  <!-- this demo is based on following tuturials:
     https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm 
     https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil  
     -->

   <script src='https://d3js.org/d3.v4.min.js'></script>
   <script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
   <script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
  

 </head>
 
 <body>
     
     <div style="font-family:arial;">

    <div style="float:left;padding:20px;">
    <div >
     <label>Number of colors:</label>
     <div id='color-chart-count'></div>
    </div>

    <div>
     <label>Value sum for colors:</label>
     <div id='color-chart-value-sum'></div>
    </div>

   </div>



   <div style="float:center;padding:20px;">
    <div>
     <label>Number of ages:</label>
     <div id='age-chart-count'></div>
    </div>

    <div>
     <label>Value sum for ages:</label>
     <div id='age-chart-value-sum'></div>
    </div>

   </div>

     </div>
     
     
        <div style="font-family:arial;">
            <label for='value-chart'>Values:</label>
         <div id='value-chart'></div>
        </div>
     
 
  <script>
   
   let data = [                 

        {color: 'red', age: 1, value: 10},
        {color: 'red', age: 2, value: 11},
        {color: 'red', age: 3, value: 12},        

        {color: 'green', age: 1, value: 20},
        {color: 'green', age: 2, value: 21},
        {color: 'green', age: 3, value: 22},

        {color: 'blue', age: 1, value: 30},
        {color: 'blue', age: 2, value: 31},
        {color: 'blue', age: 3, value: 32},
      ];

            //create instance of cross filter
   let cf = crossfilter(data);    

            //define dimensions and groups
            let colorDim = cf.dimension(d=> d.color);
            let colorGroupCount = colorDim.group().reduceCount();
            let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);

            let ageDim = cf.dimension(d=> d.age);
            let ageGroupCount = ageDim.group().reduceCount();
            let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);

            let colorAgeDim = cf.dimension(d => [d.color, d.age]);              
            let colorAgeGroup = colorAgeDim.group().reduceSum( d => d.value);

            let ordinalColors =  ['red','green','blue'];    
            let ordinalAgeColors =  ['lightgray','grey','#666666'];                    

            //color charts

            let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);

            let colorChartCount = barChart('#color-chart-count')               
    .xAxisLabel('Color')             
    .x(d3.scaleBand().domain(ordinalColors))  
    .dimension(colorDim)  
    .yAxisLabel('Count')     
    .group(colorGroupCount)   
    .defineColors(rgbColorScale);    

   colorChartCount.yAxis().ticks(4);  
    

   barChart('#color-chart-value-sum')               
    .xAxisLabel('Color')                   
    .x(d3.scaleBand().domain(ordinalColors))  
    .dimension(colorDim)  
    .yAxisLabel('Value sum')    
    .group(colorGroupValueSum)
    .defineColors(rgbColorScale);    

            //age charts

            let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);

            /*
            dc.pieChart('#age-chart-count')
                .width(100)
    .height(100)
    .ordinalColors(ordinalAgeColors)
    .dimension(ageDim)
    .group(ageGroupCount);
   */

   let ageChartCount = barChart('#age-chart-count')              
    .xAxisLabel('Age')                    
    .x(d3.scaleLinear().domain([1,2,3]))  
    .dimension(ageDim)  
    .yAxisLabel('Count')    
    .group(ageGroupCount)   
    .defineColors(ageColorScale);

   ageChartCount.yAxis().ticks(4); 

   barChart('#age-chart-value-sum')               
    .xAxisLabel('Age')             
    .x(d3.scaleLinear().domain([1,2,3])) 
    .dimension(colorDim)  
    .yAxisLabel('Value sum')    
    .group(ageGroupValueSum)
    .defineColors(ageColorScale);

   function barChart(elementSelector){

    let barChart = dc.barChart(elementSelector)
     .width(200)
     .height(200)    
     .xUnits(dc.units.ordinal)     
     .margins({top:10,left:30,right:15,bottom:35}) 
     .barPadding(0.1)
     .outerPadding(0.1);

     barChart.defineColors = function(colorScale){
      
      this.renderlet(chart=>{
          chart.selectAll('rect.bar')
            .each(function(d){
            d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
            });
         
         });
         return this;
     }

     return barChart;
   } 

             
             //value chart
             let chart = dc.seriesChart('#value-chart');

    chart
    .width(500)
    .height(500)
    .chart( c => 
           dc.lineChart(c)
           .renderDataPoints(true)            
            
    )    
    .xAxisLabel('Age')
    .x(d3.scaleLinear().domain([1,3]))
    .dimension(colorAgeDim)    
    .yAxisLabel('Value')
    .elasticY(true)
    .group(colorAgeGroup)     
    .brushOn(false) 
    .clipPadding(10)
    .mouseZoomable(true)
    .seriesAccessor(d => d.key[0])
    .keyAccessor(d => d.key[1])
    .valueAccessor(d => d.value)
    .ordinalColors(['blue','green','red','yellow'])
    .legend(dc.legend().x(430).y(350)); 
   
            
            dc.renderAll();

            //dimensions can also be used for filtering:
            
            //let color_red = colorDim.filter('red');           
            //let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
            //console.log(filterResult);

            //let functionFilter = ageDim.filter(age => age === 2);  
            //let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
            //console.log(functionFilterResult);


  </script>
 
 
 </body>

</html>

如果只有一个年龄 selected 显示水平轴上不需要的黑色区域和线条和点的线系列图表

Gordon 的评论为我指明了正确的方向。下面是两个修改后的版本。

  • 我包含了 dc.css 样式表来解决黑色区域问题。

  • 而不是 group().reduceSum() 我使用自定义减少操作将缺失值视为 null 而不是 0。现在我的值访问器正确地 returns null 缺失值。

        function reduceAdd(previous, current) {
    
            if(current){
                if(current.value !== null){
                    if(previous.sum === null){
                        previous.sum = current.value;
                        previous.count = 1;
                    } else {
                        previous.sum += current.value;
                        previous.count += 1;
    
                    }
                } 
            } 
            return previous;                                                
        }
    
        function reduceRemove(previous, current) {                  
            if(current){
                if(current.value !== null){
                    if(previous.sum !== null){
                        previous.sum -= current.value;
                        previous.count -= 1;
                        if(previous.count === 0){
                            previous.sum = null;
                        }
                    } 
                } 
            } 
            return previous;                
        }
    
        function reduceInit() {
                return { 
                        sum: null,
                        count: 0
                       };
        }
    
        let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);
    

A. 第一个示例应用defined 方法来过滤折线图中的空值。缺失值显示为间隙:

<html>

 <head>

  <title>dc demo</title>

  <meta http-equiv='content-type' content='text/html; charset=UTF8'>

  <!-- this demo is based on following tuturials:
     https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm 
     https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil  
     -->

      



   <script src='https://d3js.org/d3.v4.min.js'></script>
   <script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
   <script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
   <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.css" />

 </head>
 
 <body>
     
     <div style="font-family:arial;">

    <div style="float:left;padding:20px;">
    <div >
     <label>Number of colors:</label>
     <div id='color-chart-count'></div>
    </div>

    <div>
     <label>Value sum for colors:</label>
     <div id='color-chart-value-sum'></div>
    </div>

   </div>



   <div style="float:center;padding:20px;">
    <div>
     <label>Number of ages:</label>
     <div id='age-chart-count'></div>
    </div>

    <div>
     <label>Value sum for ages:</label>
     <div id='age-chart-value-sum'></div>
    </div>

   </div>

     </div>
     
     
        <div style="font-family:arial;">
            <label for='value-chart'>Values:</label>
         <div id='value-chart'></div>
        </div>
     
 
  <script>
   
   let data = [                 

        {color: 'red', age: 1, value: 10},
        {color: 'red', age: 2, value: 11},
        {color: 'red', age: 3, value: 12},        

        {color: 'green', age: 1, value: 20},
        {color: 'green', age: 2, value: 21},
        {color: 'green', age: 3, value: 22},

        {color: 'blue', age: 1, value: 30},
        {color: 'blue', age: 2, value: 31},
        {color: 'blue', age: 3, value: 32},
      ];

            //create instance of cross filter
   let cf = crossfilter(data);    

            //define dimensions and groups
            let colorDim = cf.dimension(d=> d.color);
            let colorGroupCount = colorDim.group().reduceCount();
            let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);

            let ageDim = cf.dimension(d=> d.age);
            let ageGroupCount = ageDim.group().reduceCount();
            let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);

            let colorAgeDim = cf.dimension(d => [d.color, d.age]);


            function reduceAdd(previous, current) {

             if(current){
              if(current.value !== null){
               if(previous.sum === null){
                previous.sum = current.value;
                previous.count = 1;
               } else {
                previous.sum += current.value;
                previous.count += 1;

               }
              } 
             } 
             return previous;                     
   }

   function reduceRemove(previous, current) {     
    if(current){
              if(current.value !== null){
               if(previous.sum !== null){
                previous.sum -= current.value;
                previous.count -= 1;
                if(previous.count === 0){
                 previous.sum = null;
                }
               } 
              } 
             } 
             return previous;     
   }

   function reduceInit() {
     return { 
             sum: null,
             count: 0
            };
   }
                  
            let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);

            let ordinalColors =  ['red','green','blue'];    
            let ordinalAgeColors =  ['lightgray','grey','#666666'];                    

            //color charts

            let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);

            let colorChartCount = barChart('#color-chart-count')               
    .xAxisLabel('Color')             
    .x(d3.scaleBand().domain(ordinalColors))  
    .dimension(colorDim)  
    .yAxisLabel('Count')     
    .group(colorGroupCount)       
    .defineColors(rgbColorScale);    

   colorChartCount.yAxis().ticks(4);  
    

   barChart('#color-chart-value-sum')               
    .xAxisLabel('Color')                   
    .x(d3.scaleBand().domain(ordinalColors))  
    .dimension(colorDim)  
    .yAxisLabel('Value sum')    
    .group(colorGroupValueSum)
    .defineColors(rgbColorScale);    

            //age charts

            let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);

            /*
            dc.pieChart('#age-chart-count')
                .width(100)
    .height(100)
    .ordinalColors(ordinalAgeColors)
    .dimension(ageDim)
    .group(ageGroupCount);
   */

   let ageChartCount = barChart('#age-chart-count')              
    .xAxisLabel('Age')                    
    .x(d3.scaleLinear().domain([1,2,3]))  
    .dimension(ageDim)  
    .yAxisLabel('Count')    
    .group(ageGroupCount)   
    .defineColors(ageColorScale);

   ageChartCount.yAxis().ticks(4); 

   barChart('#age-chart-value-sum')               
    .xAxisLabel('Age')             
    .x(d3.scaleLinear().domain([1,2,3])) 
    .dimension(colorDim)  
    .yAxisLabel('Value sum')    
    .group(ageGroupValueSum)
    .defineColors(ageColorScale);

   function barChart(elementSelector){

    let barChart = dc.barChart(elementSelector)
     .width(200)
     .height(200)    
     .xUnits(dc.units.ordinal)     
     .margins({top:10,left:30,right:15,bottom:35}) 
     .barPadding(0.1)
     .outerPadding(0.1)     
     .transitionDuration(500);

     barChart.defineColors = function(colorScale){
      
      this.renderlet(chart=>{
          chart.selectAll('rect.bar')
            .each(function(d){

                                    let isSelected = this.classList.contains('selected');
                                    if(isSelected){
                                        d3.select(this).attr('style', 'fill: ' + colorScale(d.x) + ';stroke-width:2;stroke:#39ff14');
                                       
                                    } else {
                                     d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
                                    }

            
            });
         
         });
         return this;
     }

     return barChart;
   } 

             
             //value chart
             let chart = dc.seriesChart('#value-chart');

    chart
    .width(500)
    .height(500)
    .chart( c => 
           dc.lineChart(c)
           .renderDataPoints(true)
           .defined(d=>{
                if(d.y !== null){
                 return d.y;
                };
           })                 
            
    )    
    .xAxisLabel('Age')
    .x(d3.scaleLinear().domain([1,3]))
    .dimension(colorAgeDim)    
    .yAxisLabel('Value')
    .elasticY(true)
    .group(colorAgeGroup)     
    .brushOn(false) 
    .clipPadding(10)
    .mouseZoomable(true)
    .seriesAccessor(d => d.key[0])
    .keyAccessor(d => d.key[1])
    .valueAccessor(d => {
     return d.value.sum;
    })
    .ordinalColors(['blue','green','red','yellow'])
    .legend(dc.legend().x(430).y(350)); 
   
            
            dc.renderAll();

            //dimensions can also be used for filtering:
            
            //let color_red = colorDim.filter('red');           
            //let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
            //console.log(filterResult);

            //let functionFilter = ageDim.filter(age => age === 2);  
            //let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
            //console.log(functionFilterResult);


  </script>
 
 
 </body>

</html>

B. 第二个示例使用过滤器从组中删除空值。跳过缺失值并连接相邻点。

<html>

 <head>

  <title>dc demo</title>

  <meta http-equiv='content-type' content='text/html; charset=UTF8'>

  <!-- this demo is based on following tuturials:
     https://www.tutorialspoint.com/dcjs/dcjs_introduction_to_d3js.htm 
     https://www.codeproject.com/articles/693841/making-dashboards-with-dc-js-part-1-using-crossfil  
     -->

      



   <script src='https://d3js.org/d3.v4.min.js'></script>
   <script src='https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.5.2/crossfilter.min.js'></script>
   <script src='https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.min.js'></script>
   <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.8/dc.css" />

 </head>
 
 <body>
     
     <div style="font-family:arial;">

    <div style="float:left;padding:20px;">
    <div >
     <label>Number of colors:</label>
     <div id='color-chart-count'></div>
    </div>

    <div>
     <label>Value sum for colors:</label>
     <div id='color-chart-value-sum'></div>
    </div>

   </div>



   <div style="float:center;padding:20px;">
    <div>
     <label>Number of ages:</label>
     <div id='age-chart-count'></div>
    </div>

    <div>
     <label>Value sum for ages:</label>
     <div id='age-chart-value-sum'></div>
    </div>

   </div>

     </div>
     
     
        <div style="font-family:arial;">
            <label for='value-chart'>Values:</label>
         <div id='value-chart'></div>
        </div>
     
 
  <script>
   
   let data = [                 

        {color: 'red', age: 1, value: 10},
        {color: 'red', age: 2, value: 11},
        {color: 'red', age: 3, value: 12},        

        {color: 'green', age: 1, value: 20},
        {color: 'green', age: 2, value: 21},
        {color: 'green', age: 3, value: 22},

        {color: 'blue', age: 1, value: 30},
        {color: 'blue', age: 2, value: 31},
        {color: 'blue', age: 3, value: 32},
      ];

            //create instance of cross filter
   let cf = crossfilter(data);    

            //define dimensions and groups
            let colorDim = cf.dimension(d=> d.color);
            let colorGroupCount = colorDim.group().reduceCount();
            let colorGroupValueSum = colorDim.group().reduceSum(d => d.value);

            let ageDim = cf.dimension(d=> d.age);
            let ageGroupCount = ageDim.group().reduceCount();
            let ageGroupValueSum = ageDim.group().reduceSum(d => d.value);

            let colorAgeDim = cf.dimension(d => [d.color, d.age]);


            function reduceAdd(previous, current) {

             if(current){
              if(current.value !== null){
               if(previous.sum === null){
                previous.sum = current.value;
                previous.count = 1;
               } else {
                previous.sum += current.value;
                previous.count += 1;

               }
              } 
             } 
             return previous;                     
   }

   function reduceRemove(previous, current) {     
    if(current){
              if(current.value !== null){
               if(previous.sum !== null){
                previous.sum -= current.value;
                previous.count -= 1;
                if(previous.count === 0){
                 previous.sum = null;
                }
               } 
              } 
             } 
             return previous;     
   }

   function reduceInit() {
     return { 
             sum: null,
             count: 0
            };
   }
                  
            let colorAgeGroup = colorAgeDim.group().reduce(reduceAdd, reduceRemove, reduceInit);

            let filteredColorAgeGroup = removeMissingEntries(colorAgeGroup);

            function removeMissingEntries(sourceGroup) {
    return {
     all:function () {
      return sourceGroup.all().filter(function(d) {       
       return d.value.sum !== null; 
      });
     }
    };
   }

            let ordinalColors =  ['red','green','blue'];    
            let ordinalAgeColors =  ['lightgray','grey','#666666'];                    

            //color charts

            let rgbColorScale = d3.scaleOrdinal().domain(ordinalColors).range(ordinalColors);

            let colorChartCount = barChart('#color-chart-count')               
    .xAxisLabel('Color')             
    .x(d3.scaleBand().domain(ordinalColors))  
    .dimension(colorDim)  
    .yAxisLabel('Count')     
    .group(colorGroupCount)       
    .defineColors(rgbColorScale);    

   colorChartCount.yAxis().ticks(4);  
    

   barChart('#color-chart-value-sum')               
    .xAxisLabel('Color')                   
    .x(d3.scaleBand().domain(ordinalColors))  
    .dimension(colorDim)  
    .yAxisLabel('Value sum')    
    .group(colorGroupValueSum)
    .defineColors(rgbColorScale);    

            //age charts

            let ageColorScale = d3.scaleOrdinal().domain([1,2,3]).range(ordinalAgeColors);

            /*
            dc.pieChart('#age-chart-count')
                .width(100)
    .height(100)
    .ordinalColors(ordinalAgeColors)
    .dimension(ageDim)
    .group(ageGroupCount);
   */

   let ageChartCount = barChart('#age-chart-count')              
    .xAxisLabel('Age')                    
    .x(d3.scaleLinear().domain([1,2,3]))  
    .dimension(ageDim)  
    .yAxisLabel('Count')    
    .group(ageGroupCount)   
    .defineColors(ageColorScale);

   ageChartCount.yAxis().ticks(4); 

   barChart('#age-chart-value-sum')               
    .xAxisLabel('Age')             
    .x(d3.scaleLinear().domain([1,2,3])) 
    .dimension(colorDim)  
    .yAxisLabel('Value sum')    
    .group(ageGroupValueSum)
    .defineColors(ageColorScale);

   function barChart(elementSelector){

    let barChart = dc.barChart(elementSelector)
     .width(200)
     .height(200)    
     .xUnits(dc.units.ordinal)     
     .margins({top:10,left:30,right:15,bottom:35}) 
     .barPadding(0.1)
     .outerPadding(0.1)     
     .transitionDuration(500);

     barChart.defineColors = function(colorScale){
      
      this.renderlet(chart=>{
          chart.selectAll('rect.bar')
            .each(function(d){

                                    let isSelected = this.classList.contains('selected');
                                    if(isSelected){
                                        d3.select(this).attr('style', 'fill: ' + colorScale(d.x) + ';stroke-width:2;stroke:#39ff14');
                                       
                                    } else {
                                     d3.select(this).attr('style', 'fill: ' + colorScale(d.x));
                                    }

            
            });
         
         });
         return this;
     }

     return barChart;
   } 

             
             //value chart
             let chart = dc.seriesChart('#value-chart');

    chart
    .width(500)
    .height(500)
    .chart( c => 
           dc.lineChart(c)
           .renderDataPoints(true)
                          
            
    )    
    .xAxisLabel('Age')
    .x(d3.scaleLinear().domain([1,3]))
    .dimension(colorAgeDim)    
    .yAxisLabel('Value')
    .elasticY(true)
    .group(filteredColorAgeGroup)     
    .brushOn(false) 
    .clipPadding(10)
    .mouseZoomable(true)
    .seriesAccessor(d => d.key[0])
    .keyAccessor(d => d.key[1])
    .valueAccessor(d => {
     return d.value.sum;
    })
    .ordinalColors(['blue','green','red','yellow'])
    .legend(dc.legend().x(430).y(350)); 
   
            
            dc.renderAll();

            //dimensions can also be used for filtering:
            
            //let color_red = colorDim.filter('red');           
            //let filterResult = JSON.stringify(color_red.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
            //console.log(filterResult);

            //let functionFilter = ageDim.filter(age => age === 2);  
            //let functionFilterResult = JSON.stringify(functionFilter.top(Infinity)).replace('[','[\n\t').replace(/}\,/g,'},\n\t').replace(']','\n]');
            //console.log(functionFilterResult);


  </script>
 
 
 </body>

</html>