/**
* 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;
}
}