在下拉列表更改时刷 extent/width(dc.js、moment.js、d3.js、crossfilter)

Brush extent/width on dropdown change (dc.js,moment.js,d3.js ,crossfilter)

我必须创建一个具有来自下拉列表的固定范围值的画笔(3、6、12、24 小时)。尝试获得画笔的影响作为画笔范围 http://jsfiddle.net/9yccpjbu/, but instead of buttons to use the drop-down (like here http://jsfiddle.net/gordonwoodhull/400wd2nd/16/)。 也欢迎对按钮进行修复。

下拉菜单

1) 下拉列表仅在第一次单击时更改显示的堆栈(条:g.chart-正文- g.stack),到显示来自下拉列表的正确值:

当从下拉列表中单击另一个值时,它会添加白色的值而不渲染堆栈(在图片中,从下拉列表中选择 6(灰色条)而不是 3(白色)的结果:

2) 点击后下拉笔刷范围(rect.extent)与
断开连接 g.chart-正文- g.stack
。 (图中红框):

.

在不点击下拉菜单的情况下,从 timeSlider 中选择画笔效果很好,看起来像这样:

3) 点击下拉后"Reset All"link中显示的选中记录数为0(移动rect.extent堆栈显示所选记录的数量)。

4) "Reset All" link 不重置 图形** 并显示下拉菜单选择的项目数

按钮:

4) 单击时,它们会显示预期的 g.chart-body- g.stack 和维度上的互补 rect.extent (stack+rect.extent= dimesion) :

当我从

更改范围时
filterDimension.filterRange([start, start.add('hours,amountHours).hours()]) 

filterDimension.filterRange([start.add('hours', amountHours).hours(), end])

g.chart-body- g.stack 和 rect.extent 连接在一起 - 但在范围的互补范围内,我需要显示来自下拉列表的画笔范围(我需要的范围(灰色)+ 显示的范围(蓝色带红色边框)= 域:

5) "Reset All" link 似乎重置了图表,但在单击 "Reset all" 时显示了错误的数字。

我的fiddle:https://jsfiddle.net/dani2011/upa3eowb/

JAVASCRIPT

            'use strict';
            // chart objects 
            var bitChart = dc.compositeChart("#bitrate-move-chart");//Before dynamic Y-Axis nonzero_min used var bitChart = dc.lineChart('#bitrate-move-chart');
            var bitChart2 = dc.compositeChart("#bitrate-move-chart2");//Before dynamic Y-Axis nonzero_min used var bitChart = dc.lineChart('#bitrate-move-chart');
            var timeSlider = dc.barChart('#bitrate-timeSlider-chart');
            var bitCount = dc.dataCount('.dc-data-count');
            var bitrateTable = dc.dataTable('.dc-data-table');

            //Creating dynamic Y axis with min/max ticks' values depending on min/max of data - copied from http://jsfiddle.net/gordonwoodhull/7anae5c5/1/...

            // 15 Min Interval - copied from https://github.com/mbostock/d3/blob/master/src/time/interval.js.....

            // generalization of d3.time.minute copied from- https://github.com/mbostock/d3/blob/master/src/time/minute.js....

            //bitchart
            var min15 = n_minutes_interval(15);
            //bitchart_2
            var min15_2 = n_minutes_interval(15);
            //timeSlider
            var min15_3 = n_minutes_interval(15);

            //### Load  data 
            //var data = d3.csv.parse(d3.select("pre#data").text());
            d3.csv('CHANNEL_CLUSTER_BITRATE_takeThis.csv', function (data) {
                // Format CSV data
                var dateFormat = d3.time.format('%Y/%m/%d/%H:%M');
                var numberFormat = d3.format('.2');
                data.forEach(function (d) {
                    d.bitdate = new Date(d.DATETIME);    //d.DATETIME = dateFormat.parse(d.DATETIME);
                    d.hours = d3.time.hours(d.bitdate);
                    d.BITRATE = +d.BITRATE.match(/\d+/); //d.BITRATE = +d.BITRATE; 
                });

                //### Crossfilter Dimensions 
                var crossFilteredData = crossfilter(data);
                var all = crossFilteredData.groupAll();
                // Dimension by full date
                //bitChart
                var dateDimension = crossFilteredData.dimension(function (d) {
                    return d.bitdate;
                });
                //bitChart2
                var dateDimension2 = crossFilteredData.dimension(function (d) {
                    return d.bitdate;
                });
                //timeSlider
                var dateDimension3 = crossFilteredData.dimension(function (d) {
                    return d.bitdate;
                });
                //dropDown
                var filterDimension = crossFilteredData.dimension(function (d) {
                    return d.bitdate;
                });

                //### Crossfiltered Groups
                //timeSlider
                var minIntervalWeekBitrateGroup3 = dateDimension3.group(min15_3).reduceSum(function (d) {
                    return 10 //  +d.BITRATE
                });

                //Group bitrate per week, 15 minInterval - maintain running tallies 
                //bitChart
                var bitrateWeekMinIntervalGroupMove = dateDimension.group(min15).reduce(
                   /* callback for when data is added to the current filter results */
                   function (p, v) {
                       ++p.count;
                       p.BITRATE = +v.BITRATE;
                       p.total += +v.BITRATE;
                       p.avg = p.count ? Math.round(p.total / p.count) : 0;
                       return p;
                   },
                   /* callback for when data is removed from the current filter results */
                   function (p, v) {
                       --p.count;
                       p.BITRATE = +v.BITRATE;
                       p.total -= +v.BITRATE;
                       p.avg = p.count ? Math.round(p.total / p.count) : 0;
                       return p;
                   },
                   /* initialize p */
                   function () {
                       return {
                           count: 0,
                           bitrate: 0,
                           total: 0,
                           avg: 0
                       };
                   }
                );

                //bitChart2
                var bitrateWeekMinIntervalGroupMove2 = dateDimension2.group(min15_2).reduce(
                  /* callback for when data is added to the current filter results */
                  function (p, v) {
                      ++p.count;
                      p.BITRATE = +v.BITRATE;
                      p.total += +v.BITRATE;
                      p.avg = p.count ? Math.round(p.total / p.count) : 0;
                      return p;
                  },
                  /* callback for when data is removed from the current filter results */
                  function (p, v) {
                      --p.count;
                      p.BITRATE = +v.BITRATE;
                      p.total -= +v.BITRATE;
                      p.avg = p.count ? Math.round(p.total / p.count) : 0;
                      return p;
                  },
                  /* initialize p */
                  function () {
                      return {
                          count: 0,
                          bitrate: 0,
                          total: 0,
                          avg: 0
                      };
                  }
               );

                //domain limits
                var minDate = dateDimension.bottom(1)[0].DATETIME;
                var maxDate = dateDimension.top(1)[0].DATETIME;
                var start = moment(new Date(minDate));
                var end = moment(new Date(maxDate)); 
                //max line
                var maxbit = d3.max(data, function (d) { return  
                +d["BITRATE"]; }); 

                //dropdown / buttons copied from http://jsfiddle.net/gordonwoodhull/400wd2nd/16/ , http://jsfiddle.net/9yccpjbu/
                var btns = d3.select(".buttons-container").selectAll("button").data(["3 Hours", "6 Hours", "12 Hours", "24 Hours"]);
                    btns = btns.enter().append("button")
                   .attr("class", "btn btn-sm btn-success")
                    // fill the buttons with the year from the data assigned to them
                    btns.each(function (d) {
                        this.innerText = d;
                    })
                    btns.on("click", drawBrush)

              function drawBrush() {
                    if (this.innerText === "Brush Extent") {  }
                    if (this.innerText === "3 Hours") { addHours(3);  }
                    if (this.innerText === "6 Hours") { addHours(6); }
                    if (this.innerText === "12 Hours") { addHours(12); }
                    if (this.innerText === "24 Hours") { addHours(24); }
                    timeSlider.filter(null);
                    timeSlider.filter(dc.filters.RangedFilter( start, end);
                   // timeSlider.x.domain(brush.empty() ? 
                    timeSlider.x.domain() : brush.extent());
                    dc.redrawAll();
              }

                function addHours(amountHours) {

                    filterDimension.filterRange([start, start.add('hours', amountHours).hours()]);
                    //filterDimension.filterRange([start.add('hours', amountHours).hours(), end]);
                    dc.redrawAll();
                }

                function brushed() {
                    timeSlider.x.domain(brush.empty()) ?    
                    timeSlider.x.domain() : brush.extent());
                }
                function fixed_now() {
                    return new Date(minDate)
                }

                d3.select('#hoursDropDown').on('change', function() {
                   filterDimension.filterRange([start, start.add(this.value,  
                   'hours').hours()]);
                    dc.redrawAll();
                    timeSlider.filter(null);//filterAll()
                    timeSlider.filter(dc.filters.RangedFilter(new 
                    Date(start), new Date(end)));
                    dc.redrawAll();

                  //  timeSlider.x.domain(brush.empty() ? 
                  //  timeSlider.x.domain() : brush.extent());

                    //var start = moment(new Date(minDate));
                   // var end = moment(new Date(maxDate));
                   // filterDimension.filterRange([start,  
                   //start.add(this.value, 'hours').hours()]);
                  //  dc.redrawAll();

                });

                //###Graphs
                bitChart /* dc.lineChart('#bitrate-move-chart', 'chartGroup') */
                    .xUnits(min15.range)  //.xUnits(d3.time.weeks)//.round(d3.time.week) //.round(d3.time.minute)//d3.time.month.round)
                    .x(d3.time.scale().domain([new Date(minDate), new Date(maxDate)]))
                    .yAxisPadding('5%')     
                    .elasticY(true)
                   //Specify a "range chart" to link its brush extent with the zoom of the current "focus chart".
                    .rangeChart(timeSlider)
                    .width(450)
                    .height(200)
                    .transitionDuration(500)
                    .margins({ top: 30, right: 50, bottom: 25, left: 50, padding: 1 })
                    .mouseZoomable(true)
                    .brushOn(false)
                    .renderHorizontalGridLines(true)
                    .legend(dc.legend().x(800).y(10).itemHeight(13).gap(5))
                    //Render max bitrate horizontal line copied from bar-extra-line.html
                     .yAxisLabel("Total Bitrate per 15 minutes")
                    .renderlet(function (chart) {
                        chart.svg().selectAll('.chart-body').attr('clip-path', null)
                    })
                    .on('renderlet', function (chart) {
                        var left_y = 10, right_y = 70; // use real statistics here!
                        var extra_data = [{ x: chart.x().range()[0], y: chart.y()(left_y) }, { x: chart.x().range()[1], y: chart.y()(right_y) }];
                        var line = d3.svg.line()
                            .x(function (d) { return d.x; })
                            .y(function (d) { return maxbit; })
                            .interpolate('linear');
                        var chartBody = chart.select('g.chart-body');
                        var path = chartBody.selectAll('path.extra').data([extra_data]);
                        path.enter().append('path').attr({
                            class: 'extra',
                            stroke: 'red',
                            id: 'extra-line'
                        });
                        path.attr('d', line);
                        // Label the max line
                        var text = chartBody.selectAll('text.extra-label').data([0]);
                        text.enter().append('text')
                                .attr('text-anchor', 'middle')
                            .append('textPath').attr({
                                class: 'extra-label',
                                'xlink:href': '#extra-line',
                                startOffset: '50%'
                            })
                            .text('Total Bitrate Max Value');      
                    })
                    // .ordinalColors('red')
                   // Title can be called by any stack layer.
                    .title(function (d) {
                        var value = d.value.total ? d.value.total : d.value;
                        if (isNaN(value)) {
                            value = 0;
                        }
                        return dateFormat(d.key) + ' \n Total Bit:' + numberFormat(value)
                    })

                  //Creating dynamic Y axis with min max ticks' values depending on min max of data - copied from http://jsfiddle.net/gordonwoodhull/7anae5c5/1/
                 .compose([
                  nonzero_min(dc.lineChart(bitChart)
                      .dimension(min15)
                       .colors('blue')
                       .group(bitrateWeekMinIntervalGroupMove, 'Bitrate Total')

                       .valueAccessor(function (d) {
                            return d.value.total;
                        })
                     // .dashStyle([2,2])
                     .interpolate('step-after')
                      .renderArea(false)
                      .brushOn(false)
                      .renderDataPoints(false)         
                      .clipPadding(10)),
                 ])
                bitChart.render();

                //bitchart2
                bitChart2 /* dc.lineChart('#bitrate-move-chart', 'chartGroup') */
                   .xUnits(min15_2.range)  //.xUnits(d3.time.weeks)//.round(d3.time.week) //.round(d3.time.minute)//d3.time.month.round)
                   .x(d3.time.scale().domain([new Date(minDate), new Date(maxDate)]))
                   .yAxisPadding('5%')
                   .elasticY(true)
                  //Specify a "range chart" to link its brush extent with the zoom of the current "focus chart".
                   .rangeChart(timeSlider)
                   .width(450)
                   .height(200)
                   .transitionDuration(500)
                   .margins({ top: 30, right: 50, bottom: 25, left: 50, padding: 1 })
                   .mouseZoomable(true)
                   .brushOn(false)
                   .renderHorizontalGridLines(true)
                   .legend(dc.legend().x(800).y(10).itemHeight(13).gap(5))
                   //Render max bitrate horizontal line copied from bar-extra-line.html
                    .yAxisLabel("Total Bitrate per 15 minutes")
                   .renderlet(function (chart) {
                       chart.svg().selectAll('.chart-body').attr('clip-path', null)
                   })
                   .on('renderlet', function (chart) {
                       var left_y = 10, right_y = 70; // use real statistics here!
                       var extra_data = [{ x: chart.x().range()[0], y: chart.y()(left_y) }, { x: chart.x().range()[1], y: chart.y()(right_y) }];
                       var line = d3.svg.line()
                           .x(function (d) { return d.x; })
                           .y(function (d) { return maxbit; })
                           .interpolate('linear');
                       var chartBody = chart.select('g.chart-body');
                       var path = chartBody.selectAll('path.extra').data([extra_data]);
                       path.enter().append('path').attr({
                           class: 'extra',
                           stroke: 'red',
                           id: 'extra-line'
                       });
                       path.attr('d', line);
                       // Label the max line
                       var text = chartBody.selectAll('text.extra-label').data([0]);
                       text.enter().append('text')
                       .attr('text-anchor', 'middle')
                       .append('textPath').attr({
                               class: 'extra-label',
                               'xlink:href': '#extra-line',
                               startOffset: '50%'
                           })
                           .text('Total Bitrate Max Value');
                   })
                   // .ordinalColors('red')
                  // Title can be called by any stack layer.
                   .title(function (d) {
                       var value = d.value.total ? d.value.total : d.value;
                       if (isNaN(value)) {
                           value = 0;
                       }
                       return dateFormat(d.key) + ' \n Total Bit:' + numberFormat(value)
                   })

                 //Creating dynamic Y axis with min max ticks' values depending on min max of data - copied from http://jsfiddle.net/gordonwoodhull/7anae5c5/1/
                .compose([
                 nonzero_min(dc.lineChart(bitChart2)
                     .dimension(min15_2)
                      .colors('blue')
                      .group(bitrateWeekMinIntervalGroupMove2, 'Bitrate Total')
                      .valueAccessor(function (d) {
                          return d.value.total;
                      })
                     //.dashStyle([2,2])
                    .interpolate('step-after')
                     .renderArea(false)
                     .brushOn(false)
                     .renderDataPoints(false)
                     .clipPadding(10)),
                ])
                bitChart2.render();

                //#### Range Chart
                // Since this bar chart is specified as "range chart" for the area chart, its brush extent will always match the zoom of the area chart.
                timeSlider
                   .dimension(dateDimension3)//.dimension(min15)//.dimension(weekDim)//   //
                    .group(minIntervalWeekBitrateGroup3)
                    // .x(d3.time.scale().range([0, brushContainer.select("rect").attr("width")]).domain([new Date(dateDimension3.bottom(1)[0].DATETIME), new Date(dateDimension3.top(1)[0].DATETIME)]))
                    .x(d3.time.scale().domain([new Date(dateDimension3.bottom(1)[0].DATETIME), new Date(dateDimension3.top(1)[0].DATETIME)]))
                    .round(dc.round.floor) //(d3.time.month.round)
                    .xUnits(min15_3.range)//.xUnits(d3.time.week) //.xUnits(d3.time.minute) //.xUnits(d3.time.months)
                    .width(990) /* dc.barChart('#bitrate-timeSlider-chart', 'chartGroup'); */
                    .height(40)
                    .margins({ top: 0, right: 50, bottom: 20, left: 40 })
                   // .centerBar(true)
                    .gap(1)
                    .mouseZoomable(true)
                //#### Data Count  dateformat.parse(d.time);
                bitCount /* dc.dataCount('.dc-data-count', 'chartGroup'); */
                    .dimension(crossFilteredData)
                    .group(all)
                    .html({
                        some: '<strong>%filter-count</strong> records selected out of <strong>%total-count</strong> records'  +
                            ' | <a href=\'javascript:dc.filterAll(); dc.renderAll();\'>Reset All</a>',
                        all: ' All records selected. Please click on the graph to apply filters.'
                    });
                //#### Data Table
                bitrateTable /* dc.dataTable('.dc-data-table', 'chartGroup') */
                    .dimension(dateDimension)    //  .dimension(dateDimension)
                    // Data table does not use crossfilter group but rather a closure as a grouping function
                    .group(function (d) {
                        var format = d3.format('02d');
                        return d.bitdate.getFullYear() + '/' + format((d.bitdate.getMonth() + 1));
                    })
                    .sortBy(function (d) { return d.bitdate; })
                    // (_optional_) max number of records to be shown, `default = 25`
                    .size(13)
                    .columns([
                        'DATETIME',
                        'CHANNEL_ID',
                        'BITRATE'
                    ])
                    // (_optional_) custom renderlet to post-process chart using [D3](http://d3js.org)
                    .on('renderlet', function (table) {
                        table.selectAll('.dc-table-group').classed('info', true);
                    });
                //#### Rendering
                //Render all charts on the page
                dc.renderAll();


                //#### Versions
                //Determine the current version of dc with `dc.version`
                d3.selectAll('#version').text(dc.version);
                // Determine latest stable version in the repo via Github API
                d3.json('https://api.github.com/repos/dc-js/dc.js/releases/latest', function (error, latestRelease) {
                    /*jshint camelcase: false */
                    d3.selectAll('#latest').text(latestRelease.tag_name); /* jscs:disable */
                });

            });

            //d3.select('#myDropDown2').on('change', function () {
            //    var nd = new Date(minDate);
            //    nd.setDate(nd.getDate() + +this.value);
            // var start1 = moment(new Date(d.key));
            //var nd = new Date(minDate);
            //nd.setDate(nd.getDate());
            //    filterDimension.filterRange([nd, nd.setDate(nd.getDate() + +this.value)]);
            //    dc.redrawAll();
            //});

            //var brushContainer = d3.select("svg");
            //alert( brushContainer.select("rect").attr("width"));
            // start.max(new Date(minDate), new Date(maxDate));
            // moment.max(start, end);
            //if ((start.add(this.value, 'hours').hours()).getTime()>= end)
            //{
            //    filterDimension.filterRange([start, end]);
            //}
            //else
            //{
            //filterDimension.filterRange([start, start.add(this.value, 'hours').hours()]);
            //}
            // timeSlider.filter(null);
            //d3.select("svg").select("rect").enter().append('rect').attr({  width: ''+ this.value +''  });
            //  timeSlider.x.domain(brush.empty() ? timeSlider.x.domain() : brush.extent(0,this.value));
            //  focus.select("timeSlider.area").attr("d", area);
            // focus.select("timeSlider.x.axis").call(xAxis);
            // }

            // dc.renderAll();

任何帮助将不胜感激!

这里有很多多余的代码。如果某些东西不起作用,您应该在尝试下一步之前真正将其删除。 :)

所以,这真的很简单。

首先,您通常应该通过图表设置过滤器,而不是直接在交叉过滤器维度上设置过滤器。 Crossfilter 不提供任何 getter,因此需要告知图表在何处显示画笔。并且只有在没有显示时间过滤器的图表时才需要单独的 filterDimension。设置这个额外的维度意味着当图表被重置时,仍然有额外的过滤发生,所以重置不会发生。

为了使按钮与下拉菜单一样工作,我们只对两者使用 addHours。我们还可以使用 replaceFilter,这比 .filter(null) 然后设置另一个过滤器要快一点:

function drawBrush() {
    if (this.innerText === "Brush Extent") {  }
    if (this.innerText === "3 Hours") { addHours(3);  }
    if (this.innerText === "6 Hours") { addHours(6); }
    if (this.innerText === "12 Hours") { addHours(12); }
    if (this.innerText === "24 Hours") { addHours(24); }
}

d3.select('#hoursDropDown').on('change', function() {
    addHours(this.value);
});

function addHours(amountHours) {
    timeSlider.replaceFilter(...);
    dc.redrawAll();
}

现在 ... 中发生了什么?我认为这是让这件事变得如此混乱的另一部分,我花了一段时间才弄明白。您可能希望 moment.js 使用没有副作用的函数式样式,但实际上,它每次都在修改日期对象。所以如果你打电话给

start.add(amountHours, 'hours')

多次,它每次都会向 start 添加更多小时!相反,我们可以 clone start before modifying it:

moment(start).add(amountHours, 'hours')

(注意:我还颠倒了参数的顺序,因为当时正在抱怨一个已弃用的接口。)

这里的另一个问题是您不想调用 moment.hours() - 它只会获取小时数、整数而不是有效的日期对象。

将所有这些放在一起,就是:

function addHours(amountHours) {
    timeSlider.replaceFilter(dc.filters.RangedFilter(start, moment(start).add(amountHours, 'hours')));
    dc.redrawAll();
}

你的 fiddle 的分支:http://jsfiddle.net/gordonwoodhull/ewmrmu83/9/