Eugene Goldberg
Eugene Goldberg

Reputation: 15544

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

Trying to learn dc.js Looking at the Nasdaq example in Dc.js home page : http://dc-js.github.io/dc.js/

The main attraction for me at this time is to figure out how clicking on individual chart components, such as a specific bubble, a specific segment of the doughnut chart, etc... triggers re-filtering of all other components. I'm looking through the stock.js file, but I can not seem to find the event handlers, which respond to specific mouse selections, and trigger other parts to update. Could anyone, who is experienced with dc.js point me where to look for these things?

Here is the 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 */
});

Upvotes: 1

Views: 2076

Answers (1)

Ethan Jewett
Ethan Jewett

Reputation: 6010

I believe it is defined in the onClick handler of the base mixin (which is included in all charts): https://github.com/dc-js/dc.js/blob/develop/src/base-mixin.js#L1072

If you want to trigger redrawing from outside of dc.js, use the dc.redrawAll and dc.renderAll methods.

Upvotes: 2

Related Questions