2017-01-16 40 views
1

在过去的几个星期里,我一直在尝试创建一个简单的个人仪表板,并一直在使用dc.js。dc.js - 减少从单选按钮中选择的功能

我设法实现了一个弹出式菜单,用于选择我想用于分组lineChart时间维度的时间粒度,并感谢社区的帮助,我设法大幅提升了演出效果。

现在我正在尝试动态更改我在分组数据(总和,平均,模式,最小和最大)上执行的聚合类型。

我发现这example非常有帮助,但是,尽管如此,我没有完全设法适应我的情况,我没有设法使其工作。 从我的理解,在这种情况下,我只需要更改值存取函数,然后重新绘制。实际上,valueAccessor决定了y轴的像素位置,所以这是它应该改变的唯一部分。 相反,当我处理群组聚合的变化时,我不得不用新的分组来重新设置整个图表...

现在,这里是我的代码,导致根本没有打印任何单选按钮位置(只有总和和svg实施)。

如果我删除动态valueAccessor部分,默认的“总和”选择正常工作。

下面是代码:

// Disable it or dash_reduceAvgAdd will give an error with ++p! 
//'use strict'; 


// TODO temp dirty workaround 
var selectedAggr = 'sum'; 


// ### Create Chart Objects 
// Create chart objects associated with the container elements identified by the css selector. 
// Note: It is often a good idea to have these objects accessible at the global scope so that they can be modified or 
// filtered by other page controls. 
var stackChart = dc.lineChart("#stack-chart"); 
var volumeChart = dc.barChart('#volume-chart'); 


// Asynchronously load the data and only when finished build the charts 
queue() 
    .defer(d3.json, "/data") 
    .await(makeGraphs); 


// Function to elaborate the data and build the charts 
function makeGraphs(error, recordsJson) { 

    // Clean data 
    var records = recordsJson; 

    // Works on d3-v4 only: var dateFormat = d3.timeFormat("%Y-%m-%d %H:%M:%S"); 
    //var dateFormat = d3.time.format("%Y-%m-%d %H:%M"); 
    console.log(Object.prototype.toString.call(records[0].date)); 

    // Coerce values to number and create javascript Date objects 
    records.forEach(function(d) { 
     d.date = new Date(+d.date); 
     d.prodPow = +d.prodPow; 
     d.consPow = +d.consPow; 
    }); 


    // Crossfilter instance 
    var ndx = crossfilter(records); 


    // Aggregation functions 
    // SUM mode 
    //function reduceAdd(attr) { return reduceSum(function (d) { return d[attr]; }); } 
    function dash_reduceSumAdd(attr) { return function (p, v) { return p + +v[attr]; }; } 
    function dash_reduceSumSub(attr) { return function (p, v) { return p - v[attr]; }; } 
    function dash_reduceInit() { return 0; } 

    // AVG mode 
    function dash_reduceAvgAdd(attr) { 
     return function (p, v) { 
      ++p.count; 
      p.sum += v[attr]; 
      p.avg = p.sum/p.count; 
      return p; 
     }; 
    } 
    function dash_reduceAvgSub(attr) { 
     return function (p, v) { 
      --p.count; 
      p.sum -= v[attr]; 
      p.avg = p.count ? p.sum/p.count : 0; 
      return p; 
     } 
    } 
    function dash_reduceAvgInit() { 
     return function() { 
      return {count:0, sum:0, avg:0}; 
     } 
    } 
    function valAccSum(d) { 
     return d.value; 
    } 
    function valAccAvg(d) { 
     return d.value.avg; 
    } 


    // Map selector to correct map-reduce functions 
    var aggregators = { 
     sum: [dash_reduceSumAdd, dash_reduceSumSub, dash_reduceInit, valAccSum], 
     avg: [dash_reduceAvgAdd, dash_reduceAvgSub, dash_reduceAvgInit, valAccAvg]//, 
     //mode: reduceMode, 
     //min: reduceMin, 
     //max: reduceMax 
    }; 


    // Granularities selectable values 
    var granularities = { 
     Hours: [d3.time.hour, d3.time.hours], 
     Days: [d3.time.day, d3.time.days], 
     Weeks: [d3.time.week, d3.time.weeks], 
     Months: [d3.time.month, d3.time.months], 
     Years: [d3.time.year, d3.time.years] 
    }; 

    // Assign default granularity 
    d3.select('#granularity').selectAll('option') 
     .data(Object.keys(granularities)) 
     .enter().append('option') 
     .text(function(d) { return d; }) 
     .attr('selected', function(d) { return d === 'Days' ? '' : null; }); 

    var dateDim, consPowByHour, prodPowByHour; 

    // Function to build the charts from the selected granularity 
    function setup(aggr) { 
     if (dateDim) { 
      dateDim.dispose(); 
      consPowByHour.dispose(); 
      prodPowByHour.dispose(); 
     } 
     var gran = granularities[d3.select('#granularity')[0][0].value]; 
     dateDim = ndx.dimension(function (d) { return gran[0](d.date); }); 
     consPowByHour = 
      dateDim 
       .group(function (d) { return gran[0](d); }) 
       .reduce(aggregators[aggr][0]('consPow'), aggregators[aggr][1]('consPow'), aggregators[aggr][2]); 
     //consPowByHour = dateDim.group(function (d) { return granularity[0](d); }).reduceSum(); 
     prodPowByHour = 
      dateDim 
       .group(function (d) { return gran[0](d); }) 
       .reduce(aggregators[aggr][0]('prodPow'), aggregators[aggr][1]('prodPow'), aggregators[aggr][2]); 

     // Min and max dates to be used in the charts 
     var minDate = gran[0](dateDim.bottom(1)[0]["date"]); 
     var maxDate = gran[0](dateDim.top(1)[0]["date"]); 

     // Charts customization 
     stackChart 
      .renderArea(true) 
      /* Make the chart as big as the bootstrap grid by not setting ".width(960)" */ 
      .height(350) 
      .transitionDuration(1500) 
      .margins({top: 30, right: 50, bottom: 25, left: 40}) 
      .dimension(dateDim) 
      /* Grouped data to represent and label to use in the legend */ 
      .group(consPowByHour, "Consumed Power [kW]") 
      /* Function to access grouped-data values in the chart */ 
      .valueAccessor(aggregators[aggr][2]) 
      /* x-axis range */ 
      .x(d3.time.scale().domain([minDate, maxDate])) 
      .xUnits(gran[1]) 
      /* Auto-adjust axis */ 
      .elasticY(true) 
      .renderHorizontalGridLines(true) 
      .legend(dc.legend().x(80).y(0).itemHeight(13).gap(5)) 
      /* When on, you can't visualize values, when off you can filter data */ 
      .brushOn(false) 
      /* Add another line to the chart; pass (i) group, (ii) legend label and (iii) value accessor */ 
      .stack(prodPowByHour, "Produced Power [kW]", aggregators[aggr][2]) 
      /* Range chart to link the brush extent of the range with the zoom focus of the current chart. */ 
      .rangeChart(volumeChart) 
      /* dc.js bug, this should be true by default to turn on visibility for reset class */ 
      .controlsUseVisibility(true) 
     ; 

     volumeChart//.width(990) 
      .height(60) 
      .margins({top: 0, right: 50, bottom: 20, left: 40}) 
      .dimension(dateDim) 
      .group(consPowByHour) 
      .centerBar(true) 
      .gap(1) 
      .x(d3.time.scale().domain([minDate, maxDate])) 
      .xUnits(gran[1]) 
      .elasticY(true) 
      .alwaysUseRounding(true) 
      /* dc.js bug, this avoids the reset and filter to remain after resetting using the brush/focus */ 
      .on('renderlet', function (chart) { 
       var rangeFilter = chart.filter(); 
       var focusFilter = chart.focusChart().filter(); 
       if (focusFilter && !rangeFilter) { 
        dc.events.trigger(function() { 
         chart.focusChart().replaceFilter(rangeFilter); 
        }); 
       } 
      }) 
     ; 
    } 

    // First time build charts 
    setup(selectedAggr); 

    // Render all graphs 
    dc.renderAll(); 

    // Listen for changes on granularities selection 
    d3.select('#granularity').on('change', function() { 
     setup(selectedAggr); 
     dc.redrawAll(); 
    }); 

    // Listen for changes on aggregation mode selection 
    d3.selectAll('#select-operation input') 
     .on('click', function() { 
      stackChart.valueAccessor(aggregators[this.value][3]); 
      selectedAggr = this.value; 
      //setup(this.value); 
      dc.redrawAll(); 
     }); 

这里有一对夫妇的它看起来什么截图的工作,在不喜欢的时候。 Working (sum mode) With the dynamic valueAccessor function (not working)

在此先感谢,我真的有不知道如何前进,因为我甚至不得到从控制台的任何错误。

编辑: 竣工,这里是我的html代码:

<!DOCTYPE html> 
<html> 

<head> 
    <title>Dashboard</title> 
    <link rel="stylesheet" href="./static/css/bootstrap.min.css"> 
    <link rel="stylesheet" href="./static/css/dc.css"> 
    <link rel="stylesheet" href="./static/css/custom.css"> 
</head> 


<body class="application"> 

<!-- Header bar on top --> 
<div class="navbar navbar-inverse navbar-fixed-top" role="navigation"> 
    <div class="container-fluid"> 
     <div class="navbar-header"> 
      <a class="navbar-brand" href="./">Dashboard</a> 
     </div> 
    </div> 
</div> 

<!-- Chart container page --> 
<div class="container-fluid"> 

    <!-- First row of charts (compensate on the left part the strange padding on right "trbl") --> 
    <div class="row" style="width:100%; padding: 0px 0px 0px 25px;"> 

     <!-- Control Panel --> 
     <div class="col-sm-12"> 
      <div class="chart-wrapper control-panel"> 
       <div class="chart-title control-panel"> 
        Control Panel 
       </div> 
       <div class="row"> 
        <div class="col-sm-4"> 
         <div class="chart-stage control-panel" style="height: 100px; border-right: 1px solid #e2e2e2;"> 
          <div class="text-center" style="padding: 10px;"> 
          <!--<div class="inner">--> 
           <strong>Granularity:</strong> 
           <select id="granularity" style="margin-left: 10px"></select> 
           <div id="select-operation" style="margin-top: 15px;"> 
            <strong>Aggregation:</strong> 
            <label><input type=radio name="operation" value="sum" checked="checked" style="margin-left: 10px">&nbsp;sum</label> 
            <label><input type=radio name="operation" value="avg">&nbsp;average</label> 
            <label><input type=radio name="operation" value="mode">&nbsp;mode</label> 
            <label><input type=radio name="operation" value="min">&nbsp;min</label> 
            <label><input type=radio name="operation" value="max">&nbsp;max</label> 
           </div> 
          </div> 
         </div> 
        </div> 
        <div class="col-sm-4"> 
         <div class="chart-stage control-panel" style="height: 100px; border-right: 1px solid #e2e2e2;"> 
          Test 
         </div> 
        </div> 
        <div class="col-sm-4"> 
         <div class="chart-stage control-panel" style="height: 100px;"> 
          Test 
         </div> 
        </div> 
       </div> 
      </div> 
     </div> 

     <!-- Stack Chart and its Range Chart as a single bootstrap grid --> 
     <div class="col-sm-12"> 
      <div class="chart-wrapper"> 
       <div class="chart-title"> 
        Stack Chart 
       </div> 
       <div class="chart-stage"> 
        <!-- Stack Chart --> 
        <div class="row"> 
          <div id="stack-chart" style="width:100%;"> 
           <a class="reset" 
           href="javascript:stackChart.filterAll();volumeChart.filterAll();dc.redrawAll();" 
           style="visibility: hidden; float: right; margin-right: 15px;"> 
            reset chart 
           </a> 
           <span class='reset' 
            style='visibility: hidden; float: right; margin-right: 15px; font-style: italic;'> 
            Current filter: <span class='filter'></span> 
           </span> 
           <div class="clearfix"></div> <!-- Use it when using the reset class for IE --> 
          </div> 
        </div> 
        <!-- Range Chart --> 
        <div class="row"> 
         <div id="volume-chart" style="width:100%;"></div> 
         <p class="muted pull-right" style="margin-right: 15px;"><i>select a time range to zoom in</i></p> 
        </div> 
       </div> 
      </div> 
     </div> <!-- End of "col-sm-12" grid --> 

    </div> <!-- End of first row --> 

</div> 
</body> 
</html> 
+0

好吧,我想我找到了我的主要问题,我很快就会发布解决方案。简而言之,reduce函数(add,sub,init)可以封装我在执行group()。reduce()时要计算的所有值,因此我可以只使用一个来计算avg和sum。然后在valueAccessor中,我只需要指定我想要的那个(p.value.avg或p.value.sum)。现在看起来很简单... – Bertone

回答

1

我设法解决的主要问题,这是在降低的功能。

的解决方法就是使用这些功能减少:

// Custom reduce functions 
function dash_reduceAdd(p, v) { 
    ++p.count; 
    p.conSum += v.consPow; 
    p.prodSum += v.prodPow; 
    p.consAvg = p.conSum/p.count; 
    p.prodAvg = p.prodSum/p.count; 
    return p; 
} 
function dash_reduceSub(p, v) { 
    --p.count; 
    p.conSum -= v.consPow; 
    p.prodSum -= v.prodPow; 
    p.consAvg = p.count ? p.conSum/p.count : 0; 
    p.prodAvg = p.count ? p.prodSum/p.count : 0; 
    return p; 
} 
function dash_reduceInit() { 
    return { count:0, conSum:0, prodSum:0, consAvg:0, prodAvg:0 }; 
} 

使用独特的分组维度“stackChart”和“volumeChart”。像这样:

powByTime = 
     dateDim 
      .group(function (d) { return gran[0](d); }) 
      .reduce(dash_reduceAdd, dash_reduceSub, dash_reduceInit); 

里面的stackChart的 “建设” 的价值存取的消耗和产生这样的:

stackChart.valueAccessor(function(d) { return d.value.conSum; }); 

这:

stackChart.stack(powByTime, "Produced Power [kW]", function(d) { return d.value.prodSum; }) 

最后只选择valueAccessor like this:

// Map the selected mode to the correct valueAccessor value 
var accessors = { 
    sum: {consPow: 'conSum', prodPow: 'prodSum'}, 
    avg: {consPow: 'consAvg', prodPow: 'prodAvg'} 
}; 

// Listen for changes on the aggregation mode and update the valueAccessor 
d3.selectAll('#select-operation input') 
    .on('click', function() { 
     var aggrMode = this.value; 
     stackChart.valueAccessor(function(d) { var sel = accessors[aggrMode]['consPow']; return d.value[sel]; }); 
     dc.redrawAll(); 
    }); 

现在,这个工程我问这个问题,但如果你正在重用此(这就是为什么我张贴的解决方案),请注意,这引入了其他问题:

  • 我不能访问/修改值访问“.stack”图层...我只设法添加新图层直到现在...
  • 当鼠标悬停在图表中某点上时,“消耗”(基本图层)的值是正确的,但是生产层(堆叠层)是错误的(它显示“消耗功率”的值)。

我还没有弄清楚如何解决它们,如果我管理的话,我会打开另一个线程以防万一或者将来发布完整的解决方案。希望这可以帮助别人。

+0

为了完整起见,我通过为图表定义完整的标题函数来克服鼠标悬停的问题,如下所示:var dateFormat = d3.time.format(“%a%d-% m-%Y%H:%M“); function powTitle(p){ return'Date:'+ dateFormat(p.key)+'\ n' +'消耗:'+ p.value.conSum +'\ n' +'消耗(平均):' '+ p.value.consAvg +'\ n' +'制作:'+ p.value.prodSum +'\ n' +'制作(平均):'+ p.value.prodAvg; }'。我认为问题在于悬停在两个图层上都使用相同的“p.value”。 – Bertone

+0

对于接下来的任何人,这里是博通的后续问题:http://stackoverflow.com/q/41700431/676195 – Gordon