model/model-crossfilter.js

/**
 * Copyright (c) 2019
 *
 * @summary d3 script for charts of AggreSet
 * @author Elitza Vasileva
 * @author Bernhard Pointner
 */
class Crossfilter {

    constructor(_data) {
        this.data = _data;

        // necessary data
        this.movies = _data.entries;
        this.genres = _data.genres;

        // num of genres
        this.num_genres = this.genres.length;

        // crossfilter
        this.cf = null;

        this.cat_identifier = "g";
        this.cat_identifier_start = 1;

        // dimensions and groups and descriptions of multi hot array
        this.dimensions = {};
        this.groups = {};
        this.descriptions = {};
        this.filters = {};
        this.filters_title = {};
        this.selected = [];
        this.hovered = [];


        this.all = null;

        this.formatNumber = null;
        this.formatChange = null;
        this.formatDate = null;
        this.formatTime = null;

        this.initFormats();
        this.initDataTypesOnDimensions();
        this.initDimensionsAndGroups();
    }

    /**
     * Initializes the different type formats
     */
    initFormats() {
        // Various formatters
        this.formatNumber = d3.format(",d");
        this.formatChange = d3.format("+,d");
        this.formatDate = d3.timeFormat("%B %d, %Y");
        this.formatTime = d3.timeFormat("%I:%M %p");
    }

    /**
     * Initializes the types of dimensions
     */
    initDataTypesOnDimensions() {
        let num_genres = this.num_genres;

        this.movies.forEach(function(d, i) {
            d.index = i;
            for(let i=1;i<=num_genres;i++) {
                d["g"+i] = +d["g"+i];
            }
        });
    }

    /**
     * Initializes the dimension and groups according to the data
     */
    initDimensionsAndGroups() {
        let genres = this.genres;
        let cat_identifier = this.cat_identifier;

        this.dimensions["genres"] = new Array(this.num_genres);
        this.groups["genres"] = new Array(this.num_genres);
        this.descriptions["genres"] = new Array(this.num_genres);
        this.filters["genres"] = {active : null, preview: null};
        this.selected["genres"] = new Array(this.num_genres).fill(false);
        this.hovered["genres"] = new Array(this.num_genres).fill(false);
        this.filters_title["genres"] = "Genres";


        this.dimensions["num_genres"] = null;
        this.groups["num_genres"] = null;
        this.descriptions["num_genres"] = null;
        this.filters["num_genres"] = {active : null, preview: null};
        this.selected["num_genres"] = [];
        this.hovered["num_genres"] = [];
        this.filters_title["genres"] = "# of Genres";


        this.dimensions["ratings"] = null;
        this.groups["ratings"] = null;
        this.descriptions["ratings"] = null;
        this.filters["ratings"] = {active : null, preview: null};
        this.selected["ratings"] = [];
        this.hovered["ratings"] = [];
        this.filters_title["ratings"] = "Ratings";


        this.dimensions["watched"] = null;
        this.groups["watched"] = null;
        this.descriptions["watched"] = null;
        this.filters["watched"] = {active : null, preview: null};
        this.selected["watched"] = [];
        this.hovered["watched"] = [];
        this.filters_title["watched"] = "Watches";



        this.cf = crossfilter(this.movies);

        this.all = this.cf.groupAll();

        for(let j=0;j<this.num_genres;j++) {
            this.dimensions["genres"][j] = this.cf.dimension(function(d) {return d[cat_identifier+genres[j].key];});
            this.groups["genres"][j] = this.dimensions["genres"][j].group(function(d) { return d; });
            this.descriptions["genres"][j] = this.genres[j];
            //console.log(this.groups["genres"][i].all());
        }
        this.filters["genres"].active = new FilterDimensionsLogicalOperators(this.dimensions["genres"],this.cat_identifier,this.cat_identifier_start);
        this.filters["genres"].preview = new FilterDimensionsLogicalOperators(this.dimensions["genres"],this.cat_identifier,this.cat_identifier_start);

        let num_genres_per_row = [];
        this.dimensions["num_genres"] = this.cf.dimension(function (d) { let num = genres.map(genre => d["g"+genre.key]).reduce((a,b) => a + b, 0);num_genres_per_row.push(num);return num});
        this.groups["num_genres"] = this.dimensions["num_genres"].group();
        this.descriptions["num_genres"] = this.genres;
        this.filters["num_genres"].active = new FilterSumOfDimensions(this.dimensions["genres"],this.cat_identifier,this.cat_identifier_start,num_genres_per_row);
        this.filters["num_genres"].preview = new FilterSumOfDimensions(this.dimensions["genres"],this.cat_identifier,this.cat_identifier_start,num_genres_per_row);


        this.dimensions["ratings"] = this.cf.dimension(function(d) {return Math.round(d["rating"]);});
        this.groups["ratings"] = this.dimensions["ratings"].group();
        this.descriptions["ratings"] = this.genres;
        this.filters["ratings"].active = new FitlerDefault(this.dimensions["ratings"],this.cat_identifier,this.cat_identifier_start,"rating");
        this.filters["ratings"].preview = new FitlerDefault(this.dimensions["ratings"],this.cat_identifier,this.cat_identifier_start,"rating");

        //console.log(Math.round(d["watched"]/Math.pow(10,(''+d["watched"]).length))*Math.pow(10,(''+d["watched"]).length));
        this.dimensions["watched"] = this.cf.dimension(function(d) { return Math.ceil(d["watched"]/Math.pow(10,(''+d["watched"]).length))*Math.pow(10,(''+d["watched"]).length-1);});
        this.groups["watched"] = this.dimensions["watched"].group();
        this.descriptions["watched"] = this.genres;
        this.filters["watched"].active = new FilterLog(this.dimensions["watched"],this.cat_identifier,this.cat_identifier_start,"watched");
        this.filters["watched"].preview = new FilterLog(this.dimensions["watched"],this.cat_identifier,this.cat_identifier_start,"watched");


        // TEST - DONT DELETE
        /*console.log(this.genres);

        let dimension = this.cf.dimension(function(d) { return genres.map(genre => "g"+genre.key+"-"+d["g"+genre.key]) },true);
        let group = dimension.group();

        console.log(group.all());

        let test1 = this.groups["num_genres"].all();
        console.log(test1);

        //dimension.filter("g1-1");

        console.log(dimension.top(Infinity));

        let test2 = this.groups["num_genres"].all();
        console.log(test2);*/
    }

    /**
     * Filters transmitted data by all available dimensions on available filters
     * @param data   array of data entries to filter
     * @param type   'active' or 'preview'
     * @returns {*} .. filtered data entries
     */
    filterCustomData(data, type) {
        const _this = this;
        let filter_keys = Object.keys(this.filters);

        let filtered_data = data.filter((row,i) => {
            let false_found = filter_keys.filter(key => {
                return _this.filters[key][type].evaluateRow(data[i],i) == false;
            });

            return false_found.length == 0;
        });

        return filtered_data;
    }


    /**
     * Get all rows which are currently filtered
     * @returns {*} filtered rows
     */
    getAllFilteredRows() {
        return this.cf.allFiltered();
    }

    /**
     * Update global filter array and show filter container
     */
    updateGlobalFilterArray() {
        const _this = this;
        let filter_container = d3.select(".vis-container-header .vis-filter-container");
        let active_filters = d3.select(".active-filters");
        let filter_keys = Object.keys(this.filters);
        if(filter_keys.length !== 0) {
            filter_keys.map((key,i) => {
                active_filters.text(_this.filters_title[key]);
            });
            filter_container.style("display","block");
        }
    }
}