javascript - dc.js - 从单选按钮中选择减少功能

标签 javascript jquery d3.js dc.js crossfilter

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

我成功地实现了一个弹出菜单来选择我想要用于对折线图的时间维度进行分组的时间粒度,并且感谢社区的帮助,我成功地大幅提高了性能。

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

我找到了这个example非常有帮助,但是,尽管如此,我并没有完全适应我的情况,也没有设法让它发挥作用。 根据我的理解,在这种情况下,我只需要更改值访问器函数,然后重新绘制即可。事实上,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();
        });

下面是一些工作时和不工作时的屏幕截图。 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>

最佳答案

我设法解决了有关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”层的值访问器...到目前为止我只能添加新层...
  • 当鼠标悬停在图表中的某一点上时,“消耗”(基础层)的值是正确的,但生产(堆叠层)的值是错误的(它显示“消耗的功率”的值) )。

我还不知道如何解决它们,如果我能解决,我会打开另一个线程以防万一,或者在将来发布完整的解决方案。希望这可以帮助其他人。

关于javascript - dc.js - 从单选按钮中选择减少功能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41684874/

相关文章:

javascript - 更改就绪处理程序上的 jQuery UI Accordion

jquery - 将所选文本缩进 4 个空格

backbone.js - 在 Jasmine 规范测试中触发 d3 事件

d3.js - 垂直对齐 nvd3 图例

javascript - 在传递给图像 src 之前检查 promise 是否已解决

javascript - 使用 Javascript 从 S3 下载

javascript - 在两个不同的 json 数组中合并相同的特定 json 对象

javascript - 错误 : 'jQuery is not defined'

Javascript 通过将鼠标悬停在按钮外部来更改按钮的颜色

javascript - D3 => NVD3.js 更新同一页面上的其他图表