dc.js 中的事件处理和过滤究竟是如何工作的?

how exactly does event handling and filtering work in dc.js?

努力学习dc.js 查看 Dc.js 主页中的纳斯达克示例:http://dc-js.github.io/dc.js/

此时对我来说主要的吸引力是弄清楚如何单击单个图表组件,例如特定气泡、圆环图的特定部分等...触发所有其他组件的重新过滤. 我正在查看 stock.js 文件,但似乎找不到响应特定鼠标选择并触发其他部分更新的事件处理程序。 dc.js 有经验的人能告诉我在哪里可以找到这些东西吗?

这是stock.js:

'use strict';

var gainOrLossChart = dc.pieChart('#gain-loss-chart');
var fluctuationChart = dc.barChart('#fluctuation-chart');
var quarterChart = dc.pieChart('#quarter-chart');
var dayOfWeekChart = dc.rowChart('#day-of-week-chart');
var moveChart = dc.lineChart('#monthly-move-chart');
var volumeChart = dc.barChart('#monthly-volume-chart');
var yearlyBubbleChart = dc.bubbleChart('#yearly-bubble-chart');
var nasdaqCount = dc.dataCount('.dc-data-count');
var nasdaqTable = dc.dataTable('.dc-data-table');


 <div id='chart'>
 <a class='reset'
 href='javascript:myChart.filterAll();dc.redrawAll();'
 style='visibility: hidden;'>reset</a>
 </div>

 <div id='chart'>
 <span class='reset' style='visibility: hidden;'>
 Current filter: <span class='filter'></span>
 </span>
 </div>

d3.csv('ndx.csv', function (data) {
    var dateFormat = d3.time.format('%m/%d/%Y');
    var numberFormat = d3.format('.2f');

    data.forEach(function (d) {
        d.dd = dateFormat.parse(d.date);
        d.month = d3.time.month(d.dd); // pre-calculate month for better performance
        d.close = +d.close; // coerce to number
        d.open = +d.open;
    });

    var ndx = crossfilter(data);
    var all = ndx.groupAll();

    var yearlyDimension = ndx.dimension(function (d) {
        return d3.time.year(d.dd).getFullYear();
    });
    var yearlyPerformanceGroup = yearlyDimension.group().reduce(
        /* callback for when data is added to the current filter results */
        function (p, v) {
            ++p.count;
            p.absGain += v.close - v.open;
            p.fluctuation += Math.abs(v.close - v.open);
            p.sumIndex += (v.open + v.close) / 2;
            p.avgIndex = p.sumIndex / p.count;
            p.percentageGain = p.avgIndex ? (p.absGain / p.avgIndex) * 100 : 0;
            p.fluctuationPercentage = p.avgIndex ? (p.fluctuation / p.avgIndex) * 100 : 0;
            return p;
        },
        function (p, v) {
            --p.count;
            p.absGain -= v.close - v.open;
            p.fluctuation -= Math.abs(v.close - v.open);
            p.sumIndex -= (v.open + v.close) / 2;
            p.avgIndex = p.count ? p.sumIndex / p.count : 0;
            p.percentageGain = p.avgIndex ? (p.absGain / p.avgIndex) * 100 : 0;
            p.fluctuationPercentage = p.avgIndex ? (p.fluctuation / p.avgIndex) * 100 : 0;
            return p;
        },
        /* initialize p */
        function () {
            return {
                count: 0,
                absGain: 0,
                fluctuation: 0,
                fluctuationPercentage: 0,
                sumIndex: 0,
                avgIndex: 0,
                percentageGain: 0
            };
        }
    );

    var dateDimension = ndx.dimension(function (d) {
        return d.dd;
    });

    var moveMonths = ndx.dimension(function (d) {
        return d.month;
    });
    var monthlyMoveGroup = moveMonths.group().reduceSum(function (d) {
        return Math.abs(d.close - d.open);
    });
    var volumeByMonthGroup = moveMonths.group().reduceSum(function (d) {
        return d.volume / 500000;
    });
    var indexAvgByMonthGroup = moveMonths.group().reduce(
        function (p, v) {
            ++p.days;
            p.total += (v.open + v.close) / 2;
            p.avg = Math.round(p.total / p.days);
            return p;
        },
        function (p, v) {
            --p.days;
            p.total -= (v.open + v.close) / 2;
            p.avg = p.days ? Math.round(p.total / p.days) : 0;
            return p;
        },
        function () {
            return {days: 0, total: 0, avg: 0};
        }
    );

    var gainOrLoss = ndx.dimension(function (d) {
        return d.open > d.close ? 'Open' : 'Closed';
    });
    var gainOrLossGroup = gainOrLoss.group();

    var fluctuation = ndx.dimension(function (d) {
        return Math.round((d.close - d.open) / d.open * 100);
    });
    var fluctuationGroup = fluctuation.group();

    var quarter = ndx.dimension(function (d) {
        var month = d.dd.getMonth();
        if (month <= 2) {
            return 'Q1';
        } else if (month > 2 && month <= 5) {
            return 'Q2';
        } else if (month > 5 && month <= 8) {
            return 'Q3';
        } else {
            return 'Q4';
        }
    });
    var quarterGroup = quarter.group().reduceSum(function (d) {
        return d.volume;
    });

    var dayOfWeek = ndx.dimension(function (d) {
        var day = d.dd.getDay();
        var name = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
        return day + '.' + name[day];
    });
    var dayOfWeekGroup = dayOfWeek.group();
.radiusValueAccessor(function (p) {
            return p.value.fluctuationPercentage;
        })
        .maxBubbleRelativeSize(0.3)
        .x(d3.scale.linear().domain([-2500, 2500]))
        .y(d3.scale.linear().domain([-100, 100]))
        .r(d3.scale.linear().domain([0, 4000]))

        .yAxisPadding(100)
        .xAxisPadding(500)
        .renderHorizontalGridLines(true)
        .renderVerticalGridLines(true)
        .xAxisLabel('Index Gain')
        .yAxisLabel('Index Gain %')

        .renderLabel(true)
        .label(function (p) {
            return p.key;
        })
        // (_optional_) whether chart should render titles, `default = false`
        .renderTitle(true)
        .title(function (p) {
            return [
                p.key,
                'Index Gain: ' + numberFormat(p.value.absGain),
                'Index Gain in Percentage: ' + numberFormat(p.value.percentageGain) + '%',
                'Fluctuation / Index Ratio: ' + numberFormat(p.value.fluctuationPercentage) + '%'
            ].join('\n');
        })

        .yAxis().tickFormat(function (v) {
        return v + '%';
    });



    gainOrLossChart /* dc.pieChart('#gain-loss-chart', 'chartGroup') */

        .width(180)
        .height(180)
        .radius(80)
        .dimension(gainOrLoss)
        .group(gainOrLossGroup)
        .label(function (d) {
            if (gainOrLossChart.hasFilter() && !gainOrLossChart.hasFilter(d.key)) {
                return d.key + '(0%)';
            }
            var label = d.key;
            if (all.value()) {
                label += '(' + Math.floor(d.value / all.value() * 100) + '%)';
            }
            return label;
        })


    quarterChart /* dc.pieChart('#quarter-chart', 'chartGroup') */
        .width(180)
        .height(180)
        .radius(80)
        .innerRadius(30)
        .dimension(quarter)
        .group(quarterGroup);

        .width(180)
        .height(180)
        .margins({top: 20, left: 10, right: 10, bottom: 20})
        .group(dayOfWeekGroup)
        .dimension(dayOfWeek)
        .ordinalColors(['#3182bd', '#6baed6', '#9ecae1', '#c6dbef', '#dadaeb'])
        .label(function (d) {
            return d.key.split('.')[1];
        })
        // Title sets the row text
        .title(function (d) {
            return d.value;
        })
        .elasticX(true)
        .xAxis().ticks(4);

        .width(420)
        .height(180)
        .margins({top: 10, right: 50, bottom: 30, left: 40})
        .dimension(fluctuation)
        .group(fluctuationGroup)
        .elasticY(true)
        // (_optional_) whether bar should be center to its x value. Not needed for ordinal chart, `default=false`
        .centerBar(true)
        // (_optional_) set gap between bars manually in px, `default=2`
        .gap(1)
        // (_optional_) set filter brush rounding
        .round(dc.round.floor)
        .alwaysUseRounding(true)
        .x(d3.scale.linear().domain([-25, 25]))
        .renderHorizontalGridLines(true)
        // Customize the filter displayed in the control span
        .filterPrinter(function (filters) {
            var filter = filters[0], s = '';
            s += numberFormat(filter[0]) + '% -> ' + numberFormat(filter[1]) + '%';
            return s;
        });

    // Customize axes
    fluctuationChart.xAxis().tickFormat(
        function (v) { return v + '%'; });
    fluctuationChart.yAxis().ticks(5);

        .renderArea(true)
        .width(990)
        .height(200)
        .transitionDuration(1000)
        .margins({top: 30, right: 50, bottom: 25, left: 40})
        .dimension(moveMonths)
        .mouseZoomable(true)
        .rangeChart(volumeChart)
        .x(d3.time.scale().domain([new Date(1985, 0, 1), new Date(2012, 11, 31)]))
        .round(d3.time.month.round)
        .xUnits(d3.time.months)
        .elasticY(true)
        .renderHorizontalGridLines(true)

        .legend(dc.legend().x(800).y(10).itemHeight(13).gap(5))
        .brushOn(false)

        .group(indexAvgByMonthGroup, 'Monthly Index Average')
        .valueAccessor(function (d) {
            return d.value.avg;
        })

        .stack(monthlyMoveGroup, 'Monthly Index Move', function (d) {
            return d.value;
        })
        // Title can be called by any stack layer.
        .title(function (d) {
            var value = d.value.avg ? d.value.avg : d.value;
            if (isNaN(value)) {
                value = 0;
            }
            return dateFormat(d.key) + '\n' + numberFormat(value);
        });


    volumeChart.width(990) /* dc.barChart('#monthly-volume-chart', 'chartGroup'); */
        .height(40)
        .margins({top: 0, right: 50, bottom: 20, left: 40})
        .dimension(moveMonths)
        .group(volumeByMonthGroup)
        .centerBar(true)
        .gap(1)
        .x(d3.time.scale().domain([new Date(1985, 0, 1), new Date(2012, 11, 31)]))
        .round(d3.time.month.round)
        .alwaysUseRounding(true)
        .xUnits(d3.time.months);

    nasdaqCount /* dc.dataCount('.dc-data-count', 'chartGroup'); */
        .dimension(ndx)
        .group(all)      
        .html({
            some: '<strong>%filter-count</strong> 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.'
        });

    nasdaqTable /* dc.dataTable('.dc-data-table', 'chartGroup') */
        .dimension(dateDimension)
        .group(function (d) {
            var format = d3.format('02d');
            return d.dd.getFullYear() + '/' + format((d.dd.getMonth() + 1));
        })
        // (_optional_) max number of records to be shown, `default = 25`
        .size(10)
        // There are several ways to specify the columns; see the data-table documentation.
        // This code demonstrates generating the column header automatically based on the columns.
        .columns([
            // Use the `d.date` field; capitalized automatically
            'date',
            // Use `d.open`, `d.close`
            'open',
            'close',
            {

                label: 'Change',
                format: function (d) {
                    return numberFormat(d.close - d.open);
                }
            },
            // Use `d.volume`
            'volume'
        ])

        .sortBy(function (d) {
            return d.dd;
        })

     dc.geoChoroplethChart('#us-chart')
     // (_optional_) define chart width, default 200
     .width(990)
     // (optional) define chart height, default 200
     .height(500)
     // (optional) define chart transition duration, default 1000
     .transitionDuration(1000)
     // set crossfilter dimension, dimension key should match the name retrieved in geojson layer
     .dimension(states)
     // set crossfilter group
     .group(stateRaisedSum)
     // (_optional_) define color function or array for bubbles
     .colors(['#ccc', '#E2F2FF','#C4E4FF','#9ED2FF','#81C5FF','#6BBAFF','#51AEFF','#36A2FF','#1E96FF','#0089FF',
     '#0061B5'])
     // (_optional_) define color domain to match your data domain if you want to bind data or color
     .colorDomain([-5, 200])
     // (_optional_) define color value accessor
     .colorAccessor(function(d, i){return d.value;})
     // Project the given geojson. You can call this function multiple times with different geojson feed to generate

     .overlayGeoJson(statesJson.features, 'state', function(d) {
     return d.properties.name;
     })

     .title(function(d) {
     return 'State: ' + d.key + '\nTotal Amount Raised: ' + numberFormat(d.value ? d.value : 0) + 'M';
     });


     dc.bubbleOverlay('#bubble-overlay', 'chartGroup')
     // The bubble overlay chart does not generate its own svg element but rather reuses an existing
     // svg to generate its overlay layer
     .svg(d3.select('#bubble-overlay svg'))
     // (_optional_) define chart width, `default = 200`
     .width(990)
     // (_optional_) define chart height, `default = 200`
     .height(500)
     // (_optional_) define chart transition duration, `default = 1000`
     .transitionDuration(1000)
     // Set crossfilter dimension, dimension key should match the name retrieved in geo json layer
     .dimension(states)
     // Set crossfilter group
     .group(stateRaisedSum)
     // Closure used to retrieve x value from multi-value group
     .keyAccessor(function(p) {return p.value.absGain;})
     // Closure used to retrieve y value from multi-value group
     .valueAccessor(function(p) {return p.value.percentageGain;})
     // (_optional_) define color function or array for bubbles
     .colors(['#ccc', '#E2F2FF','#C4E4FF','#9ED2FF','#81C5FF','#6BBAFF','#51AEFF','#36A2FF','#1E96FF','#0089FF',
     '#0061B5'])
     // (_optional_) define color domain to match your data domain if you want to bind data or color
     .colorDomain([-5, 200])
     // (_optional_) define color value accessor
     .colorAccessor(function(d, i){return d.value;})
     // Closure used to retrieve radius value from multi-value group
     .radiusValueAccessor(function(p) {return p.value.fluctuationPercentage;})
     // set radius scale
     .r(d3.scale.linear().domain([0, 3]))
     // (_optional_) whether chart should render labels, `default = true`
     .renderLabel(true)
     // (_optional_) closure to generate label per bubble, `default = group.key`
     .label(function(p) {return p.key.getFullYear();})
     // (_optional_) whether chart should render titles, `default = false`
     .renderTitle(true)
     // (_optional_) closure to generate title per bubble, `default = d.key + ': ' + d.value`
     .title(function(d) {
     return 'Title: ' + d.key;
     })
     // add data point to its layer dimension key that matches point name: it will be used to
     // generate a bubble. Multiple data points can be added to the bubble overlay to generate
     // multiple bubbles.
     .point('California', 100, 120)
     .point('Colorado', 300, 120)
     // (_optional_) setting debug flag to true will generate a transparent layer on top of
     // bubble overlay which can be used to obtain relative `x`,`y` coordinate for specific
     // data point, `default = false`
     .debug(true);
     */

    //#### Rendering

    //simply call `.renderAll()` to render all charts on the page
    dc.renderAll();
    /*
     // Or you can render charts belonging to a specific chart group
     dc.renderAll('group');
     // Once rendered you can call `.redrawAll()` to update charts incrementally when the data
     // changes, without re-rendering everything
     dc.redrawAll();
     // Or you can choose to redraw only those charts associated with a specific chart group
     dc.redrawAll('group');
     */

});

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

我相信它是在基本 mixin 的 onClick 处理程序中定义的(它包含在所有图表中):https://github.com/dc-js/dc.js/blob/develop/src/base-mixin.js#L1072

如果您想从 dc.js 外部触发重绘,请使用 dc.redrawAll 和 dc.renderAll 方法。