chart/chart-aggreset.js

/**
 * Copyright (c) 2019
 *
 * @summary chart class
 * @author Elitza Vasileva
 * @author Bernhard Pointner
 */

/**
 * AggreSet chart
 * @extends Chart
 */
class AggreSetChart extends Chart {

    constructor(_parent, _dummyId, _id, _title, _subtitle, _is_cat_list, _dimension, _group, _description, _filters, _selected, _hovered, _filterCustomData, _updateAll, _previewAll) {
        super(_parent, _dummyId, _id, _title, _subtitle, _is_cat_list, _dimension, _group, _description, _filters, _selected, _hovered, _filterCustomData, _updateAll, _previewAll);

        // local variables
        this.matrix_root_cliques = null
        this.matrix_active_cliques = null;
        this.matrix_preview_cliques = null;
        this.rows = null;
        this.circles = null;

        // call init functions
        this.initMargins(this.data);
        this.initSVGDimensions(this.data);
        this.initSVG();
        this.sortData();


        // grid statistic
        this.amount_cell_total = Math.pow(this.description.length,2)/2;
        this.amount_cell_pairs = 0;

        this.matrix_root_cliques = this.calcRootCliquesInMatrix();
        this.matrix_active_cliques = this.matrix_root_cliques;
        this.matrix_preview_cliques = this.matrix_root_cliques;

        // grid dimensions
        let temp_data = this.data;
        this.grid_margin_label_left = getLongestTextDimension(this.data.filter((el,i)=>(i>temp_data.length-5)).map(el=>el.title),this.labelFontSizeWeight).width;
        this.grid_margin_label_top = 10;
        this.grid_width_without_label = this.width - this.grid_margin_label_left;
        this.grid_height_without_label = this.height - this.grid_margin_label_top;
        this.grid_cell_width = this.grid_width_without_label/this.description.length;
        this.grid_cell_height = this.grid_height_without_label/(this.description.length-1);
        this.grid_line_x_offset = 5;
        this.grid_line_y_offset = -5;

        // show settings
        this.show_active_value = false;
        this.show_preview_value = false;

        // grid circles
        this.max_circle_size = this.grid_cell_width < this.grid_cell_height ? this.grid_cell_width/2-1 : this.grid_cell_height/2-1;

        this.max_root_value_in_matrix = Math.max.apply(null,this.matrix_root_cliques.map(row => Math.max.apply(Math, row.map(el => el.key1 == el.key2 ? -1 : el.value))));
        this.max_active_value_in_matrix = this.max_root_value_in_matrix;
        this.max_preview_value_in_matrix = this.max_root_value_in_matrix;
        this.previewed_cell = null;

        this.legend_margin = { left: 30 - this.margin.left, top: 30 - this.margin.top };
        this.legend_inner_circle_factor = 0.25;
        this.stroke_correction = 1; // circle radius is reduced by stroke
    }

    /**
     * Initializes the sizes of the margins of the chart
     * @param data
     */
    initMargins(data) {
        this.margin = {top: 37,right: 0,bottom: 16,left: 0};
    }

    /**
     * Initializes the SVG dimensions
     * @param data
     */
    initSVGDimensions(data) {
        this.width = getWidth(this.container) - 20 - this.margin.left - this.margin.right; // 20px padding
        this.height = getHeight(this.container) - 20 - getHeight(getVisHeader(this.container)) - this.margin.top - this.margin.bottom; // 20px padding + header height
    }

    /**
     * Sorts the data labels in lexicographical order
     */
    sortData() {
        //sort bars based on value
        this.data = this.data.sort(function (a, b) {
            return b.value - a.value || ('' + a.title).localeCompare(b.title);
        });

        // format the data
        this.data.forEach(function(d) {
            d.value = +d.value;
        });
    }

    /**
     * Initializes the SVG
     */
    initSVG() {
        this.svg
            .attr("width", this.width + this.margin.left + this.margin.right)
            .attr("height", this.height + this.margin.top + this.margin.bottom)
        this.svg.g = this.svg
            .append("g")
            .attr("transform","translate(" + this.margin.left + "," + this.margin.top + ")");
    }

    /**
     * Draws necessary information as legend, statistics, rows and intersections on the chart
     */
    draw() {
        this.drawLegend();
        this.drawStatistic();
        this.drawCellLegendText();
        this.drawRows();
        this.drawIntersections();
    }

    /**
     * Updated the displayed information of the aggreset-chart
     */
    update() {
        this.matrix_active_cliques = this.calcCliquesInMatrix(this.matrix_root_cliques,'active');
        this.max_active_value_in_matrix = Math.max.apply(null,this.matrix_active_cliques.map(row => Math.max.apply(Math, row.map(el => el.key1 == el.key2 ? -1 : el.value))));
        this.matrix_preview_cliques = this.matrix_active_cliques;
        this.max_preview_value_in_matrix = this.max_active_value_in_matrix;

        this.checkIfDataIsFiltered();
        this.updateRows();
        this.updateIntersections(this.max_active_value_in_matrix);
        this.updateLegend(this.max_active_value_in_matrix);
        this.updateStatistic();
    }

    /**
     * Shows the preview information when hovering above the elements of the chart
     * @param reset
     */
    preview(reset) {
        this.matrix_preview_cliques = this.calcCliquesInMatrix(this.matrix_active_cliques,'preview');
        this.max_preview_value_in_matrix = Math.max.apply(null,this.matrix_preview_cliques.map(row => Math.max.apply(Math, row.map(el => el.key1 == el.key2 ? -1 : el.value))));
        //console.log(this.max_preview_value_in_matrix,this.matrix_preview_cliques);

        this.updateRows();
        this.previewIntersections(reset);
    }


    /* draw functions */

    /**
     * Draws row and column lines on the aggreset-chart
     */
    drawRows() {
        const _this = this;

        //console.log(_this.grid_cell_width, _this.grid_cell_height, _this.grid_width_without_label, _this.grid_height_without_label);

        if(!this.svg.g.selectAll(".rows").empty()) {
            this.svg.g.selectAll(".rows").remove();
        }
        if(this.svg.g.selectAll(".rows").empty()) {
            this.rows = this.svg.g.append("g").attr("class", "rows").attr("font-family","sans-serif").attr("text-anchor","end");
        }

        this.rows.selectAll(".row").data(this.description)
            .enter()
            .append("g")
            .attr("class","row tick")
            .attr("id", function(d, i) { return "g-"+d.key+"-row"; })
            .attr("transform", function(d, i) { return "translate(" + (_this.width-((i+1)*_this.grid_cell_width)) + "," + (i*(_this.grid_cell_height)) + ")"; });

        this.rows.selectAll(".tick")
            .append("text")
            .text((d, i) => _this.description[(+_this.data[i].key)-1].title)
            .attr("class", "axis-label")
            .classed("zeroed", (d,i) => i===0 ? 0 : _this.matrix_active_cliques[i][_this.matrix_active_cliques[i].length-1].horizontal_sum === 0)
            .on("mouseover", (d,i) => _this.handleCatMouseOver(d,i,_this))
            .on("mousemove", (d,i) => _this.handleCatMouseMove(d,i,_this))
            .on("mouseout", (d,i) => _this.handleCatMouseOut(d,i,_this))
            .on("click", (d,i) => _this.handleCatMouseClick(d,i,_this));

        this.rows.selectAll(".tick")
            .append("line")
            .attr("class","line line-vert")
            .classed("zeroed", (d,i) => i===0 ? 0 : _this.matrix_active_cliques[i][_this.matrix_active_cliques[i].length-1].horizontal_sum === 0)
            .attr("x1",_this.grid_line_x_offset)
            .attr("x2",_this.grid_line_x_offset)
            .attr("y1",_this.grid_line_y_offset)
            .attr("y2",(d,i) => _this.height - (i*_this.grid_cell_height));

        this.rows.selectAll(".tick")
            .append("line")
            .attr("class","line line-horz")
            .classed("zeroed", (d,i) => i===0 ? 0 : _this.matrix_active_cliques[i][_this.matrix_active_cliques[i].length-1].horizontal_sum === 0)
            .attr("x1",_this.grid_line_x_offset)
            .attr("x2",(d,i) => ((i+1)*_this.grid_cell_width))
            .attr("y1",_this.grid_line_y_offset)
            .attr("y2",_this.grid_line_y_offset);
    }

    /**
     * Draws the intersections of the row and column lines represented by rectangles and circles
     */
    drawIntersections() {
        const _this = this;

        if(this.svg.g.selectAll(".cell-circles").empty()) {
            this.circles = this.svg.g.append("g").attr("class", "cell-circles");
        }

        for(let node=0; node<this.description.length; node++) {

            let cells = this.circles.selectAll(".x").data(this.matrix_root_cliques[node].slice(0,-1)).enter().append("g").attrs({id: (d,i) => ("cell-"+(node+1)+"-"+(i+1)), class: "cell" })

            // rectangle
            cells.append("rect").attrs({
                class: "cell-rectangle",
                x: (d,i) => (_this.width-((i+1)*_this.grid_cell_width)) + _this.grid_line_x_offset - _this.grid_cell_width/2 + 1,
                y: (d,i) => node*_this.grid_cell_height + _this.grid_line_y_offset - _this.grid_cell_height/2 + 1,
                rx: "4px",
                ry: "4px",
                width: _this.grid_cell_width-2,
                height: _this.grid_cell_height-2,
                id: (d,i) => "cell-rectangle-"+(node+1)+"-"+(i+1),
            }).styles({
                display: (d,i) => d.value>0 ? "block": "none"
            })
                .on("mouseover", (d,i) => _this.handleCellMouseOver(d,i,node,_this,'rect'))
                .on("mousemove", (d,i) => _this.handleCellMouseMove(d,i,node,_this,'rect'))
                .on("mouseout", (d,i) => _this.handleCellMouseOut(d,i,node,_this,'rect'))
                .on("click", (d,i) => _this.handleCellMouseClick(d,i,node,_this,'circle'));


            // circle
            cells.append("circle").attrs({
                class: "cell-circle",
                cx: 0,
                cy: 0,
                r: function(d) {
                    return Math.sqrt(d.value/Math.PI)/Math.sqrt(_this.max_root_value_in_matrix/Math.PI) * _this.max_circle_size; // r defined by linear area
                    //return d.value/this.max_value_in_matrix * _this.max_circle_size; // re defined by value
                },
                id: (d,i) => "cell-circle-"+(node+1)+"-"+(i+1),
                transform: (d,i) => {
                    let left = (_this.width-((i+1)*_this.grid_cell_width)) + _this.grid_line_x_offset;
                    let top = node*_this.grid_cell_height + _this.grid_line_y_offset;
                    return "translate(" + left + "," + top + ")";
                }
            });

            // arc
            cells.append("path").attrs({
                id: (d,i) => "cell-arc-"+(node+1)+"-"+(i+1),
                d: (d) => {
                    let arc = d3.arc();
                    return arc({
                        innerRadius: 0,
                        outerRadius: Math.sqrt(d.value/Math.PI)/Math.sqrt(_this.max_root_value_in_matrix/Math.PI) * _this.max_circle_size - _this.stroke_correction + 0.28, // r defined by linear area
                        startAngle: 0,
                        endAngle: 2 * Math.PI
                    });
                },
                "fill-opacity": 0,
                class: "cell-arc pie-preview",
                transform: (d,i) => {
                    let left = (_this.width-((i+1)*_this.grid_cell_width)) + _this.grid_line_x_offset;
                    let top = node*_this.grid_cell_height + _this.grid_line_y_offset;
                    return "translate(" + left + "," + top + ")";
                }
            })
                .on("mouseover", (d,i) => _this.handleCellMouseOver(d,i,node,_this,'circle'))
                .on("mousemove", (d,i) => _this.handleCellMouseMove(d,i,node,_this,'circle'))
                .on("mouseout", (d,i) => _this.handleCellMouseOut(d,i,node,_this,'circle'))
                .on("click", (d,i) => _this.handleCellMouseClick(d,i,node,_this,'circle'));

            cells.append("text").attrs({
                class: "cell-preview-value main-color",
                "font-size": "8px",
                fill: "rgb(150, 157, 163)",
                "text-anchor": "end",
                display: "none",
                transform: (d,i) => {
                    let left = (_this.width-((i+1)*_this.grid_cell_width)) + _this.grid_line_x_offset + _this.grid_cell_width/2;
                    let top = node*_this.grid_cell_height + _this.grid_line_y_offset + _this.grid_cell_height/2;
                    return "translate(" + left + "," + top + ")";
                }
            }).text((d,i) => "");
        }
    }

    /**
     * Updates the lines and labels in the aggreset chart if they are hovered and selected
     */
    updateRows() {
        const _this = this;

        //console.log(JSON.parse(JSON.stringify(_this.matrix_active_cliques)),JSON.parse(JSON.stringify(_this.selected)));

        this.rows.selectAll(".tick>text")
            .classed("selected", (d,i) => _this.selected[_this.selected.length-i-1] || _this.hovered[_this.hovered.length-i-1])
            .classed("zeroed", (d,i) => { return _this.matrix_active_cliques[i][_this.matrix_active_cliques[i].length-1].horizontal_sum === 0 && _this.matrix_active_cliques[_this.matrix_active_cliques.length-1][i].vertical_sum === 0 });

        this.rows.selectAll(".tick>line.line-vert")
            .classed("selected", (d,i) => _this.selected[_this.selected.length-i-1] || _this.hovered[_this.hovered.length-i-1])
            .classed("zeroed", (d,i) => { return _this.matrix_active_cliques[i][_this.matrix_active_cliques[i].length-1].horizontal_sum === 0 && _this.matrix_active_cliques[_this.matrix_active_cliques.length-1][i].vertical_sum === 0 });

        this.rows.selectAll(".tick>line.line-horz")
            .classed("selected", (d,i) => _this.selected[_this.selected.length-i-1] || _this.hovered[_this.hovered.length-i-1])
            .classed("zeroed", (d,i) => { return _this.matrix_active_cliques[i][_this.matrix_active_cliques[i].length-1].horizontal_sum === 0 && _this.matrix_active_cliques[_this.matrix_active_cliques.length-1][i].vertical_sum === 0 });
    }

    /**
     * Updates the intersections by adding and removing circles or resizing them
     * @param max   the maximum size of a single genre
     */
    updateIntersections(max) {
        const _this = this;

        for(let node=0; node<this.description.length; node++) {
            let row_active = _this.matrix_active_cliques[node].slice(0,-1);

            //console.log(row_active);

            for(let j=0;j<row_active.length;j++) {
                let value =  Math.sqrt(row_active[j].value/Math.PI);
                let value_max = Math.sqrt(max/Math.PI);
                let outerRadius_circle = value / (value_max===0?1:value_max) * _this.max_circle_size;

                d3.select(("#cell-"+row_active[j].key1+"-"+row_active[j].key2) + ">circle")
                    .attr("r", outerRadius_circle); // r defined by linear area

                let arc = d3.arc();
                let outerRadius_arc = value / (value_max===0?1:value_max) * _this.max_circle_size - _this.stroke_correction + 0.28;
                let d_full_arc = arc({
                    innerRadius: 0,
                    outerRadius: outerRadius_arc, // r defined by linear area
                    startAngle: 0,
                    endAngle: 2 * Math.PI
                });

                d3.select(("#cell-"+row_active[j].key1+"-"+row_active[j].key2)+">path")
                    .attr("d", d_full_arc)
                    .attr("fill-opacity", 0);

                // rectangle
                d3.select(("#cell-"+row_active[j].key1+"-"+row_active[j].key2)+">rect").style("display", (d,i) => {return row_active[j].value>0 ? "block": "none"});
            }
        }
    }

    /**
     * Shows the preview intersections when hovering above or clicking on a circle
     * @param reset   true if the preview has to be reset
     */
    previewIntersections(reset) {
        const _this = this;
        //console.log(_this.matrix_active_cliques, _this.matrix_preview_cliques);

        for(let node=0; node<this.description.length; node++) {

            let row_active = _this.matrix_active_cliques[node].slice(0,-1);
            let row_preview = _this.matrix_preview_cliques[node].slice(0,-1);

            for(let j=0;j<row_preview.length;j++) {
                let value =  Math.sqrt(row_active[j].value/Math.PI);
                let value_max = Math.sqrt(_this.max_active_value_in_matrix/Math.PI);
                let outerRadius = value / (value_max===0?1:value_max) * _this.max_circle_size - _this.stroke_correction + 0.28;

                if(!reset) {
                    let arc = d3.arc();
                    let d_arc = arc({
                        innerRadius: 0,
                        outerRadius: outerRadius, // r defined by linear area
                        startAngle: 0,
                        endAngle: row_preview[j].value/(row_active[j].value==0?1:row_active[j].value) * 2 * Math.PI
                    });

                    d3.select(("#cell-"+row_preview[j].key1+"-"+row_preview[j].key2)+">path").attrs({
                        id: "cell-arc-"+(node+1)+"-"+(j+1),
                        d: d_arc,
                        class: "cell-arc pie-preview",
                        "fill-opacity": 1
                    });

                    if(_this.show_preview_value && row_preview[j].value !== 0 && this.previewed_cell != null && (_this.previewed_cell[0] !== _this.previewed_cell[1] && (_this.previewed_cell[0] !== node || _this.previewed_cell[1] !== j) ||
                        (_this.previewed_cell[0] === _this.previewed_cell[1] && !(_this.previewed_cell[0] === node || _this.previewed_cell[0] === j)))) {
                        d3.select(("#cell-"+row_preview[j].key1+"-"+row_preview[j].key2)+">text").style("display", "block").text((d,i) => Math.round(row_preview[j].value/(row_active[j].value==0?1:row_active[j].value)*100)+"%");
                    }
                } else {
                    let arc = d3.arc();
                    let d_full_arc = arc({
                        innerRadius: 0,
                        outerRadius: outerRadius, // r defined by linear area
                        startAngle: 0,
                        endAngle: 2 * Math.PI
                    });

                    d3.select(("#cell-"+row_preview[j].key1+"-"+row_preview[j].key2)+">path")
                        .attr("d", d_full_arc)
                        .attr("fill-opacity", 0);

                    d3.select(("#cell-"+row_preview[j].key1+"-"+row_preview[j].key2)+">text").style("display", "none");
                }
            }
        }
    }

    /**
     * Draws the legend about the circles
     */
    drawLegend() {
        const _this = this;

        if(!this.svg.g.selectAll(".legend-container").empty()) {
            this.svg.g.selectAll(".legend-container").remove();
        }

        let ref = this.svg.g.append("g").attrs({class:"legend-container"});

        let legend_inner = ref.append("g").attrs({id: "legend-circle-small",
            transform: (d,i) => "translate("+_this.legend_margin.left+","+_this.legend_margin.top+")"});
        let legend_outer = ref.append("g").attrs({id: "legend-circle-big",
            transform: (d,i) => "translate("+_this.legend_margin.left+","+_this.legend_margin.top+")"});

        let r_outer = _this.max_circle_size - _this.stroke_correction;
        let r_inner = Math.sqrt(_this.max_root_value_in_matrix*_this.legend_inner_circle_factor/Math.PI)/Math.sqrt(_this.max_root_value_in_matrix/Math.PI) * _this.max_circle_size - _this.stroke_correction;

        legend_outer.append("circle")
            .attrs({
                cx: 0,
                cy: 0,
                r: r_outer
            });
        legend_outer.append("line")
            .attrs({
                x1: 0,
                x2: r_outer*2,
                y1: -r_outer,
                y2: -r_outer,
            });
        legend_outer.append("text")
            .attrs({
                x: r_outer*2 + 3,
                y: -r_outer + 4,
                class: "chart-legend-label"
            })
            .text(() => "# " + _this.max_root_value_in_matrix);


        legend_inner.append("circle")
            .attrs({
                cx: 0,
                cy: 0,
                r: r_inner // r defined by linear area
            });
        legend_inner.append("line")
            .attrs({
                x1: 0,
                x2: r_outer*2,
                y1: r_inner,
                y2: r_inner
            });
        legend_inner.append("text")
            .attrs({
                x: r_outer*2 + 3,
                y: r_inner + 4,
                class: "chart-legend-label"
            })
            .text(() => "# " + Math.floor(_this.max_root_value_in_matrix*_this.legend_inner_circle_factor));
    }

    /**
     * Updates the legend of the circles according to the max value
     * @param max   the maximum value
     */
    updateLegend(max) {
        const _this = this;

        let legend_small_circle = d3.select("#legend-circle-small");
        legend_small_circle.select("text").text(() => "# " + Math.floor(max*_this.legend_inner_circle_factor));

        let legend_big_circle = d3.select("#legend-circle-big");
        legend_big_circle.select("text").text(() => "# " + Math.floor(max));
    }

    /**
     * Draws statistics about how many of the pairs have common intersections
     */
    drawStatistic() {
        const _this = this;
        if(!this.svg.g.selectAll(".statistic-container").empty()) {
            this.svg.g.selectAll(".statistic-container").remove();
        }

        this.svg.g.append("rect").attrs({
            x: -_this.margin.left,
            y: _this.height+1,
            width: _this.width+_this.margin.left+_this.margin.right+"px",
            height: "0.05px",
            class: "statistic-line"
        });

        let statistic_container = this.svg.g.append("g").attrs({class:"statistic-container",id: "relation-statistic",
            transform: (d,i) => "translate("+(_this.width)+","+(_this.height+_this.margin.bottom)+")"});

        let text = _this.amount_cell_pairs+" Pairs ("+(_this.amount_cell_pairs/_this.amount_cell_total*100).toFixed(2)+"%)"
        let statistic_circle = statistic_container.append("circle")
            .attrs({
                cx: 0,
                cy: 0,
                r: 4.5,
                transform: (d,i) => "translate("+(-getTextDimension(text,this.textLabelWeight + ' 12px ' + this.textLabelFont).width-5)+",-4.7)"});
        let statistic_text = statistic_container.append("text")
            .attrs({id: "statistic-text","text-anchor":"end"})
            .text(text);
    }

    /**
     * Updates statistics about the pairs that have common intersections
     */
    updateStatistic() {
        const _this = this;
        let statistic_container = this.svg.g.select("#relation-statistic");

        let text = _this.amount_cell_pairs+" Pairs ("+(_this.amount_cell_pairs/_this.amount_cell_total*100).toFixed(2)+"%)"
        statistic_container.select("circle")
            .attr("transform", "translate("+(-getTextDimension(text,this.textLabelWeight + ' 12px ' + this.textLabelFont).width-5)+",-4.7)");

        statistic_container.select("text")
            .text(text);
    }


    drawCellLegendText() {
        const _this = this;
        if(!this.svg.g.selectAll(".menu-container").empty()) {
            this.svg.g.selectAll(".menu-container").remove();
        }

        this.svg.g.append("text").attrs({
            x: 0,
            y: _this.height+_this.margin.bottom,
            id: "aggreset-cell-circle-legend-text",
            class: "text-dec-line-through",
        }).text("Show cell legend text")
            .on("mouseover", (d,i) => _this.handleCellLegendShowTextMouseOver(d,i,_this))
            .on("mouseout", (d,i) => _this.handleCellLegendShowTextMouseOut(d,i,_this))
            .on("click", (d,i) => _this.handleCellLegendShowTextClick(d,i,_this));
    }

    handleCellLegendShowTextMouseOver(d, i, _this) {
        this.svg.g.select("#aggreset-cell-circle-legend-text").classed("text-dec-line-through", _this.show_preview_value);
    }

    handleCellLegendShowTextMouseOut(d, i, _this) {
        this.svg.g.select("#aggreset-cell-circle-legend-text").classed("text-dec-line-through", !_this.show_preview_value);
    }

    handleCellLegendShowTextClick(d, i, _this) {
        _this.show_active_value = !_this.show_active_value;
        _this.show_preview_value = !_this.show_preview_value;
        this.svg.g.select("#aggreset-cell-circle-legend-text").classed("text-dec-line-through", !_this.show_active_value);
    }

    /* mouse events */
    /**
     * Handles the events when hovering with the mouse above the chart
     * @param d   the current element
     * @param i   the index
     * @param node   node from the data
     * @param _this   reference of this
     * @param type   rect or circle
     */
    handleCellMouseOver(d, i, node, _this, type) {
        let cat1 = _this.data[i];
        let cat2 = _this.data[node];
        //console.log(cat1,cat2);

        let value = _this.matrix_active_cliques[node][i].value;

        if(type === 'rect' || type === 'circle') {
            d3.select("#aggreset-cell-circle-tooltip").styles({
                display: "block",
                left: d3.event.pageX + 20 + "px",
                top: d3.event.pageY - 20 + "px",
                width: getTextDimension("# "+value,_this.labelFontSizeWeight).width + 5 + "px"
            }).text(() => "# " + value);


            _this.filterDimensionOnDescriptionKey.bind(_this);
            if(!_this.selected[_this.selected.length-i-1]) {
                _this.hovered[_this.hovered.length-i-1] = true;
                this.filters.preview.addFilterOnDimensionIndex(cat1.key-1,1,"AND");
                _this.filterDimensionOnDescriptionKey(i,1);
            }
            if(!_this.selected[_this.selected.length-node-1]) {
                _this.hovered[_this.hovered.length-node-1] = true;
                this.filters.preview.addFilterOnDimensionIndex(cat2.key-1,1,"AND");
                _this.filterDimensionOnDescriptionKey(node,1);
            }

            _this.previewed_cell = [node,i];

            _this.previewAll(false);
        }
    }

    /**
     * Handles events when moving the mouse. Displays a tooltip when above a circle.
     * @param d   the current element
     * @param i   the index
     * @param node   node from the data
     * @param _this   reference of this
     * @param type   rect or circle
     */
    handleCellMouseMove(d, i, node, _this, type) {
        let cat1 = _this.data[i];
        let cat2 = _this.data[node];
        //console.log(cat1,cat2);

        d3.select("#aggreset-cell-circle-tooltip").styles({
            left: d3.event.pageX + 20 + "px",
            top: d3.event.pageY - 20 + "px",
        });
    }

    /**
     * Handles events when the mouse is out of a circle or rectangle
     * @param d   the current element
     * @param i   the index
     * @param node   node from the data
     * @param _this   reference of this
     * @param type   rect or circle
     */
    handleCellMouseOut(d, i, node, _this, type) {
        let cat1 = _this.data[i];
        let cat2 = _this.data[node];
        //console.log(cat1,cat2);

        //console.log(d3.event, type);
        let id = (type === 'rect') ? d3.event.path[1].id : d3.event.path[0].id;

        d3.select(id)
            .attr("r", Math.sqrt(d.value/Math.PI)/Math.sqrt(_this.max_active_value_in_matrix/Math.PI) * _this.max_circle_size)
            .classed("cell-circle-hover",false);
        d3.select("#aggreset-cell-circle-tooltip").styles({display: "none"});  // Remove text location

        _this.previewed_cell = null;

        if(!_this.selected[_this.selected.length-i-1]) {
            _this.hovered[_this.hovered.length-i-1] = false;
            this.filters.preview.removeFilterOnDimensionIndex(cat1.key-1);
            _this.resetFilterDimensionOnDescriptionKey(i);
        }
        if(!_this.selected[_this.selected.length-node-1]) {
            _this.hovered[_this.hovered.length-node-1] = false;
            this.filters.preview.removeFilterOnDimensionIndex(cat2.key-1);
            _this.resetFilterDimensionOnDescriptionKey(node);
        }

        _this.previewAll(true);
    }

    /**
     * Handles the events when clicking on a rectangle or circle with the mouse
     * @param d   the current element
     * @param i   the index
     * @param node   node from the data
     * @param _this   reference of this
     */
    handleCellMouseClick(d, i, node, _this) {
        let cat1 = _this.data[i];
        let cat2 = _this.data[node];
        //console.log(cat1,cat2);

        if(!_this.selected[_this.selected.length-i-1]) {
            cat1.filtered = true;
            _this.selected[_this.selected.length-i-1] = true;
            this.filters.active.addFilterOnDimensionIndex(cat1.key-1,1,"AND");
            _this.filterDimensionOnDescriptionKey.bind(_this);
            _this.filterDimensionOnDescriptionKey(i,1);
        } else {
            cat1.filtered = false;
            _this.selected[_this.selected.length-i-1] = false;
            this.filters.active.removeFilterOnDimensionIndex(cat1.key-1);
            _this.resetFilterDimensionOnDescriptionKey(i);
        }

        if(!_this.selected[_this.selected.length-node-1]) {
            cat2.filtered = true;
            _this.selected[_this.selected.length-node-1] = true;
            this.filters.active.addFilterOnDimensionIndex(cat2.key-1,1,"AND");
            _this.filterDimensionOnDescriptionKey.bind(_this);
            _this.filterDimensionOnDescriptionKey(node,1);
        } else {
            cat2.filtered = false;
            _this.selected[_this.selected.length-node-1] = false;
            this.filters.active.removeFilterOnDimensionIndex(cat2.key-1);
            _this.resetFilterDimensionOnDescriptionKey(node);
        }

        _this.previewed_cell = null;
        _this.updateAll(true);
    }

    /**
     * Handle the events when hovering above the categories of elements (above genres in case of movies)
     * @param d   the current element
     * @param i   the index
     * @param _this   reference of this
     */
    handleCatMouseOver(d, i, _this) {
        let cat = _this.data[d.key-1];
        //console.log(cat,d.key-1,_this.matrix_active_cliques[d.key-1]);

        if(_this.matrix_active_cliques[d.key-1][_this.matrix_active_cliques[d.key-1].length-1].horizontal_sum !== 0 || _this.matrix_active_cliques[_this.matrix_active_cliques.length-1][d.key-1].vertical_sum !== 0) {
            //console.log(JSON.parse(JSON.stringify(_this.selected)),this.selected.length-d.key);

            if(!_this.selected[_this.selected.length-d.key]) {
                _this.hovered[_this.selected.length-d.key] = true;
                this.filters.preview.addFilterOnDimensionIndex(cat.key-1,1,"AND");
                _this.filterDimensionOnDescriptionKey.bind(_this);
                _this.filterDimensionOnDescriptionKey(d.key-1,1);

                _this.previewed_cell = [d.key-1,d.key-1];
            }

            _this.previewAll(false);
        }
    }

    handleCatMouseMove(d, i, _this) {

    }

    /**
     * Handles events when the mouse is out of a category
     * @param d   the current element
     * @param i   the index
     * @param _this   reference of this
     */
    handleCatMouseOut(d, i, _this) {
        let cat = _this.data[d.key-1];
        //console.log(cat,d.key-1);

        if(!_this.selected[_this.selected.length-d.key] && (_this.matrix_active_cliques[d.key-1][_this.matrix_active_cliques[d.key-1].length-1].horizontal_sum !== 0 || _this.matrix_active_cliques[_this.matrix_active_cliques.length-1][d.key-1].vertical_sum !== 0)) {
            _this.hovered[_this.selected.length-d.key] = false;
            this.filters.preview.removeFilterOnDimensionIndex(cat.key-1);
            _this.resetFilterDimensionOnDescriptionKey(d.key-1);
        }

        _this.previewed_cell = null;
        _this.previewAll(true);
    }

    /**
     * Handles the events when clicking on a category with the mouse
     * @param d   the current element
     * @param i   the index
     * @param _this   reference of this
     */
    handleCatMouseClick(d, i, _this) {
        let cat = _this.data[d.key-1];
        //console.log(cat);

        if(!_this.selected[_this.selected.length-d.key] && (_this.matrix_active_cliques[d.key-1][_this.matrix_active_cliques[d.key-1].length-1].horizontal_sum !== 0 || _this.matrix_active_cliques[_this.matrix_active_cliques.length-1][d.key-1].vertical_sum !== 0)) {
            cat.filtered = true;
            _this.selected[_this.selected.length-d.key] = true;
            this.filters.active.addFilterOnDimensionIndex(cat.key-1,1,"AND");
            _this.filterDimensionOnDescriptionKey.bind(_this);
            _this.filterDimensionOnDescriptionKey(d.key-1,1);
        } else {
            cat.filtered = false;
            _this.selected[_this.selected.length-d.key] = false;
            this.filters.active.removeFilterOnDimensionIndex(cat.key-1);
            _this.resetFilterDimensionOnDescriptionKey(d.key-1);
        }

        _this.updateAll(true);
    }


    /**
     *  Initial matrix calculation to reduce total number of rows for later filtering and re-rendering
     */
    calcRootCliquesInMatrix() {
        let matrix = new Array(this.description.length);
        this.amount_cell_pairs = 0;
        let vertical_sum = new Array(this.description.length).fill(0);
        for(let i=0;i<this.description.length;i++) {
            matrix[i] = new Array(i+1);
            this.dimension[this.data[i].key-1].filter(1);
            let horizontal_sum = 0;

            for(let j=0;j<=i;j++) {
                this.dimension[this.data[j].key-1].filter(1);

                let rows = this.dimension[this.data[i].key-1].top(Infinity);
                horizontal_sum += (i===j) ? 0 : rows.length;
                vertical_sum[j] += (i===j) ? 0 :rows.length;

                if(i!==j && rows.length !== 0) {
                    this.amount_cell_pairs++;
                }

                matrix[i][j] = { key1: i+1, key2: j+1, value: rows.length, rows: rows, horizontal_sum: horizontal_sum, vertical_sum: vertical_sum[j]};
                this.dimension[this.data[j].key-1].filterAll();
            }
            this.dimension[this.data[i].key-1].filterAll();
        }
        return matrix;
    }

    /**
     * Re-calculation of clique matrix with reduced number of rows
     */
    calcCliquesInMatrix(ref_matrix,type) {
        const _this = this;
        let matrix = new Array(this.description.length);
        this.amount_cell_pairs = 0;
        let vertical_sum = new Array(this.description.length).fill(0);
        for(let i=0;i<this.description.length;i++) {
            matrix[i] = new Array(i+1);
            let horizontal_sum = 0;

            for(let j=0;j<=i;j++) {
                let cell = ref_matrix[i][j];
                let rows = cell.rows;

                let preview_rows = (i===j) ? rows : _this.filterCustomData(rows,type);
                horizontal_sum += (i===j) ? 0 : preview_rows.length;
                vertical_sum[j] += (i===j) ? 0 : preview_rows.length;

                if(i!==j && preview_rows.length !== 0) {
                    this.amount_cell_pairs++;
                }

                matrix[i][j] = { key1: i+1, key2: j+1, value: preview_rows.length, rows: preview_rows, horizontal_sum: horizontal_sum, vertical_sum: vertical_sum[j]};
            }

        }
        return matrix;
    }
}