2017-01-16 7 views
1

에서 선택한 기능 감소 지난 몇 주 동안 간단한 개인 대시 보드를 만드는 동안 dc.js를 많이 사용했습니다.dc.js - 라디오 버튼

필자는 lineChart의 시간 차원을 그룹화하기 위해 사용하고자하는 시간 세분성을 선택하기 위해 팝업 메뉴를 구현했으며 커뮤니티의 도움을 받아 성능을 대폭 향상 시켰습니다.

이제 그룹화 된 데이터 (합계, 평균, 모드, 최소 및 최대)에서 수행하는 집계 유형을 동적으로 변경하려고합니다.

나는이 example을 매우 유용하게 찾았지만, 그럼에도 불구하고 나는 그것을 내 경우에 맞게 조정하지 못했고 제대로 작동시키지 못했다. 내 이해에서,이 경우에는 값 접근 자 함수를 변경 한 다음 다시 그려야합니다. 실제로 valueAccessor는 y 축 픽셀 위치를 결정하므로 변경해야하는 부분이 있습니다. 대신 그룹 집계에서 변경 사항을 처리 할 때 새로운 그룹으로 전체 차트를 다시 설정해야했습니다 ...

이제 라디오 버튼 위치가 전혀 인쇄되지 않는 코드가 있습니다. (합계 및 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(); 
     }); 

그리고 여기이하지 않을 때 작업 때처럼 그 모습의 스크린 샷의 커플 : 여기

는 코드입니다. 사전에 Working (sum mode) With the dynamic valueAccessor function (not working)

덕분에, 나는 심지어는 콘솔에서 어떤 오류가 발생하지 않기 때문에 앞으로 이동하는 방법을 정말 아무 생각이 없습니다.

편집 :

<!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()를 수행 할 때 계산하고자하는 모든 값을 캡슐화 할 수 있으므로 각각에 대해 하나씩 평균 및 합계를 계산할 수 있습니다. valueAccessor에서 원하는 것을 지정하기 만하면됩니다 (p.value.avg 또는 p.value.sum). 이제는 직설적 인 것 같습니다 ... – Bertone

답변

1

내가 감소 기능에 있던 주요 문제를 해결하기 위해 관리 : 완료, 여기 내 html 코드이다.

// 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"에 대한 고유의 그룹화 차원을 사용

이 솔루션은 이러한 기능을 감소 사용하는 것입니다.이 같은 :

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

을이 : 그냥 마지막으로

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

그리고에서 선택 stackChart의 "건물"내부

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

이 같은 소비와 생산을위한 가치 접근 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(); 
    }); 

지금이 내가 묻는 문제에 대한 작동하지만, 당신이 (필자는 솔루션을 게시 그 이유는)이를 재사용하는 경우, 이것은 다른 문제 소개주의하십시오 :

  • 내가하지 액세스/값 접근을 수정할 수 있습니다 ".stack"레이어의 ... 지금까지 새 레이어를 추가 할 수있었습니다.
  • 차트의 한 지점 위로 마우스를 가져 가면 "소비 된"(기본 레이어) 값은 정확하지만 생산 (스택 레이어)에 대한 것이 잘못되었습니다 ("소비 전력"의 값을 표시합니다).

아직 해결 방법을 찾지 못했지만 나중에 해결할 경우 다른 스레드를 열거 나 관리 할 경우 전체 솔루션을 게시합니다. 희망이 다른 사람을 도울 수 있습니다.

+0

완전성을 위해, 차트의 완전한 제목 기능을 정의하여 마우스 호버링 문제를 극복했습니다 :'var dateFormat = d3.time.format ("% a % d- % % Y % H : % M "); 함수 powTitle (p) { 창 '날짜'+ dateFormat는 (p.key) + '\ n'+ '소비'+ p.value.conSum + '\ n'+ '소비 (평균) : '+ p.value.consAvg +'\ n ' +'생성 : '+ p.value.prodSum +'\ n ' +'생산 된 (avg) : '+ p.value.prodAvg; }'. 나는 호버가 양쪽 레이어에 대해 동일한 "p.value"를 사용한다는 것이 문제라고 생각한다. – Bertone

+0

다음은 Bertone의 후속 질문입니다. http://stackoverflow.com/q/41700431/676195 – Gordon