Source: charts/data-count.js

import {format} from 'd3-format';

import {logger} from '../core/logger';
import {BaseMixin} from '../base/base-mixin';

/**
 * The data count widget is a simple widget designed to display the number of records selected by the
 * current filters out of the total number of records in the data set. Once created the data count widget
 * will automatically update the text content of child elements with the following classes:
 *
 * * `.total-count` - total number of records
 * * `.filter-count` - number of records matched by the current filters
 *
 * Note: this widget works best for the specific case of showing the number of records out of a
 * total. If you want a more general-purpose numeric display, please use the
 * {@link NumberDisplay} widget instead.
 *
 * Examples:
 * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
 * @mixes BaseMixin
 */
export class DataCount extends BaseMixin {
    /**
     * Create a Data Count widget.
     * @example
     * var ndx = crossfilter(data);
     * var all = ndx.groupAll();
     *
     * new DataCount('.dc-data-count')
     *     .crossfilter(ndx)
     *     .groupAll(all);
     * @param {String|node|d3.selection} parent - Any valid
     * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying
     * a dom block element such as a div; or a dom element or d3 selection.
     * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
     * Interaction with a chart will only trigger events and redraws within the chart's group.
     */
    constructor (parent, chartGroup) {
        super();

        this._formatNumber = format(',d');
        this._crossfilter = null;
        this._groupAll = null;
        this._html = {some: '', all: ''};

        this._mandatoryAttributes(['crossfilter', 'groupAll']);

        this.anchor(parent, chartGroup);
    }

    /**
     * Gets or sets an optional object specifying HTML templates to use depending how many items are
     * selected. The text `%total-count` will replaced with the total number of records, and the text
     * `%filter-count` will be replaced with the number of selected records.
     * - all: HTML template to use if all items are selected
     * - some: HTML template to use if not all items are selected
     * @example
     * counter.html({
     *      some: '%filter-count out of %total-count records selected',
     *      all: 'All records selected. Click on charts to apply filters'
     * })
     * @param {{some:String, all: String}} [options]
     * @returns {{some:String, all: String}|DataCount}
     */
    html (options) {
        if (!arguments.length) {
            return this._html;
        }
        if (options.all) {
            this._html.all = options.all;
        }
        if (options.some) {
            this._html.some = options.some;
        }
        return this;
    }

    /**
     * Gets or sets an optional function to format the filter count and total count.
     * @see {@link https://github.com/d3/d3-format/blob/master/README.md#format d3.format}
     * @example
     * counter.formatNumber(d3.format('.2g'))
     * @param {Function} [formatter=d3.format('.2g')]
     * @returns {Function|DataCount}
     */
    formatNumber (formatter) {
        if (!arguments.length) {
            return this._formatNumber;
        }
        this._formatNumber = formatter;
        return this;
    }

    _doRender () {
        const tot = this.crossfilter().size(),
            val = this.groupAll().value();
        const all = this._formatNumber(tot);
        const selected = this._formatNumber(val);

        if ((tot === val) && (this._html.all !== '')) {
            this.root().html(this._html.all.replace('%total-count', all).replace('%filter-count', selected));
        } else if (this._html.some !== '') {
            this.root().html(this._html.some.replace('%total-count', all).replace('%filter-count', selected));
        } else {
            this.selectAll('.total-count').text(all);
            this.selectAll('.filter-count').text(selected);
        }
        return this;
    }

    _doRedraw () {
        return this._doRender();
    }

    crossfilter (cf) {
        if (!arguments.length) {
            return this._crossfilter;
        }
        this._crossfilter = cf;
        return this;
    }

    dimension (cf) {
        logger.warnOnce('consider using dataCount.crossfilter instead of dataCount.dimension for clarity');
        if (!arguments.length) {
            return this.crossfilter();
        }
        return this.crossfilter(cf);
    }

    groupAll (groupAll) {
        if (!arguments.length) {
            return this._groupAll;
        }
        this._groupAll = groupAll;
        return this;
    }

    group (groupAll) {
        logger.warnOnce('consider using dataCount.groupAll instead of dataCount.group for clarity');
        if (!arguments.length) {
            return this.groupAll();
        }
        return this.groupAll(groupAll);
    }
}

export const dataCount = (parent, chartGroup) => new DataCount(parent, chartGroup);