2017-01-16 44 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