filter/filter-dimensions-logical-operators.js

/**
 * Copyright (c) 2019
 *
 * @summary chart class
 * @author Elitza Vasileva
 * @author Bernhard Pointner
 */
class FilterDimensionsLogicalOperators {
    constructor(_dimensions, _cat_identifier, _cat_identifier_start) {
        this.dimensions = _dimensions; // crossfilter dimensions list
        this.cat_identifier = _cat_identifier;
        this.cat_identifier_start = _cat_identifier_start;

        this.filters = []; // ex: [{index: 0, match: 0, operator: "AND"}]

        this.operatorsLookup = {"AND":"&&","OR":"||","NOT":"!"};
    }

    /**
     *  Adds a filter on a dimension specified by the index
     * @param index
     * @param match
     * @param operator   the operator to be applied
     */
    addFilterOnDimensionIndex(index,match,operator) {
        // change filter type if index already exists but with different operator
        let found_filter_with_different_op = this.filters.filter(filter => filter.index == index && filter.operator != operator);
        if(found_filter_with_different_op.length == 1) {
            // update filter
            found_filter_with_different_op.operator = operator;

            return;
        }

        let found_filter_with_same_op = this.filters.filter(filter => filter.index == index && filter.operator == operator);
        if(found_filter_with_same_op.length == 1) {
            // remove filter
            this.removeFilterOnDimensionIndex(index);

            return;
        }

        let found_filter = this.filters.filter(filter => filter.index == index);
        if(found_filter.length == 0) {
            // add filter
            this.filters.push({index:index,match:match,operator:operator});

            return;
        }
    }

    /**
     * Remove a filter on a specific dimension
     * @param index
     */
    removeFilterOnDimensionIndex(index) {
        this.filters = this.filters.filter(filter => filter.index != index);
    }

    /**
     * Evaluates the expression result of a single row
     * @param row   the row to be evaluated
     * @param i   the index
     * @returns {boolean}
     */
    evaluateRow(row,i) {
        // uses eval() function: https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/eval
        let expression = 1;
        let operator = "";

        for(let i=0;i<this.filters.length;i++) {
            operator = this.operatorsLookup[this.filters[i].operator];

            if(i==0 && this.filters[i].operator == "NOT") {
                expression = operator + (row[this.cat_identifier + (this.filters[i].index + this.cat_identifier_start)] == this.filters[i].match ? 1 : 0);
            }
            else if(i==0 && this.filters[i].operator != "NOT") {
                expression = "" + (row[this.cat_identifier + (this.filters[i].index + this.cat_identifier_start)] == this.filters[i].match ? 1 : 0);
            }
            else if(this.filters[i].operator == "NOT") {
                expression += "&&" + operator + (row[this.cat_identifier + (this.filters[i].index + this.cat_identifier_start)] == this.filters[i].match ? 1 : 0);
            }
            else {
                expression += operator + (row[this.cat_identifier + (this.filters[i].index + this.cat_identifier_start)] == this.filters[i].match ? 1 : 0);
            }
        }
        let result = this.evaluateExpression(""+expression);

        return result; // boolean
    }

    /**
     * Own method for evaluating an expression containing only the operators AND, OR or NOT
     * @param expression   the expression to be evaluated
     * @returns {boolean}
     */
    evaluateExpression(expression) {
        let ors = expression.split("||");
        let resultingExpression = [];
        for(let i=0;i<ors.length;i++) {
            let ands = ors[i].split("&&").map(value => value.length==2 ? !value[1] : value);
            let amountZero = ands.filter(value => value.length==2 ? +(!value[1]) == 0 : +value == 0).length;
            resultingExpression.push(!(amountZero>0));
        }
        return resultingExpression.filter(value => value).length > 0;
    }

    /**
     * Resets the filter
     */
    resetFilter() {
        this.filters = []; // ex: [{index: 0, math: 0, operator: "AND"}]
    }
}