/**
* Copyright (c) 2019
*
* @summary chart class
* @author Elitza Vasileva
* @author Bernhard Pointner
*/
/**
* Chart class
*/
class Chart {
constructor(_parent, _dummyId, _id, _title, _subtitle, _is_cat_list, _dimension, _group, _description, _filters, _selected, _hovered, _filterCustomData, _updateAll, _previewAll) {
this.id = _id;
this.title = _title;
this.subtitle = _subtitle;
this.parent = _parent;
this.dimension = _dimension; // unsorted!!!
this.group = _group; // unsorted!!!
this.description = _description; // unsorted!!!
this.filters = _filters; // filters consisting of active filter and preview filters
this.filterCustomData = _filterCustomData;
this.updateAll = _updateAll;
this.previewAll = _previewAll;
this.data = null;
this.preview_data = null;
this.filtered_data = null;
// selected and hovered array - must same order as group
this.selected = _selected;
this.hovered = _hovered;
this.filter_button = null;
this.old_filtered_data = null;
this.old_extent_labels = null;
this.current_extent_labels = null;
this.is_cat_list = _is_cat_list; // special chart because of multiple dimensions for one single chart
this.preview_active = false;
this.filter_active = false;
this.hover_active = false;
this.sort_by = [];
this.container = addNewVis(_parent, _dummyId, this.id, this.title, this.subtitle);
this.margin = null;
this.width = null;
this.height = null;
this.svg = this.container.append("svg");
this.textLabelFont = 'sans-serif';
this.textLabelSize = "14px"
this.textLabelWeight = "400";
this.labelFontSizeWeight = this.textLabelWeight + ' ' + this.textLabelSize + ' ' + this.textLabelFont;
this.addFilterButtonClickEvents();
this.updateData(false);
}
/**
* Updates the data when filtering over the charts
* @param preview true when the preview data has to be updated
* @param reset_preview true when the updated preview data has to be reset
*/
updateData(preview, reset_preview) {
if(preview) {
let old_data = JSON.parse(JSON.stringify(this.preview_data)); // deep clone
if(reset_preview) {
this.preview_data = JSON.parse(JSON.stringify(this.data)); // deep clone
this.filtered_data = JSON.parse(JSON.stringify(this.preview_data)); // deep clone
} else {
if(!this.is_cat_list){
this.preview_data = this.group.all().map((group,i) => new Object({
key: group.key,
title: group.key,
value: group.value,
filtered: false
}));
} else {
this.preview_data = this.group.map(group => group.all()).map((group,i) => new Object({
key: this.description[i].key,
title: this.description[i].title,
value: (typeof group[1] === "undefined") ? 0 : group[1].value,
filtered: false
}));
}
}
// sort data by reference to get it in the same order like active data
this.sortDataByReference(this.preview_data, this.data.map(el => el.key), "key");
// reset selected status
this.resetFiltered(this.preview_data,old_data);
// reset value if selected
this.filtered_data = JSON.parse(JSON.stringify(this.preview_data)); // deep clone
this.setFilteredData(this.filtered_data);
} else {
let old_data = JSON.parse(JSON.stringify(this.data)); // deep clone
if (!this.is_cat_list) {
this.data = this.group.all().map((group, i) => new Object({
key: group.key,
title: group.key,
value: group.value,
filtered: false
}));
} else {
this.data = this.group.map(group => group.all()).map((group, i) => new Object({
key: this.description[i].key,
title: this.description[i].title,
value: (typeof group[1] === "undefined") ? 0 : group[1].value,
filtered: false
}));
}
if(old_data !== null) {
// sort data by reference to get it in the same order like active data
this.sortDataByReference(this.data,old_data.map(el=>el.key),"key");
// reset selected status
this.resetFiltered(this.data,old_data);
// reset value if selected
this.filtered_data = JSON.parse(JSON.stringify(this.data)); // deep clone
this.setFilteredData(this.filtered_data);
}
this.preview_data = JSON.parse(JSON.stringify(this.data)); // deep clone
}
}
// TODO: override
sortData() {}
/**
Sorts data by a reference array of keys
@data data to be sorted
@key_array reference array of keys
@key object key on which the data should be sorted
*/
sortDataByReference(data,key_array,key) {
function sortFunc(a, b) {
return key_array.indexOf(a[key]) - key_array.indexOf(b[key]);
}
data.sort(sortFunc.bind(key_array));
}
/**
Resets filtered status of entry
@data data to be sorted
@old_data .. old data
*/
resetFiltered(data,old_data) {
if(old_data != null) {
data.forEach((el,i) => {
el.filtered = old_data[i].filtered;
});
}
}
/**
Resets value if selected
@data data to be sorted
*/
setFilteredData(data_filtered) {
if(data_filtered != null) {
data_filtered.forEach((el, i) => {
el.value = el.filtered ? el.value : 0;
});
}
}
// TODO: override
draw() {}
// TODO: override
update() {}
// TODO: override
preview(reset) {}
// TODO: override
drawStatistic() {}
// TODO: override
updateStatistic() {}
// add filter button click events
addFilterButtonClickEvents() {
const _this = this;
this.filter_button = d3.select("#"+this.id+" .vis-filter-container");
this.filter_button.on("click", _this.handleFilterButtonMouseClick.bind(_this));
}
// handle filter button
handleFilterButtonMouseClick() {
let visible = this.filter_button.style("display") === "block";
if(visible) {
this.filter_button.style("display", "none");
this.resetAllFilterDimensions();
} else {
this.filter_button.style("display", "block");
}
}
/**
* Show filter button
* @param show true || false
*/
showFilterButton(show) {
this.filter_button.style("display", show ? "block" : "none");
}
/**
* Set filter on crossfilter dimension
* @param index of data because dimension is unsorted => index == -1 no array available for dimension
* @param match
*/
filterDimensionOnDescriptionKey(index,match) {
// note: key must be the index of the dimension
if(index != -1) {
this.dimension[this.data[index].key-1].filter(match);
} else {
this.dimension.filter(match);
}
}
/**
* Reset filter on crossfilter dimension
* @param index of data because dimension is unsorted
* index index of dimension if chart is cat list
*/
resetFilterDimensionOnDescriptionKey(index) {
// note: key must be the index of the dimension
if(this.is_cat_list) {
this.dimension[this.data[index].key-1].filterAll();
} else {
this.dimension.filterAll();
}
}
/**
* Resets all the filter dimensions for all charts
*/
resetAllFilterDimensions() {
const _this = this;
this.data.forEach((el,i) => {
_this.selected[i] = false;
_this.hovered[i] = false;
if(el.filtered) {
el.filtered = false;
_this.filters.active.resetFilter();
_this.filters.preview.resetFilter();
_this.resetFilterDimensionOnDescriptionKey(i);
}
});
this.old_filtered_data = null;
this.old_extent_labels = null;
this.current_extent_labels = null;
this.preview_active = false;
this.filter_active = false;
this.hover_active = false;
_this.updateAll(true);
}
/**
* Checks if the data has been filtered
*/
checkIfDataIsFiltered() {
let num_filter_active = this.is_cat_list ? this.selected.filter(el=>el) : (this.current_extent_labels !== null ? this.current_extent_labels : []);
this.showFilterButton(num_filter_active.length !== 0);
}
}