dc.js – 减少从单选按钮中选择的function

在过去的几周里,我在尝试创建一个简单的个人仪表板时,一直在玩dc.js。

我设法实现了一个弹出菜单来选择我想用来分组lineChart的时间维度的时间粒度,并感谢社区的帮助,我设法大大提高了性能。

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

我发现这个例子非常有帮助,但是,尽管如此,我并没有完全适应我的情况而且我无法使其工作。 根据我的理解,在这种情况下,我只需要更改值访问器function然后重新绘制。 实际上,valueAccessor确定了y轴像素的位置,因此它是唯一应该改变的部分。 相反,当我处理组聚合的变化时,我不得不用新的分组重新设置整个图表……

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

如果我删除动态valueAccessor部分,默认的“sum”选项可以正常工作。

这是代码:

// 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(); }); 

以下是工作时和未工作时的截图。 工作(总和模式) 使用动态valueAccessor函数(不工作)

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

编辑:完成后,这是我的HTML代码:

    Dashboard         
Control Panel
<!--
--> Granularity:
Aggregation:
Test
Test
Stack Chart
reset chart Current filter:

select a time range to zoom in

我设法修复了reduce函数的主要问题。

解决方案是使用这些reduce函数:

 // 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中选择如下:

 // 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(); }); 

现在这适用于我问的问题,但是如果你重复使用它(这就是我发布解决方案的原因),请注意这会引入其他问题:

  • 我无法访问/修改“.st​​ack”图层的值访问器…我只设法添加新图层到现在为止…
  • 当用鼠标hover在图表中的某个点上时,“消耗”(基础层)的值是正确的,但生产(堆叠层)的值是错误的(它显示“消耗功率”的值)。

我还没弄清楚如何解决这些问题,如果我管理的话,我会打开另一个案例,或者在将来发布完整的解决方案。 希望这可以帮助别人。

Interesting Posts