blob: 0406755761a9541311faa4e336ec421cd3200af2 [file] [log] [blame]
'use strict';
// Register the table component for displaying data.
// This is a child for the app Vue instance, so it might
// access some of the app's fields.
Vue.component('data-table', {
template: '#table-template',
props: {
data: Array,
columns: Array,
filterKey: String,
additional: Array,
plot: String
},
mounted() {
// TODO(anthonyalridge): Should also update table state.
const jumpToStory = (story) => {
this.plot = 'Cumulative frequency plot';
const diagnostic = 'labels';
app.plotSingleMetric(
this.state.metric.metric,
story,
diagnostic,
this.state.markedTableDiagnostics,
this.plot);
};
app.$on('bar_clicked', jumpToStory);
app.pushCurrentState();
},
data() {
const sort = {};
this.columns.forEach(function(key) {
sort[key] = 1;
});
return {
state: {
sortKey: '',
sortOrders: sort,
openedMetric: [],
openedStory: [],
storiesEntries: null,
metric: null,
story: null,
diagnostic: 'storysetRepeats',
selected_diagnostics: [],
markedTableMetrics: [],
markedTableStories: [],
markedTableDiagnostics: [],
},
};
},
computed: {
// Filter data from one column.
filteredData() {
const sortKey = this.state.sortKey;
const filterKey = this.filterKey && this.filterKey.toLowerCase();
const order = this.state.sortOrders[sortKey] || 1;
let data = this.data;
if (filterKey) {
data = data.filter(function(row) {
return Object.keys(row).some(function(key) {
return String(row[key]).toLowerCase().indexOf(filterKey) > -1;
});
});
}
if (sortKey) {
data = data.slice().sort(function(a, b) {
a = a[sortKey];
b = b[sortKey];
return (a === b ? 0 : a > b ? 1 : -1) * order;
});
}
return data;
},
// All sub-diagnostics must be visible just after the user
// has already chosen a specific diagnostic and all the
// options for that one are now available.
seen_diagnostics() {
return this.diagnostics_options.length > 0 ? true : false;
},
// Compute all the options for sub-diagnostics after the user
// has already chosen a specific diagnostic.
// Depending on the GUID of that diagnostic, the value can be
// a string, a number or undefined.
diagnostics_options() {
if (this.state.story !== null &&
this.state.metric !== null &&
this.state.diagnostic !== null) {
const sampleArr = this.$parent.sampleArr;
const guidValue = this.$parent.guidValue;
const result = sampleArr
.filter(value => value.name === this.state.metric.metric &&
guidValue
.get(value.diagnostics.stories)[0] ===
this.state.story.story);
const content = [];
for (const val of result) {
const diagnosticItem = guidValue.get(
val.diagnostics[this.state.diagnostic]);
if (diagnosticItem === undefined) {
continue;
}
let currentDiagnostic = '';
if (typeof diagnosticItem === 'number') {
currentDiagnostic = diagnosticItem.toString();
} else {
currentDiagnostic = diagnosticItem[0];
if (typeof currentDiagnostic === 'number') {
currentDiagnostic = currentDiagnostic.toString();
}
}
content.push(currentDiagnostic);
}
return _.uniq(content);
}
return undefined;
}
},
// Capitalize the objects field names.
filters: {
capitalize(str) {
if (typeof str === 'number') {
return str.toString();
}
return str.charAt(0).toUpperCase() + str.slice(1);
}
},
methods: {
uncheckLabelsButtons() {
const checkboxes = document.getElementsByClassName('checkbox-head');
Array.prototype.map.call(checkboxes, function(checkbox) {
checkbox.checked = false;
});
this.markedTableDiagnostics = [];
},
// Sort by key where the key is a title head in table.
sortBy(key) {
this.state.sortKey = key;
this.state.sortOrders[key] = this.state.sortOrders[key] * -1;
},
// Remove all the selected items from the array.
// Especially for the cases when the user changes the mind and select
// another high level diagnostic and the selected sub-diagnostics
// will not be usefull anymore.
empty() {
this.state.selected_diagnostics = [];
},
// Compute all the sample values depending on a single
// metric for each stories and for multiple sub-diagnostics.
getSampleByStoryBySubdiagnostics(entry, sampleArr, guidValue, globalDiag) {
const diagValues = new Map();
for (const e of sampleArr) {
if (e.name !== entry.metric) {
continue;
}
let nameOfStory = guidValue.get(e.diagnostics.stories);
if (nameOfStory === undefined) {
continue;
}
if (typeof nameOfStory !== 'number') {
nameOfStory = nameOfStory[0];
}
let diagnostic = guidValue.
get(e.diagnostics[globalDiag]);
if (diagnostic === undefined) {
continue;
}
if (diagnostic !== 'number') {
diagnostic = diagnostic[0];
}
if (!diagValues.has(nameOfStory)) {
const map = new Map();
map.set(diagnostic, [average(e.sampleValues)]);
diagValues.set(nameOfStory, map);
} else {
const map = diagValues.get(nameOfStory);
if (!map.has(diagnostic)) {
map.set(diagnostic, [average(e.sampleValues)]);
diagValues.set(nameOfStory, map);
} else {
const array = map.get(diagnostic);
array.push(average(e.sampleValues));
map.set(diagnostic, array);
diagValues.set(nameOfStory, map);
}
}
}
return diagValues;
},
getStoriesByMetric(entry, sampleArr, guidValue) {
const stories = [];
for (const e of sampleArr) {
if (e.name !== entry) {
continue;
}
let nameOfStory = guidValue.get(e.diagnostics.stories);
if (nameOfStory === undefined) {
continue;
}
if (typeof nameOfStory !== 'number') {
nameOfStory = nameOfStory[0];
}
stories.push(nameOfStory);
}
return _.uniq(stories);
},
// This method will be called when the user clicks a specific
// 'row in table' = 'metric' and we have to provide the stories for that.
// Also all the previous choices must be removed.
toggleMetric(entry) {
const index = this.state.openedMetric.indexOf(entry.id);
if (index > -1) {
this.state.openedMetric.splice(index, 1);
} else {
this.state.openedMetric.push(entry.id);
}
const sampleArr = this.$parent.sampleArr;
const guidValue = this.$parent.guidValue;
const globalDiag = this.$parent.globalDiagnostic;
const addCol = this.$parent.columnsForChosenDiagnostic;
const storiesEntries = [];
const stories = this
.getStoriesByMetric(entry.metric, sampleArr, guidValue);
for (const key of stories) {
storiesEntries.push({
story: key
});
}
if (addCol !== null) {
const diagValues = this
.getSampleByStoryBySubdiagnostics(entry,
sampleArr, guidValue, 'labels');
for (const e of storiesEntries) {
for (const diag of addCol) {
e[diag] = fromBytesToMiB(average(diagValues
.get(e.story).get(diag)));
}
}
}
this.uncheckLabelsButtons();
this.state.storiesEntries = storiesEntries;
this.state.metric = entry;
this.empty();
},
// This method will be called when the user clicks a specific
// story row and we have to compute all the available diagnostics.
// Also all the previous choices regarding a diagnostic must be removed.
toggleStory(story) {
const index = this.state.openedStory.indexOf(story.story);
if (index > -1) {
this.state.openedStory.splice(index, 1);
} else {
this.state.openedStory.push(story.story);
}
const sampleArr = this.$parent.sampleArr;
const guidValue = this.$parent.guidValue;
const result = sampleArr
.filter(value => value.name === this.state.metric.metric &&
guidValue
.get(value.diagnostics.stories)[0] ===
story.story);
const allDiagnostic = [];
result.map(val => allDiagnostic.push(Object.keys(val.diagnostics)));
this.state.story = story;
app.plotSingleMetricWithAllSubdiagnostics(this.state.metric.metric,
this.state.story.story, this.state.diagnostic);
this.empty();
},
createPlot() {
let diagnostic = this.state.diagnostic;
let diagnostics = [];
if (this.state.markedTableDiagnostics.length !== 0) {
diagnostic = 'labels';
diagnostics = this.state.markedTableDiagnostics;
} else
if (this.state.selected_diagnostics.length !== 0) {
diagnostics = this.state.selected_diagnostics;
} else {
diagnostics = this.diagnostics_options;
}
app.plotSingleMetric(
this.state.metric.metric,
this.state.story.story,
diagnostic,
diagnostics,
this.plot);
},
// When the user pick a new metric for further analysis
// this one has to be stored. If this is already stored
// this means that the action is the reverse one: unpick.
pickTableMetric(entry) {
if (this.state.markedTableMetrics.includes(entry.metric)) {
this.state.markedTableMetrics.splice(
this.state.markedTableMetrics.indexOf(entry.metric), 1);
} else {
this.state.markedTableMetrics.push(entry.metric);
}
},
// Whenever the user pick a new metric for further analysis
// this one has to be stored. If it is already stored,
// this means that the user actually unpicked it.
pickTableStory(entry) {
if (this.state.markedTableStories.includes(entry.story)) {
this.state.markedTableStories.splice(
this.state.markedTableStories.indexOf(entry.story), 1);
} else {
this.state.markedTableStories.push(entry.story);
}
},
// The same for pickTableMetric and pickTableStory.
pickHeadTable(title) {
if (this.state.markedTableDiagnostics.includes(title)) {
this.state.markedTableDiagnostics.splice(
this.state.markedTableDiagnostics.indexOf(title), 1);
} else {
this.state.markedTableDiagnostics.push(title);
}
},
// Draw a bar chart when multiple stories are selected
// from a single metric and multiple sub-diagnostics are
// selected froma a single main diagnostic.
plotMultipleStoriesMultipleDiag() {
if (this.state.markedTableDiagnostics.length !== 0) {
const sampleArr = this.$parent.sampleArr;
const guidValue = this.$parent.guidValue;
const map = this
.getSampleByStoryBySubdiagnostics(this.state.metric,
sampleArr, guidValue, 'labels');
const data = {};
for (const e of this.state.markedTableDiagnostics) {
const obj = {};
for (const story of this.state.markedTableStories) {
obj[story] = map.get(story).get(e);
}
data[e] = obj;
}
app.plotBarChart(data);
}
},
// When the user selects a specific row from the table
// this does not mean that it is the only one metric
// with that name, so we have to extract all available
// metrics from sampleValues.
getAllMetricsFromMetricRow() {
const sampleArr = this.$parent.sampleArr;
const markedMetrics = [];
for (const metric of sampleArr) {
for (const e of this.state.markedTableMetrics) {
if (metric.name === e) {
markedMetrics.push(metric);
}
}
}
return markedMetrics;
},
// The metrics from grid are the ones that come
// after selecting items from tree-menu.
// We need to filter just that metrics from the total
// sampleValues metrics.
getMetricsFromGrid() {
const sampleArr = this.$parent.sampleArr;
const gridData = this.$parent.state.gridData;
const metricsDependingOnGrid = [];
for (const metric of sampleArr) {
for (const e of gridData) {
if (metric.name === e.metric) {
metricsDependingOnGrid.push(metric);
}
}
}
return metricsDependingOnGrid;
}
},
watch: {
// Whenever a new diagnostic is chosen or removed, the graph
// is replotted because these are displayed in the same plot
// by comparison and it should be updated.
'state.selected_diagnostics'() {
if (this.state.selected_diagnostics.length !== 0) {
this.uncheckLabelsButtons();
this.createPlot();
}
},
// Whenever the chosen plot is changed by the user it has to
// be created another type of plot with the same specifications.
plot() {
if (this.plot === 'Cumulative frequency plot' ||
this.plot === 'Dot plot') {
this.createPlot();
}
},
// Whenever the top level diagnostic is changed all the previous
// selected sub-diagnostics have to be removed. Otherwise the old
// selections will be displayed. Also the plot is displayed with
// values for all available sub-diagnostics.
'state.diagnostic'() {
this.empty();
app.plotSingleMetricWithAllSubdiagnostics(this.state.metric.metric,
this.state.story.story, this.state.diagnostic);
},
// Whenever a new subdiagnostic from table columns is chosen
// it is added to the chart. Depending on the main diagnostic
// and its subdiagnostics, all the sample values for a particular
// metric, multiple stories, a single main diagnostic and multiple
// subdiagnostics are computed. The plot is drawn using this data.
'state.markedTableDiagnostics'() {
if (this.state.markedTableDiagnostics.length !== 0) {
const sampleArr = this.$parent.sampleArr;
const guidValue = this.$parent.guidValue;
if (this.state.markedTableMetrics.length === 0) {
if (this.$parent.state.chosenTypeOfPlot === 'Stacked bar plot') {
const markedMetrics = this.getMetricsFromGrid();
const stories = this.getStoriesByMetric(
app.state.gridData[0].metric, sampleArr, guidValue);
const obj = app.computeDataForStackPlot(markedMetrics,
stories, this.state.markedTableDiagnostics);
const string = 'Stacked plot';
app.plotStackBar(obj, string);
} else if (this.$parent.chosenTypeOfPlot === 'Bar chart plot') {
this.plotMultipleStoriesMultipleDiag();
} else {
this.createPlot();
this.selected_diagnostics = [];
}
} else {
const markedMetrics = this.getAllMetricsFromMetricRow();
let stories = [];
if (this.markedTableStories.length === 0) {
stories = this.getStoriesByMetric(app
.gridData[0].metric, sampleArr, guidValue);
} else {
stories = this.markedTableStories;
}
const obj = app.computeDataForStackPlot(markedMetrics,
stories, this.state.markedTableDiagnostics);
const string = 'Stacked plot';
app.plotStackBar(obj, string);
}
}
},
// Whenever a new story from table is chosen it has to be added
// in the final chart. The chart that should be updated might be
// a stacked chart or a bar chart in this particular case.
'state.markedTableStories'() {
if (this.state.markedTableMetrics.length === 0) {
// In this case the user wants to change the stories for
// the initial stacked plot obtained using all the metrics
// from grid, all the stories from top level metric and
// all the available options.
if (this.$parent.state.chosenTypeOfPlot === 'Stacked bar plot') {
const markedMetrics = this.getMetricsFromGrid();
const labelsName = this.$parent.columnsForChosenDiagnostic;
const obj = app.computeDataForStackPlot(markedMetrics,
this.state.markedTableStories, labelsName);
const string = 'Stacked plot';
app.plotStackBar(obj, string);
} else {
this.plotMultipleStoriesMultipleDiag();
}
} else {
// The user wants to change the stories after having
// some selected metrics.
const markedMetrics = this.getAllMetricsFromMetricRow();
const labelsName = this.$parent.columnsForChosenDiagnostic;
const obj = app.computeDataForStackPlot(markedMetrics,
this.state.markedTableStories, labelsName);
const string = 'Stacked plot';
app.plotStackBar(obj, string);
}
},
// Whenever the main selected metric from the table is changed
// all marked diagnostics have to be removed because they are
// not available.
'state.metric'() {
this.state.markedTableDiagnostics = [];
},
// Whenever a new metric is selected the stacked chart should
// be updated.
'state.markedTableMetrics'() {
const sampleArr = this.$parent.sampleArr;
const guidValue = this.$parent.guidValue;
// As sources for final objet:
// 1) the metrics are taken from sampleValues; these
// should have the same same as the selected row;
const markedMetrics = this.getAllMetricsFromMetricRow();
// 2) the stories are the ones from the top level metric;
const stories = this.getStoriesByMetric(
app.state.gridData[0].metric, sampleArr, guidValue);
// 3) the labels are all the available labels;
let labelsName = [];
if (this.state.markedTableDiagnostics.length !== 0) {
labelsName = this.state.markedTableDiagnostics;
} else {
labelsName = this.$parent.columnsForChosenDiagnostic;
}
const obj = app.computeDataForStackPlot(markedMetrics,
stories, labelsName);
const string = 'Stacked plot';
app.plotStackBar(obj, string);
}
}
});