Snippets

Piotr Szrajber Smart M.App - Stage summarizer

Created by Piotr Szrajber
//# sourceURL=customization.js
/**
* This snippet is dependent on particular configuration
* 2017-07-24 Piotr Szrajber
*/

/**
 * Stage summarizer creates a second crossfilter on the same data and allows
 * to perform computations that are only partially dependent on current stage's filter
 * without explicitly creating another stage in UI.
 * 
 */
var StageSummarizer = class {

    constructor(stage, config, dc) {
        this.config = config;
        this.stage = stage;
        this.data = stage.rows();
        this.xf = crossfilter(this.data);
        this.dc = dc;

        let findDim = this.findDimensionByName.bind(this);

        this.dimensions = config.dimensions.reduce((aggregated, name) => {
            let dimension = findDim(name);
            if (!dimension) {
                console.warn(`Could not find a dimension named '${name}'`);
                return aggregated;
            }
            aggregated[name] = this.xf.dimension(d => d[dimension.id]);
            return aggregated;
        }, {});
        this.group = this._createXGroup();
    }

    findDimensionByName(name) {
        return this.stage.stageModel().fields.find(field => field.name === name);
    }

    _onAdd(aggregated, current) {
        this.config.values.forEach(function(obj) {
            aggregated[obj.property] += current[obj.property];
        });
        return aggregated;
    }

    _onRemove(aggregated, current) {
        this.config.values.forEach(function(obj) {
            aggregated[obj.property] -= current[obj.property];
        });
        return aggregated;
    }

    _onInit() {
        var ret = {};
        this.config.values.forEach(function(obj) {
            ret[obj.property] = 0;
        });
        return ret;
    }

    _createXGroup() {
        let onAdd = this._onAdd.bind(this),
            onRemove = this._onRemove.bind(this),
            onInit = this._onInit.bind(this);

        return this.xf.groupAll().reduce(onAdd, onRemove, onInit);
    }

    _createNumberDisplay(selector, config) {
        let numberDisplay = this.dc.numberDisplay(selector);

        numberDisplay.group(this.group)
            .valueAccessor(function(d) {
                return d[config.property];
            })
            .html(config.html)
            .formatNumber(d3.format(config.format));

        return numberDisplay;
    }

    createNumberDisplay(selector, config) {
        let domElement = document.querySelector(selector);
        if (!domElement) {
            console.error(`No ${selector} in the DOM`);
            return;
        }
        this.numberDisplays = this.numberDisplays || {};
        let chart = this.numberDisplays[selector] = this._createNumberDisplay(domElement, config);
        chart.render();
        return chart;
    }

    filter(filters) {
        if (!filters) {
            for (let dimensionName in this.dimensions) {
                this.dimensions[dimensionName].filter(null);
            }
        }
        for (let filterName in filters) {
            let dimension = this.dimensions[filterName];
            if (!dimension) {
                console.warn(`No dimension named ${filterName}`);
                continue;
            }
            dimension.filter(filters[filterName]);
        }
        for (let selector in this.numberDisplays) {
            this.numberDisplays[selector].redraw();
        }
    }
};

let GLOBALS = {};

/**
 * Function that waits till the charts are ready (they already have SVG element)
 * @param {Function} fn callback
 * @return {void}
 */
function chartsReady(fn) {
    let observer = new MutationObserver(function(mutations, me) {
        let chartWithSvg = document.querySelector(".widget-chart.dc-chart>svg");
        if (chartWithSvg) {
            fn();
            me.disconnect();
        }
    });

    observer.observe(document, {
        childList: true,
        subtree: true
    });
}


function findTimelineWidget(fn) {
    gsp.bi.stage.findWidgets({
        descriptors: [{
            chartM: {
                name: "Dam Storage Percentage in Time" // name must match your widget
            }
        }]
    }, function(widgets) {
        let heightWidget = widgets[0];
        if (!heightWidget) return;
        if (typeof fn === "function")
            fn(heightWidget);
    });
}

function fixTimelineWidget(widget) {
    let start = new Date(2012, 0, 1),
        end = new Date(2017, 6, 14); //0 is January so 6 is July

    widget.chart.xAxis().scale().domain([start, end]);
    widget.chart.elasticY(false);
    widget.chart.redraw();
}

function createNumberDisplays(timelinewidget) {
    gsp.bi.stage.requireLibraries(function(gvc, dc) {
        gsp.bi.stage.findStage(null, function(stage) {
            let infoProvider = new StageSummarizer(stage, {
                dimensions: ["DATE", "NAME"],
                values: [{
                    property: "BULK_WATER_STORAGE"
                }, {
                    property: "STORAGE_Ml"
                }],
                filters: null
            }, dc);

            // initialize date filter
            infoProvider.filter({
                DATE: Date(2017, 6, 14)
            });
            // synchronize date filter in the summarizer with the right date of the timeline
            if (timelinewidget) {
                timelinewidget.chart.on("filtered", function(chart, range) {
                    if (range && range[1]) {
                        let year = range[1].getFullYear(),
                            month = range[1].getMonth(),
                            day = range[1].getDate();
                        infoProvider.filter({
                            DATE: new Date(year, month, day)
                        });
                    }
                });
            }

            let summary1Selector = ".summary1 .widget-chart",
                summary2Selector = ".summary2 .widget-chart",
                summary3Selector = ".summary3 .widget-chart",
                summary1,
                summary2,
                summary3;

            if (summary1 = document.querySelector(summary1Selector)) {
                summary1.innerHTML = "";
                infoProvider.createNumberDisplay(summary1Selector, {
                    property: "BULK_WATER_STORAGE",
                    format: ".2f",
                    html: {
                        one: "Bulk water storage: %number",
                        some: "Bulk water storage: %number",
                        none: "Bulk water storage: %number"
                    }
                });
            }

            if (summary2 = document.querySelector(summary2Selector)) {
                summary2.innerHTML = "";
                infoProvider.createNumberDisplay(summary2Selector, {
                    property: "STORAGE_Ml",
                    format: ".2f",
                    html: {
                        one: "Storage: %number",
                        some: "Storage: %number",
                        none: "Storage: %number"
                    }
                });
            }
        });
    });
}

chartsReady(function() {
    findTimelineWidget(function(widget) {
        fixTimelineWidget(widget);
        createNumberDisplays(widget);
    });
});

Comments (0)

HTTPS SSH

You can clone a snippet to your computer for local editing. Learn more.