blob: b3e6ec654e25266a87c5b229da04da07b28f4bbf [file] [log] [blame]
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Visualization from '../visualization';
/**
* Visualize data in table format
*/
export default class Nvd3ChartVisualization extends Visualization {
constructor(targetEl, config) {
super(targetEl, config);
this.targetEl.append('<svg></svg>');
}
refresh() {
if (this.chart) {
this.chart.update();
}
}
render(data) {
let type = this.type();
let d3g = data.d3g;
if (!this.chart) {
this.chart = nv.models[type]();
}
this.configureChart(this.chart);
let animationDuration = 300;
let numberOfDataThreshold = 150;
let height = this.targetEl.height();
// turn off animation when dataset is too large. (for performance issue)
// still, since dataset is large, the chart content sequentially appears like animated
try {
if (d3g[0].values.length > numberOfDataThreshold) {
animationDuration = 0;
}
} catch (err) { /** ignore */ }
d3.select('#' + this.targetEl[0].id + ' svg')
.attr('height', height)
.datum(d3g)
.transition()
.duration(animationDuration)
.call(this.chart);
d3.select('#' + this.targetEl[0].id + ' svg').style.height = height + 'px';
}
type() {
// override this and return chart type
}
configureChart(chart) {
// override this to configure chart
}
groupedThousandsWith3DigitsFormatter(x) {
return d3.format(',')(d3.round(x, 3));
}
customAbbrevFormatter(x) {
let s = d3.format('.3s')(x);
switch (s[s.length - 1]) {
case 'G': return s.slice(0, -1) + 'B';
}
return s;
}
defaultY() {
return 0;
}
xAxisTickFormat(d, xLabels) {
if (xLabels[d] && (isNaN(parseFloat(xLabels[d])) || !isFinite(xLabels[d]))) { // to handle string type xlabel
return xLabels[d];
} else {
return d;
}
}
yAxisTickFormat(d) {
if (Math.abs(d) >= Math.pow(10, 6)) {
return this.customAbbrevFormatter(d);
}
return this.groupedThousandsWith3DigitsFormatter(d);
}
d3DataFromPivot(
schema, rows, keys, groups, values, allowTextXAxis, fillMissingValues, multiBarChart) {
let self = this;
// construct table data
let d3g = [];
let concat = function(o, n) {
if (!o) {
return n;
} else {
return o + '.' + n;
}
};
const getSchemaUnderKey = function(key, s) {
for (let c in key.children) {
if(key.children.hasOwnProperty(c)) {
s[c] = {};
getSchemaUnderKey(key.children[c], s[c]);
}
}
};
const traverse = function(sKey, s, rKey, r, func, rowName, rowValue, colName) {
// console.log("TRAVERSE sKey=%o, s=%o, rKey=%o, r=%o, rowName=%o, rowValue=%o, colName=%o", sKey, s, rKey, r, rowName, rowValue, colName);
if (s.type === 'key') {
rowName = concat(rowName, sKey);
rowValue = concat(rowValue, rKey);
} else if (s.type === 'group') {
colName = concat(colName, rKey);
} else if (s.type === 'value' && sKey === rKey || valueOnly) {
colName = concat(colName, rKey);
func(rowName, rowValue, colName, r);
}
for (let c in s.children) {
if (fillMissingValues && s.children[c].type === 'group' && r[c] === undefined) {
let cs = {};
getSchemaUnderKey(s.children[c], cs);
traverse(c, s.children[c], c, cs, func, rowName, rowValue, colName);
continue;
}
for (let j in r) {
if (s.children[c].type === 'key' || c === j) {
traverse(c, s.children[c], j, r[j], func, rowName, rowValue, colName);
}
}
}
};
const valueOnly = (keys.length === 0 && groups.length === 0 && values.length > 0);
let noKey = (keys.length === 0);
let isMultiBarChart = multiBarChart;
let sKey = Object.keys(schema)[0];
let rowNameIndex = {};
let rowIdx = 0;
let colNameIndex = {};
let colIdx = 0;
let rowIndexValue = {};
for (let k in rows) {
if (rows.hasOwnProperty(k)) {
traverse(sKey, schema[sKey], k, rows[k], function(rowName, rowValue, colName, value) {
// console.log("RowName=%o, row=%o, col=%o, value=%o", rowName, rowValue, colName, value);
if (rowNameIndex[rowValue] === undefined) {
rowIndexValue[rowIdx] = rowValue;
rowNameIndex[rowValue] = rowIdx++;
}
if (colNameIndex[colName] === undefined) {
colNameIndex[colName] = colIdx++;
}
let i = colNameIndex[colName];
if (noKey && isMultiBarChart) {
i = 0;
}
if (!d3g[i]) {
d3g[i] = {
values: [],
key: (noKey && isMultiBarChart) ? 'values' : colName,
};
}
let xVar = isNaN(rowValue) ? ((allowTextXAxis) ? rowValue : rowNameIndex[rowValue]) : parseFloat(rowValue);
let yVar = self.defaultY();
if (xVar === undefined) {
xVar = colName;
}
if (value !== undefined) {
yVar = isNaN(value.value) ? self.defaultY() : parseFloat(value.value) / parseFloat(value.count);
}
d3g[i].values.push({
x: xVar,
y: yVar,
});
});
}
}
// clear aggregation name, if possible
let namesWithoutAggr = {};
let colName;
let withoutAggr;
// TODO - This part could use som refactoring - Weird if/else with similar actions and variable names
for (colName in colNameIndex) {
if (colNameIndex.hasOwnProperty(colName)) {
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
if (!namesWithoutAggr[withoutAggr]) {
namesWithoutAggr[withoutAggr] = 1;
} else {
namesWithoutAggr[withoutAggr]++;
}
}
}
if (valueOnly) {
for (let valueIndex = 0; valueIndex < d3g[0].values.length; valueIndex++) {
colName = d3g[0].values[valueIndex].x;
if (!colName) {
continue;
}
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
if (namesWithoutAggr[withoutAggr] <= 1) {
d3g[0].values[valueIndex].x = withoutAggr;
}
}
} else {
for (let d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) {
colName = d3g[d3gIndex].key;
withoutAggr = colName.substring(0, colName.lastIndexOf('('));
if (namesWithoutAggr[withoutAggr] <= 1) {
d3g[d3gIndex].key = withoutAggr;
}
}
// use group name instead of group.value as a column name, if there're only one group and one value selected.
if (groups.length === 1 && values.length === 1) {
for (let d3gIndex = 0; d3gIndex < d3g.length; d3gIndex++) {
colName = d3g[d3gIndex].key;
colName = colName.split('.').slice(0, -1).join('.');
d3g[d3gIndex].key = colName;
}
}
}
return {
xLabels: rowIndexValue,
d3g: d3g,
};
}
/**
* method will be invoked when visualization need to be destroyed.
* Don't need to destroy this.targetEl.
*/
destroy() {
if (this.chart) {
d3.selectAll('#' + this.targetEl[0].id + ' svg > *').remove();
this.chart = undefined;
}
}
}