dc.js - 从单选按钮中选择减少功能 [英] dc.js - reduce function selected from radio button

查看:586
本文介绍了dc.js - 从单选按钮中选择减少功能的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在过去几个星期,我一直在dc.js玩很多,同时尝试创建一个简单的个人信息中心。



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



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



我发现这
< img src =https://i.stack.imgur.com/5KU9G.pngalt =使用动态值accessAccess函数(不工作)>



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



编辑:
为完成,这里是我的html代码:

 <!DOCTYPE html> 
< html>

< head>
< title>信息中心< / title>
< link rel =stylesheethref =./ static / css / bootstrap.min.css>
< link rel =stylesheethref =./ static / css / dc.css>
< link rel =stylesheethref =./ static / css / custom.css>
< / head>


< body class =application>

<! - 标题栏顶部 - >
< div class =navbar navbar-inverse navbar-fixed-toprole =navigation>
< div class =container-fluid>
< div class =navbar-header>
< a class =navbar-brandhref =./>信息中心< / a>
< / div>
< / div>
< / div>

<! - 图表容器页面 - >
< div class =container-fluid>

<! - 第一行图表(补偿左边的奇怪填充trbl) - >
< div class =rowstyle =width:100%; padding:0px 0px 0px 25px;>

<! - 控制面板 - >
< div class =col-sm-12>
< div class =chart-wrapper control-panel>
< div class =chart-title control-panel>
控制面板
< / div>
< div class =row>
< div class =col-sm-4>
< div class =chart-stage control-panelstyle =height:100px; border-right:1px solid#e2e2e2;>
< div class =text-centerstyle =padding:10px;>
<! - < div class =inner> - >
< strong>粒度:< / strong>
< select id =granularitystyle =margin-left:10px>< / select>
< div id =select-operationstyle =margin-top:15px;>
< strong>汇总:< / strong>
< label>< input type = radio name =operationvalue =sumchecked =checkedstyle =margin-left:10px>& nbsp; sum< / label>
< label>< input type = radio name =operationvalue =avg>& nbsp; average< / label>
< label>< input type = radio name =operationvalue =mode>& nbsp; mode< / label>
< label>< input type = radio name =operationvalue =min>& nbsp; min< / label>
< label>< input type = radio name =operationvalue =max>& nbsp; max< / label>
< / div>
< / div>
< / div>
< / div>
< div class =col-sm-4>
< div class =chart-stage control-panelstyle =height:100px; border-right:1px solid#e2e2e2;>
Test
< / div>
< / div>
< div class =col-sm-4>
< div class =chart-stage control-panelstyle =height:100px;>
Test
< / div>
< / div>
< / div>
< / div>
< / div>

<! - 堆栈图表及其范围图表作为单个引导网格 - >
< 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-chartstyle =width:100%;>
< a class =reset
href =javascript:stackChart.filterAll(); volumeChart.filterAll(); dc.redrawAll();
style =visibility:hidden; float:right; margin-right:15px;>
重设图表
< / a>
< span class ='reset'
style ='visibility:hidden; float:right; margin-right:15px; font-style:italic;'>
当前过滤器:< span class ='filter'>< / span>
< / span>
< div class =clearfix>< / div> <! - 使用IE的重置类时使用 - >
< / div>
< / div>
<! - 范围图 - >
< div class =row>
< div id =volume-chartstyle =width:100%;>< / div>
< p class =muted pull-rightstyle =margin-right:15px;>< i>选择要放大的时间范围< / i>
< / div>
< / div>
< / div>
< / div> <! - col-sm-12的结束网格 - >

< / div> <! - 第一行的结束 - >

< / div>
< / body>
< / html>


解决方案

我设法解决了减少功能。



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

  //自定义reduce函数
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的building里面,消费和产生的值访问器如下:

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

和此:

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

最后只需在valueAccessor中选择如下:

 选择模式为正确的valueAccessor值
var accessors = {
sum:{consPow:'conSum',prodPow:'prodSum'},
avg:{consPow:'consAvg',prodPow :'prodAvg'}
};

//在聚合模式下监听更改并更新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.rerawAll();
});

现在这对我问的问题有效,但如果你重用这个解决方案),请注意,这会引入其他问题:




  • 我无法访问/修改.st​​ack ...我只能设法添加新的层,直到现在...

  • 当鼠标悬停在图表中的一点上时,已消耗(基本层)的值是正确的但是生产(堆叠层)的错误(显示消耗的电力的值)。



还没有找到如何解决他们,我会打开另一个线程的情况下或后期的完整解决方案,如果我管理。希望这可以帮助别人。


In the last few weeks I have been playing a lot with dc.js while trying to create a simple personal dashboard.

I managed to implement a pop-up menu to select the time granularity I want to use for grouping the time dimension of my lineChart and thank to the help from the community I managed to boost performances drastically.

Now I am trying to dynamically change the type of aggregation I perform on my grouped data (sum, average, mode, min and max).

I found this example incredibly helpful but, nevertheless, I didn't quite managed to adapt it on my case and I don't manage to make it work. From my understanding, in this case, I just need to change the value accessor function and then re-draw. In fact, the valueAccessor determines the y-axis pixels positions, so that's the only part it should change. Instead, when I handle the change in group aggregation, I had to re-set the whole chart with the new groupings...

Now here is my code that results in nothing being printed at all with any radio button position (only sum and svg implemented).

If I remove the dynamic valueAccessor part, the default "sum" selection works properly.

Here is the code:

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

And here are a couple of screenshots of what it looks like when working and when not.

Thanks in advance, I have really no idea how to move forward since I don't even get any error from the console.

Edit: For completion, here is my html code:

<!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>

解决方案

I managed to fix the main issue that was on the reduce functions.

The solution is to just use these reduce functions:

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

Use a unique grouped dimension for "stackChart" and "volumeChart". like this:

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

Inside the "building" of the stackChart the value accessors for consumed and produced like this:

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

and this:

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

And finally just select in the 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();
    });

Now this works for the problem I asked, but if you are reusing this (that's why I posted the solution), please note that this introduces other issues:

  • I can't access/modify the value accessor of the ".stack" layer...I only managed to add new layer till now...
  • When hovering with the mouse over a point in the chart, the value for "consumed" (base layer) is correct but the one for the production (stacked layer) is wrong (it displays the value of the "consumed power").

I didn't figure out yet how to solve them, I will open another thread in case or post the full solution in the future if I manage. Hope this can help someone else.

这篇关于dc.js - 从单选按钮中选择减少功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆