{{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
index 141b059e2ca..09da9443191 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.controller.js
@@ -99,12 +99,91 @@
var angularObjectRegistry = {};
+ /**
+ * Built-in visualizations
+ */
+ $scope.builtInTableDataVisualizationList = [
+ {
+ id: 'table', // paragraph.config.graph.mode
+ name: 'Table', // human readable name. tooltip
+ icon: 'fa fa-table'
+ },
+ {
+ id: 'multiBarChart',
+ name: 'Bar Chart',
+ icon: 'fa fa-bar-chart',
+ transformation: 'pivot'
+ },
+ {
+ id: 'pieChart',
+ name: 'Pie Chart',
+ icon: 'fa fa-pie-chart',
+ transformation: 'pivot'
+ },
+ {
+ id: 'stackedAreaChart',
+ name: 'Area Chart',
+ icon: 'fa fa-area-chart',
+ transformation: 'pivot'
+ },
+ {
+ id: 'lineChart',
+ name: 'Line Chart',
+ icon: 'fa fa-line-chart',
+ transformation: 'pivot'
+ },
+ {
+ id: 'scatterChart',
+ name: 'Scatter Chart',
+ icon: 'cf cf-scatter-chart'
+ }
+ ];
+
+ /**
+ * Holds class and actual runtime instance and related infos of built-in visualizations
+ */
+ var builtInVisualizations = {
+ 'table': {
+ class: zeppelin.TableVisualization,
+ instance: undefined // created from setGraphMode()
+ },
+ 'multiBarChart': {
+ class: zeppelin.BarchartVisualization,
+ instance: undefined
+ },
+ 'pieChart': {
+ class: zeppelin.PiechartVisualization,
+ instance: undefined
+ },
+ 'stackedAreaChart': {
+ class: zeppelin.AreachartVisualization,
+ instance: undefined
+ },
+ 'lineChart': {
+ class: zeppelin.LinechartVisualization,
+ instance: undefined
+ },
+ 'scatterChart': {
+ class: zeppelin.ScatterchartVisualization,
+ instance: undefined
+ }
+ };
+
+ /**
+ * TableData instance
+ */
+ var tableData;
+
+ // available columns in tabledata
+ $scope.tableDataColumns = [];
+
// Controller init
$scope.init = function(newParagraph, note) {
$scope.paragraph = newParagraph;
$scope.parentNote = note;
$scope.originalText = angular.copy(newParagraph.text);
$scope.chart = {};
+ $scope.baseMapOption = ['Streets', 'Satellite', 'Hybrid', 'Topo', 'Gray', 'Oceans', 'Terrain'];
$scope.colWidthOption = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
$scope.paragraphFocused = false;
if (newParagraph.focus) {
@@ -117,7 +196,10 @@
initializeDefault();
if ($scope.getResultType() === 'TABLE') {
- $scope.loadTableData($scope.paragraph.result);
+ var TableData = zeppelin.TableData;
+ tableData = new TableData();
+ tableData.loadParagraphResult($scope.paragraph.result);
+ $scope.tableDataColumns = tableData.columns;
$scope.setGraphMode($scope.getGraphMode(), false, false);
} else if ($scope.getResultType() === 'HTML') {
$scope.renderHtml();
@@ -517,22 +599,6 @@
commitParagraph($scope.paragraph.title, $scope.paragraph.text, newConfig, newParams);
};
- $scope.toggleLineWithFocus = function() {
- var mode = $scope.getGraphMode();
-
- if (mode === 'lineWithFocusChart') {
- $scope.setGraphMode('lineChart', true);
- return true;
- }
-
- if (mode === 'lineChart') {
- $scope.setGraphMode('lineWithFocusChart', true);
- return true;
- }
-
- return false;
- };
-
$scope.loadForm = function(formulaire, params) {
var value = formulaire.defaultValue;
if (params[formulaire.name]) {
@@ -933,55 +999,6 @@
return cell;
};
- $scope.loadTableData = function(result) {
- if (!result) {
- return;
- }
- if (result.type === 'TABLE') {
- var columnNames = [];
- var rows = [];
- var array = [];
- var textRows = result.msg.split('\n');
- result.comment = '';
- var comment = false;
-
- for (var i = 0; i < textRows.length; i++) {
- var textRow = textRows[i];
- if (comment) {
- result.comment += textRow;
- continue;
- }
-
- if (textRow === '') {
- if (rows.length > 0) {
- comment = true;
- }
- continue;
- }
- var textCols = textRow.split('\t');
- var cols = [];
- var cols2 = [];
- for (var j = 0; j < textCols.length; j++) {
- var col = textCols[j];
- if (i === 0) {
- columnNames.push({name: col, index: j, aggr: 'sum'});
- } else {
- var parsedCol = $scope.parseTableCell(col);
- cols.push(parsedCol);
- cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: parsedCol});
- }
- }
- if (i !== 0) {
- rows.push(cols);
- array.push(cols2);
- }
- }
- result.msgTable = array;
- result.columnNames = columnNames;
- result.rows = rows;
- }
- };
-
$scope.setGraphMode = function(type, emit, refresh) {
if (emit) {
setNewMode(type);
@@ -989,12 +1006,57 @@
clearUnknownColsFromGraphOption();
// set graph height
var height = $scope.paragraph.config.graph.height;
- angular.element('#p' + $scope.paragraph.id + '_graph').height(height);
+ var graphContainerEl = angular.element('#p' + $scope.paragraph.id + '_graph');
+ graphContainerEl.height(height);
- if (!type || type === 'table') {
- setTable($scope.paragraph.result, refresh);
- } else {
- setD3Chart(type, $scope.paragraph.result, refresh);
+ if (!type) {
+ type = 'table';
+ }
+
+ var builtInViz = builtInVisualizations[type];
+ if (builtInViz) {
+ // deactive previsouly active visualization
+ for (var t in builtInVisualizations) {
+ var v = builtInVisualizations[t].instance;
+ if (t !== type && v && v.isActive()) {
+ v.deactivate();
+ break;
+ }
+ }
+
+ if (!builtInViz.instance) { // not instantiated yet
+ // render when targetEl is available
+ var retryRenderer = function() {
+ var targetEl = angular.element('#p' + $scope.paragraph.id + '_' + type);
+
+ if (targetEl.length) {
+ try {
+ // set height
+ targetEl.height(height);
+
+ // instantiate visualization
+ var Visualization = builtInViz.class;
+ builtInViz.instance = new Visualization(targetEl, $scope.paragraph.config.graph);
+ builtInViz.instance.render(tableData);
+ builtInViz.instance.activate();
+ angular.element(window).resize(function() {
+ builtInViz.instance.resize();
+ });
+ } catch (err) {
+ console.log('Graph drawing error %o', err);
+ }
+ } else {
+ $timeout(retryRenderer, 10);
+ }
+ };
+ $timeout(retryRenderer);
+ } else if (refresh) {
+ // when graph options or data are changed
+ builtInViz.instance.setConfig($scope.paragraph.config.graph);
+ builtInViz.instance.render(tableData);
+ } else {
+ builtInViz.instance.activate();
+ }
}
}
};
@@ -1016,213 +1078,6 @@
websocketMsgSrv.commitParagraph($scope.paragraph.id, title, text, config, params);
};
- var setTable = function(data, refresh) {
- var renderTable = function() {
- var height = $scope.paragraph.config.graph.height;
- var container = angular.element('#p' + $scope.paragraph.id + '_table').css('height', height).get(0);
- var resultRows = data.rows;
- var columnNames = _.pluck(data.columnNames, 'name');
-
- if ($scope.hot) {
- $scope.hot.destroy();
- }
-
- $scope.hot = new Handsontable(container, {
- colHeaders: columnNames,
- data: resultRows,
- rowHeaders: false,
- stretchH: 'all',
- sortIndicator: true,
- columnSorting: true,
- contextMenu: false,
- manualColumnResize: true,
- manualRowResize: true,
- readOnly: true,
- readOnlyCellClassName: '', // don't apply any special class so we can retain current styling
- fillHandle: false,
- fragmentSelection: true,
- disableVisualSelection: true,
- cells: function(row, col, prop) {
- var cellProperties = {};
- cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) {
- if (value instanceof moment) {
- td.innerHTML = value._i;
- } else if (!isNaN(value)) {
- cellProperties.format = '0,0.[00000]';
- td.style.textAlign = 'left';
- Handsontable.renderers.NumericRenderer.apply(this, arguments);
- } else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) {
- td.innerHTML = value.substring('%html'.length);
- } else {
- Handsontable.renderers.TextRenderer.apply(this, arguments);
- }
- };
- return cellProperties;
- }
- });
- };
-
- var retryRenderer = function() {
- if (angular.element('#p' + $scope.paragraph.id + '_table').length) {
- try {
- renderTable();
- } catch (err) {
- console.log('Chart drawing error %o', err);
- }
- } else {
- $timeout(retryRenderer,10);
- }
- };
- $timeout(retryRenderer);
-
- };
-
- var groupedThousandsWith3DigitsFormatter = function(x) {
- return d3.format(',')(d3.round(x, 3));
- };
-
- var customAbbrevFormatter = function(x) {
- var s = d3.format('.3s')(x);
- switch (s[s.length - 1]) {
- case 'G': return s.slice(0, -1) + 'B';
- }
- return s;
- };
-
- var xAxisTickFormat = function(d, xLabels) {
- if (xLabels[d] && (isNaN(parseFloat(xLabels[d])) || !isFinite(xLabels[d]))) { // to handle string type xlabel
- return xLabels[d];
- } else {
- return d;
- }
- };
-
- var yAxisTickFormat = function(d) {
- if (Math.abs(d) >= Math.pow(10,6)) {
- return customAbbrevFormatter(d);
- }
- return groupedThousandsWith3DigitsFormatter(d);
- };
-
- var setD3Chart = function(type, data, refresh) {
- if (!$scope.chart[type]) {
- var chart = nv.models[type]();
- chart._options.noData = 'Invalid Data, check graph settings';
- $scope.chart[type] = chart;
- }
-
- var d3g = [];
- var xLabels;
- var yLabels;
-
- if (type === 'scatterChart') {
- var scatterData = setScatterChart(data, refresh);
-
- xLabels = scatterData.xLabels;
- yLabels = scatterData.yLabels;
- d3g = scatterData.d3g;
-
- $scope.chart[type].xAxis.tickFormat(function(d) {return xAxisTickFormat(d, xLabels);});
- $scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d, yLabels);});
- // configure how the tooltip looks.
- $scope.chart[type].tooltipContent(function(key, x, y, graph, data) {
- var tooltipContent = '' + key + '
';
- if ($scope.paragraph.config.graph.scatter.size &&
- $scope.isValidSizeOption($scope.paragraph.config.graph.scatter, $scope.paragraph.result.rows)) {
- tooltipContent += '' + data.point.size + '
';
- }
-
- return tooltipContent;
- });
-
- $scope.chart[type].showDistX(true)
- .showDistY(true);
- //handle the problem of tooltip not showing when muliple points have same value.
- } else {
- var p = pivot(data);
- if (type === 'pieChart') {
- var d = pivotDataToD3ChartFormat(p, true).d3g;
-
- $scope.chart[type].x(function(d) { return d.label;})
- .y(function(d) { return d.value;});
-
- if (d.length > 0) {
- for (var i = 0; i < d[0].values.length ; i++) {
- var e = d[0].values[i];
- d3g.push({
- label: e.x,
- value: e.y
- });
- }
- }
- } else if (type === 'multiBarChart') {
- d3g = pivotDataToD3ChartFormat(p, true, false, type).d3g;
- $scope.chart[type].yAxis.axisLabelDistance(50);
- $scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d);});
- } else if (type === 'lineChart' || type === 'stackedAreaChart' || type === 'lineWithFocusChart') {
- var pivotdata = pivotDataToD3ChartFormat(p, false, true);
- xLabels = pivotdata.xLabels;
- d3g = pivotdata.d3g;
- $scope.chart[type].xAxis.tickFormat(function(d) {return xAxisTickFormat(d, xLabels);});
- if (type === 'stackedAreaChart') {
- $scope.chart[type].yAxisTickFormat(function(d) {return yAxisTickFormat(d);});
- } else {
- $scope.chart[type].yAxis.tickFormat(function(d) {return yAxisTickFormat(d, xLabels);});
- }
- $scope.chart[type].yAxis.axisLabelDistance(50);
- if ($scope.chart[type].useInteractiveGuideline) { // lineWithFocusChart hasn't got useInteractiveGuideline
- $scope.chart[type].useInteractiveGuideline(true); // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
- }
- if ($scope.paragraph.config.graph.forceY) {
- $scope.chart[type].forceY([0]); // force y-axis minimum to 0 for line chart.
- } else {
- $scope.chart[type].forceY([]);
- }
- }
- }
-
- var renderChart = function() {
- if (!refresh) {
- // TODO force destroy previous chart
- }
-
- var height = $scope.paragraph.config.graph.height;
-
- var animationDuration = 300;
- var numberOfDataThreshold = 150;
- // 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 (ignoreErr) {
- }
-
- d3.select('#p' + $scope.paragraph.id + '_' + type + ' svg')
- .attr('height', $scope.paragraph.config.graph.height)
- .datum(d3g)
- .transition()
- .duration(animationDuration)
- .call($scope.chart[type]);
- d3.select('#p' + $scope.paragraph.id + '_' + type + ' svg').style.height = height + 'px';
- nv.utils.windowResize($scope.chart[type].update);
- };
-
- var retryRenderer = function() {
- if (angular.element('#p' + $scope.paragraph.id + '_' + type + ' svg').length !== 0) {
- try {
- renderChart();
- } catch (err) {
- console.log('Chart drawing error %o', err);
- }
- } else {
- $timeout(retryRenderer,10);
- }
- };
- $timeout(retryRenderer);
- };
-
$scope.isGraphMode = function(graphName) {
var activeAppId = _.get($scope.paragraph.config, 'helium.activeApp');
if ($scope.getResultType() === 'TABLE' && $scope.getGraphMode() === graphName && !activeAppId) {
@@ -1301,9 +1156,9 @@
for (var i = 0; i < list.length; i++) {
// remove non existing column
var found = false;
- for (var j = 0; j < $scope.paragraph.result.columnNames.length; j++) {
+ for (var j = 0; j < tableData.columns.length; j++) {
var a = list[i];
- var b = $scope.paragraph.result.columnNames[j];
+ var b = tableData.columns[j];
if (a.index === b.index && a.name === b.name) {
found = true;
break;
@@ -1319,9 +1174,9 @@
for (var f in fields) {
if (fields[f]) {
var found = false;
- for (var i = 0; i < $scope.paragraph.result.columnNames.length; i++) {
+ for (var i = 0; i < tableData.columns.length; i++) {
var a = fields[f];
- var b = $scope.paragraph.result.columnNames[i];
+ var b = tableData.columns[i];
if (a.index === b.index && a.name === b.name) {
found = true;
break;
@@ -1347,541 +1202,33 @@
/* select default key and value if there're none selected */
var selectDefaultColsForGraphOption = function() {
- if ($scope.paragraph.config.graph.keys.length === 0 && $scope.paragraph.result.columnNames.length > 0) {
- $scope.paragraph.config.graph.keys.push($scope.paragraph.result.columnNames[0]);
+ if ($scope.paragraph.config.graph.keys.length === 0 && tableData.columns.length > 0) {
+ $scope.paragraph.config.graph.keys.push(tableData.columns[0]);
}
- if ($scope.paragraph.config.graph.values.length === 0 && $scope.paragraph.result.columnNames.length > 1) {
- $scope.paragraph.config.graph.values.push($scope.paragraph.result.columnNames[1]);
+ if ($scope.paragraph.config.graph.values.length === 0 && tableData.columns.length > 1) {
+ $scope.paragraph.config.graph.values.push(tableData.columns[1]);
}
if (!$scope.paragraph.config.graph.scatter.xAxis && !$scope.paragraph.config.graph.scatter.yAxis) {
- if ($scope.paragraph.result.columnNames.length > 1) {
- $scope.paragraph.config.graph.scatter.xAxis = $scope.paragraph.result.columnNames[0];
- $scope.paragraph.config.graph.scatter.yAxis = $scope.paragraph.result.columnNames[1];
- } else if ($scope.paragraph.result.columnNames.length === 1) {
- $scope.paragraph.config.graph.scatter.xAxis = $scope.paragraph.result.columnNames[0];
- }
- }
- };
-
- var pivot = function(data) {
- var keys = $scope.paragraph.config.graph.keys;
- var groups = $scope.paragraph.config.graph.groups;
- var values = $scope.paragraph.config.graph.values;
-
- var aggrFunc = {
- sum: function(a, b) {
- var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
- var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
- return varA + varB;
- },
- count: function(a, b) {
- var varA = (a !== undefined) ? parseInt(a) : 0;
- var varB = (b !== undefined) ? 1 : 0;
- return varA + varB;
- },
- min: function(a, b) {
- var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
- var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
- return Math.min(varA,varB);
- },
- max: function(a, b) {
- var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
- var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
- return Math.max(varA,varB);
- },
- avg: function(a, b, c) {
- var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
- var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
- return varA + varB;
- }
- };
-
- var aggrFuncDiv = {
- sum: false,
- count: false,
- min: false,
- max: false,
- avg: true
- };
-
- var schema = {};
- var rows = {};
-
- for (var i = 0; i < data.rows.length; i++) {
- var row = data.rows[i];
- var s = schema;
- var p = rows;
-
- for (var k = 0; k < keys.length; k++) {
- var key = keys[k];
-
- // add key to schema
- if (!s[key.name]) {
- s[key.name] = {
- order: k,
- index: key.index,
- type: 'key',
- children: {}
- };
- }
- s = s[key.name].children;
-
- // add key to row
- var keyKey = row[key.index];
- if (!p[keyKey]) {
- p[keyKey] = {};
- }
- p = p[keyKey];
- }
-
- for (var g = 0; g < groups.length; g++) {
- var group = groups[g];
- var groupKey = row[group.index];
-
- // add group to schema
- if (!s[groupKey]) {
- s[groupKey] = {
- order: g,
- index: group.index,
- type: 'group',
- children: {}
- };
- }
- s = s[groupKey].children;
-
- // add key to row
- if (!p[groupKey]) {
- p[groupKey] = {};
- }
- p = p[groupKey];
- }
-
- for (var v = 0; v < values.length; v++) {
- var value = values[v];
- var valueKey = value.name + '(' + value.aggr + ')';
-
- // add value to schema
- if (!s[valueKey]) {
- s[valueKey] = {
- type: 'value',
- order: v,
- index: value.index
- };
- }
-
- // add value to row
- if (!p[valueKey]) {
- p[valueKey] = {
- value: (value.aggr !== 'count') ? row[value.index] : 1,
- count: 1
- };
- } else {
- p[valueKey] = {
- value: aggrFunc[value.aggr](p[valueKey].value, row[value.index], p[valueKey].count + 1),
- count: (aggrFuncDiv[value.aggr]) ? p[valueKey].count + 1 : p[valueKey].count
- };
- }
- }
- }
-
- //console.log("schema=%o, rows=%o", schema, rows);
-
- return {
- schema: schema,
- rows: rows
- };
- };
-
- var pivotDataToD3ChartFormat = function(data, allowTextXAxis, fillMissingValues, chartType) {
- // construct d3 data
- var d3g = [];
-
- var schema = data.schema;
- var rows = data.rows;
- var values = $scope.paragraph.config.graph.values;
-
- var concat = function(o, n) {
- if (!o) {
- return n;
- } else {
- return o + '.' + n;
- }
- };
-
- var getSchemaUnderKey = function(key, s) {
- for (var c in key.children) {
- s[c] = {};
- getSchemaUnderKey(key.children[c], s[c]);
- }
- };
-
- var 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 (var c in s.children) {
- if (fillMissingValues && s.children[c].type === 'group' && r[c] === undefined) {
- var cs = {};
- getSchemaUnderKey(s.children[c], cs);
- traverse(c, s.children[c], c, cs, func, rowName, rowValue, colName);
- continue;
- }
-
- for (var j in r) {
- if (s.children[c].type === 'key' || c === j) {
- traverse(c, s.children[c], j, r[j], func, rowName, rowValue, colName);
- }
- }
- }
- };
-
- var keys = $scope.paragraph.config.graph.keys;
- var groups = $scope.paragraph.config.graph.groups;
- values = $scope.paragraph.config.graph.values;
- var valueOnly = (keys.length === 0 && groups.length === 0 && values.length > 0);
- var noKey = (keys.length === 0);
- var isMultiBarChart = (chartType === 'multiBarChart');
-
- var sKey = Object.keys(schema)[0];
-
- var rowNameIndex = {};
- var rowIdx = 0;
- var colNameIndex = {};
- var colIdx = 0;
- var rowIndexValue = {};
-
- for (var k in rows) {
- 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++;
- }
- var i = colNameIndex[colName];
- if (noKey && isMultiBarChart) {
- i = 0;
- }
-
- if (!d3g[i]) {
- d3g[i] = {
- values: [],
- key: (noKey && isMultiBarChart) ? 'values' : colName
- };
- }
-
- var xVar = isNaN(rowValue) ? ((allowTextXAxis) ? rowValue : rowNameIndex[rowValue]) : parseFloat(rowValue);
- var yVar = 0;
- if (xVar === undefined) { xVar = colName; }
- if (value !== undefined) {
- yVar = isNaN(value.value) ? 0 : parseFloat(value.value) / parseFloat(value.count);
- }
- d3g[i].values.push({
- x: xVar,
- y: yVar
- });
- });
- }
-
- // clear aggregation name, if possible
- var namesWithoutAggr = {};
- var colName;
- var withoutAggr;
- // TODO - This part could use som refactoring - Weird if/else with similar actions and variable names
- for (colName in colNameIndex) {
- withoutAggr = colName.substring(0, colName.lastIndexOf('('));
- if (!namesWithoutAggr[withoutAggr]) {
- namesWithoutAggr[withoutAggr] = 1;
- } else {
- namesWithoutAggr[withoutAggr]++;
- }
- }
-
- if (valueOnly) {
- for (var 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 (var 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 (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
- };
- };
-
- var setDiscreteScatterData = function(data) {
- var xAxis = $scope.paragraph.config.graph.scatter.xAxis;
- var yAxis = $scope.paragraph.config.graph.scatter.yAxis;
- var group = $scope.paragraph.config.graph.scatter.group;
-
- var xValue;
- var yValue;
- var grp;
-
- var rows = {};
-
- for (var i = 0; i < data.rows.length; i++) {
- var row = data.rows[i];
- if (xAxis) {
- xValue = row[xAxis.index];
- }
- if (yAxis) {
- yValue = row[yAxis.index];
- }
- if (group) {
- grp = row[group.index];
- }
-
- var key = xValue + ',' + yValue + ',' + grp;
-
- if (!rows[key]) {
- rows[key] = {
- x: xValue,
- y: yValue,
- group: grp,
- size: 1
- };
- } else {
- rows[key].size++;
- }
- }
-
- // change object into array
- var newRows = [];
- for (var r in rows) {
- var newRow = [];
- if (xAxis) { newRow[xAxis.index] = rows[r].x; }
- if (yAxis) { newRow[yAxis.index] = rows[r].y; }
- if (group) { newRow[group.index] = rows[r].group; }
- newRow[data.rows[0].length] = rows[r].size;
- newRows.push(newRow);
- }
- return newRows;
- };
-
- var setScatterChart = function(data, refresh) {
- var xAxis = $scope.paragraph.config.graph.scatter.xAxis;
- var yAxis = $scope.paragraph.config.graph.scatter.yAxis;
- var group = $scope.paragraph.config.graph.scatter.group;
- var size = $scope.paragraph.config.graph.scatter.size;
-
- var xValues = [];
- var yValues = [];
- var rows = {};
- var d3g = [];
-
- var rowNameIndex = {};
- var colNameIndex = {};
- var grpNameIndex = {};
- var rowIndexValue = {};
- var colIndexValue = {};
- var grpIndexValue = {};
- var rowIdx = 0;
- var colIdx = 0;
- var grpIdx = 0;
- var grpName = '';
-
- var xValue;
- var yValue;
- var row;
-
- if (!xAxis && !yAxis) {
- return {
- d3g: []
- };
- }
-
- for (var i = 0; i < data.rows.length; i++) {
- row = data.rows[i];
- if (xAxis) {
- xValue = row[xAxis.index];
- xValues[i] = xValue;
- }
- if (yAxis) {
- yValue = row[yAxis.index];
- yValues[i] = yValue;
- }
- }
-
- var isAllDiscrete = ((xAxis && yAxis && isDiscrete(xValues) && isDiscrete(yValues)) ||
- (!xAxis && isDiscrete(yValues)) ||
- (!yAxis && isDiscrete(xValues)));
-
- if (isAllDiscrete) {
- rows = setDiscreteScatterData(data);
- } else {
- rows = data.rows;
- }
-
- if (!group && isAllDiscrete) {
- grpName = 'count';
- } else if (!group && !size) {
- if (xAxis && yAxis) {
- grpName = '(' + xAxis.name + ', ' + yAxis.name + ')';
- } else if (xAxis && !yAxis) {
- grpName = xAxis.name;
- } else if (!xAxis && yAxis) {
- grpName = yAxis.name;
- }
- } else if (!group && size) {
- grpName = size.name;
- }
-
- for (i = 0; i < rows.length; i++) {
- row = rows[i];
- if (xAxis) {
- xValue = row[xAxis.index];
- }
- if (yAxis) {
- yValue = row[yAxis.index];
- }
- if (group) {
- grpName = row[group.index];
+ if (tableData.columns.length > 1) {
+ $scope.paragraph.config.graph.scatter.xAxis = tableData.columns[0];
+ $scope.paragraph.config.graph.scatter.yAxis = tableData.columns[1];
+ } else if (tableData.columns.length === 1) {
+ $scope.paragraph.config.graph.scatter.xAxis = tableData.columns[0];
}
- var sz = (isAllDiscrete) ? row[row.length - 1] : ((size) ? row[size.index] : 1);
-
- if (grpNameIndex[grpName] === undefined) {
- grpIndexValue[grpIdx] = grpName;
- grpNameIndex[grpName] = grpIdx++;
- }
-
- if (xAxis && rowNameIndex[xValue] === undefined) {
- rowIndexValue[rowIdx] = xValue;
- rowNameIndex[xValue] = rowIdx++;
- }
-
- if (yAxis && colNameIndex[yValue] === undefined) {
- colIndexValue[colIdx] = yValue;
- colNameIndex[yValue] = colIdx++;
- }
-
- if (!d3g[grpNameIndex[grpName]]) {
- d3g[grpNameIndex[grpName]] = {
- key: grpName,
- values: []
- };
- }
-
- d3g[grpNameIndex[grpName]].values.push({
- x: xAxis ? (isNaN(xValue) ? rowNameIndex[xValue] : parseFloat(xValue)) : 0,
- y: yAxis ? (isNaN(yValue) ? colNameIndex[yValue] : parseFloat(yValue)) : 0,
- size: isNaN(parseFloat(sz)) ? 1 : parseFloat(sz)
- });
}
-
- return {
- xLabels: rowIndexValue,
- yLabels: colIndexValue,
- d3g: d3g
- };
};
- var isDiscrete = function(field) {
- var getUnique = function(f) {
- var uniqObj = {};
- var uniqArr = [];
- var j = 0;
- for (var i = 0; i < f.length; i++) {
- var item = f[i];
- if (uniqObj[item] !== 1) {
- uniqObj[item] = 1;
- uniqArr[j++] = item;
- }
- }
- return uniqArr;
- };
-
- for (var i = 0; i < field.length; i++) {
- if (isNaN(parseFloat(field[i])) &&
- (typeof field[i] === 'string' || field[i] instanceof String)) {
- return true;
- }
- }
-
- var threshold = 0.05;
- var unique = getUnique(field);
- if (unique.length / field.length < threshold) {
- return true;
+ $scope.isValidSizeOption = function(options) {
+ var builtInViz = builtInVisualizations.scatterChart;
+ if (builtInViz && builtInViz.instance) {
+ return builtInViz.instance.isValidSizeOption(options);
} else {
return false;
}
};
- $scope.isValidSizeOption = function(options, rows) {
- var xValues = [];
- var yValues = [];
-
- for (var i = 0; i < rows.length; i++) {
- var row = rows[i];
- var size = row[options.size.index];
-
- //check if the field is numeric
- if (isNaN(parseFloat(size)) || !isFinite(size)) {
- return false;
- }
-
- if (options.xAxis) {
- var x = row[options.xAxis.index];
- xValues[i] = x;
- }
- if (options.yAxis) {
- var y = row[options.yAxis.index];
- yValues[i] = y;
- }
- }
-
- //check if all existing fields are discrete
- var isAllDiscrete = ((options.xAxis && options.yAxis && isDiscrete(xValues) && isDiscrete(yValues)) ||
- (!options.xAxis && isDiscrete(yValues)) ||
- (!options.yAxis && isDiscrete(xValues)));
-
- if (isAllDiscrete) {
- return false;
- }
-
- return true;
- };
-
$scope.resizeParagraph = function(width, height) {
$scope.changeColWidth(width);
$timeout(function() {
@@ -1943,8 +1290,8 @@
$scope.exportToDSV = function(delimiter) {
var dsv = '';
- for (var titleIndex in $scope.paragraph.result.columnNames) {
- dsv += $scope.paragraph.result.columnNames[titleIndex].name + delimiter;
+ for (var titleIndex in tableData.columns) {
+ dsv += tableData.columns[titleIndex].name + delimiter;
}
dsv = dsv.substring(0, dsv.length - 1) + '\n';
for (var r in $scope.paragraph.result.msgTable) {
@@ -2336,8 +1683,11 @@
}
if (newType === 'TABLE') {
- $scope.loadTableData($scope.paragraph.result);
if (oldType !== 'TABLE' || resultRefreshed) {
+ var TableData = zeppelin.TableData;
+ tableData = new TableData();
+ tableData.loadParagraphResult($scope.paragraph.result);
+ $scope.tableDataColumns = tableData.columns;
clearUnknownColsFromGraphOption();
selectDefaultColsForGraphOption();
}
diff --git a/zeppelin-web/src/app/tabledata/pivot.js b/zeppelin-web/src/app/tabledata/pivot.js
new file mode 100644
index 00000000000..af46fcb424d
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/pivot.js
@@ -0,0 +1,165 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+var zeppelin = zeppelin || {};
+
+/**
+ * pivot table data and return d3 chart data
+ */
+zeppelin.PivotTransformation = function(config) {
+ zeppelin.Transformation.call(this, config);
+};
+
+zeppelin.PivotTransformation.prototype = Object.create(zeppelin.Transformation.prototype);
+
+/**
+ * Method will be invoked when tableData or config changes
+ */
+zeppelin.PivotTransformation.prototype.transform = function(tableData) {
+ return this.pivot(
+ tableData,
+ this.config.keys,
+ this.config.groups,
+ this.config.values);
+};
+
+zeppelin.PivotTransformation.prototype.pivot = function(data, keys, groups, values) {
+ var aggrFunc = {
+ sum: function(a, b) {
+ var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
+ var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
+ return varA + varB;
+ },
+ count: function(a, b) {
+ var varA = (a !== undefined) ? parseInt(a) : 0;
+ var varB = (b !== undefined) ? 1 : 0;
+ return varA + varB;
+ },
+ min: function(a, b) {
+ var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
+ var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
+ return Math.min(varA,varB);
+ },
+ max: function(a, b) {
+ var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
+ var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
+ return Math.max(varA,varB);
+ },
+ avg: function(a, b, c) {
+ var varA = (a !== undefined) ? (isNaN(a) ? 1 : parseFloat(a)) : 0;
+ var varB = (b !== undefined) ? (isNaN(b) ? 1 : parseFloat(b)) : 0;
+ return varA + varB;
+ }
+ };
+
+ var aggrFuncDiv = {
+ sum: false,
+ count: false,
+ min: false,
+ max: false,
+ avg: true
+ };
+
+ var schema = {};
+ var rows = {};
+
+ for (var i = 0; i < data.rows.length; i++) {
+ var row = data.rows[i];
+ var s = schema;
+ var p = rows;
+
+ for (var k = 0; k < keys.length; k++) {
+ var key = keys[k];
+
+ // add key to schema
+ if (!s[key.name]) {
+ s[key.name] = {
+ order: k,
+ index: key.index,
+ type: 'key',
+ children: {}
+ };
+ }
+ s = s[key.name].children;
+
+ // add key to row
+ var keyKey = row[key.index];
+ if (!p[keyKey]) {
+ p[keyKey] = {};
+ }
+ p = p[keyKey];
+ }
+
+ for (var g = 0; g < groups.length; g++) {
+ var group = groups[g];
+ var groupKey = row[group.index];
+
+ // add group to schema
+ if (!s[groupKey]) {
+ s[groupKey] = {
+ order: g,
+ index: group.index,
+ type: 'group',
+ children: {}
+ };
+ }
+ s = s[groupKey].children;
+
+ // add key to row
+ if (!p[groupKey]) {
+ p[groupKey] = {};
+ }
+ p = p[groupKey];
+ }
+
+ for (var v = 0; v < values.length; v++) {
+ var value = values[v];
+ var valueKey = value.name + '(' + value.aggr + ')';
+
+ // add value to schema
+ if (!s[valueKey]) {
+ s[valueKey] = {
+ type: 'value',
+ order: v,
+ index: value.index
+ };
+ }
+
+ // add value to row
+ if (!p[valueKey]) {
+ p[valueKey] = {
+ value: (value.aggr !== 'count') ? row[value.index] : 1,
+ count: 1
+ };
+ } else {
+ p[valueKey] = {
+ value: aggrFunc[value.aggr](p[valueKey].value, row[value.index], p[valueKey].count + 1),
+ count: (aggrFuncDiv[value.aggr]) ? p[valueKey].count + 1 : p[valueKey].count
+ };
+ }
+ }
+ }
+
+ //console.log('schema=%o, rows=%o', schema, rows);
+ return {
+ keys: keys,
+ groups: groups,
+ values: values,
+ schema: schema,
+ rows: rows
+ };
+};
+
diff --git a/zeppelin-web/src/app/tabledata/tabledata.js b/zeppelin-web/src/app/tabledata/tabledata.js
new file mode 100644
index 00000000000..0efcc546e0f
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/tabledata.js
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+var zeppelin = zeppelin || {};
+
+/**
+ * Create table data object from paragraph table type result
+ */
+zeppelin.TableData = function(columns, rows, comment) {
+ this.columns = columns || [];
+ this.rows = rows || [];
+ this.comment = comment || '';
+};
+
+zeppelin.TableData.prototype.loadParagraphResult = function(paragraphResult) {
+ if (!paragraphResult || paragraphResult.type !== 'TABLE') {
+ console.log('Can not load paragraph result');
+ return;
+ }
+
+ var columnNames = [];
+ var rows = [];
+ var array = [];
+ var textRows = paragraphResult.msg.split('\n');
+ var comment = '';
+ var commentRow = false;
+
+ for (var i = 0; i < textRows.length; i++) {
+ var textRow = textRows[i];
+ if (commentRow) {
+ comment += textRow;
+ continue;
+ }
+
+ if (textRow === '') {
+ if (rows.length > 0) {
+ commentRow = true;
+ }
+ continue;
+ }
+ var textCols = textRow.split('\t');
+ var cols = [];
+ var cols2 = [];
+ for (var j = 0; j < textCols.length; j++) {
+ var col = textCols[j];
+ if (i === 0) {
+ columnNames.push({name: col, index: j, aggr: 'sum'});
+ } else {
+ var parsedCol = this.parseTableCell(col);
+ cols.push(parsedCol);
+ cols2.push({key: (columnNames[i]) ? columnNames[i].name : undefined, value: parsedCol});
+ }
+ }
+ if (i !== 0) {
+ rows.push(cols);
+ array.push(cols2);
+ }
+ }
+ this.comment = comment;
+ this.columns = columnNames;
+ this.rows = rows;
+};
+
+zeppelin.TableData.prototype.parseTableCell = function(cell) {
+ if (!isNaN(cell)) {
+ if (cell.length === 0 || Number(cell) > Number.MAX_SAFE_INTEGER || Number(cell) < Number.MIN_SAFE_INTEGER) {
+ return cell;
+ } else {
+ return Number(cell);
+ }
+ }
+ var d = moment(cell);
+ if (d.isValid()) {
+ return d;
+ }
+ return cell;
+};
diff --git a/zeppelin-web/src/app/tabledata/transformation.js b/zeppelin-web/src/app/tabledata/transformation.js
new file mode 100644
index 00000000000..f8d3cc71051
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/transformation.js
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+var zeppelin = zeppelin || {};
+
+/**
+ * Base class for visualization
+ */
+zeppelin.Transformation = function(config) {
+ this.config = config;
+};
+
+/**
+ * Method will be invoked when tableData or config changes
+ */
+zeppelin.Transformation.prototype.transform = function(tableData) {
+ // override this
+};
+
+zeppelin.Transformation.prototype.setConfig = function(config) {
+ this.config = config;
+};
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-areachart.js b/zeppelin-web/src/app/visualization/builtins/visualization-areachart.js
new file mode 100644
index 00000000000..b91bc168e77
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-areachart.js
@@ -0,0 +1,68 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * Visualize data in area chart
+ */
+zeppelin.AreachartVisualization = function(targetEl, config) {
+ zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
+
+ var PivotTransformation = zeppelin.PivotTransformation;
+ this.pivot = new PivotTransformation(config);
+ this.xLables = [];
+};
+
+zeppelin.AreachartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
+
+zeppelin.AreachartVisualization.prototype.type = function() {
+ return 'stackedAreaChart';
+};
+
+zeppelin.AreachartVisualization.prototype.getTransformation = function() {
+ return this.pivot;
+};
+
+zeppelin.AreachartVisualization.prototype.render = function(tableData) {
+ var pivot = this.pivot.transform(tableData);
+ var d3Data = this.d3DataFromPivot(
+ pivot.schema,
+ pivot.rows,
+ pivot.keys,
+ pivot.groups,
+ pivot.values,
+ false,
+ true,
+ false);
+
+ this.xLabels = d3Data.xLabels;
+ zeppelin.Nvd3ChartVisualization.prototype.render.call(this, d3Data);
+};
+
+/**
+ * Set new config
+ */
+zeppelin.AreachartVisualization.prototype.setConfig = function(config) {
+ zeppelin.Nvd3ChartVisualization.prototype.setConfig.call(this, config);
+ this.pivot.setConfig(config);
+};
+
+zeppelin.AreachartVisualization.prototype.configureChart = function(chart) {
+ var self = this;
+ chart.xAxis.tickFormat(function(d) {return self.xAxisTickFormat(d, self.xLabels);});
+ chart.yAxisTickFormat(function(d) {return self.yAxisTickFormat(d);});
+ chart.yAxis.axisLabelDistance(50);
+ chart.useInteractiveGuideline(true); // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
+};
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-barchart.js b/zeppelin-web/src/app/visualization/builtins/visualization-barchart.js
new file mode 100644
index 00000000000..6480ee05e76
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-barchart.js
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * Visualize data in bar char
+ */
+zeppelin.BarchartVisualization = function(targetEl, config) {
+ zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
+
+ var PivotTransformation = zeppelin.PivotTransformation;
+ this.pivot = new PivotTransformation(config);
+};
+
+zeppelin.BarchartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
+
+zeppelin.BarchartVisualization.prototype.type = function() {
+ return 'multiBarChart';
+};
+
+zeppelin.BarchartVisualization.prototype.getTransformation = function() {
+ return this.pivot;
+};
+
+zeppelin.BarchartVisualization.prototype.render = function(tableData) {
+ var pivot = this.pivot.transform(tableData);
+ var d3Data = this.d3DataFromPivot(
+ pivot.schema,
+ pivot.rows,
+ pivot.keys,
+ pivot.groups,
+ pivot.values,
+ true,
+ false,
+ true);
+
+ zeppelin.Nvd3ChartVisualization.prototype.render.call(this, d3Data);
+};
+
+/**
+ * Set new config
+ */
+zeppelin.BarchartVisualization.prototype.setConfig = function(config) {
+ zeppelin.Nvd3ChartVisualization.prototype.setConfig.call(this, config);
+ this.pivot.setConfig(config);
+};
+
+zeppelin.BarchartVisualization.prototype.configureChart = function(chart) {
+ var self = this;
+ chart.yAxis.axisLabelDistance(50);
+ chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d);});
+};
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js b/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js
new file mode 100644
index 00000000000..6b2c6d4ad4a
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * Visualize data in line chart
+ */
+zeppelin.LinechartVisualization = function(targetEl, config) {
+ zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
+
+ var PivotTransformation = zeppelin.PivotTransformation;
+ this.pivot = new PivotTransformation(config);
+ this.xLables = [];
+};
+
+zeppelin.LinechartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
+
+zeppelin.LinechartVisualization.prototype.type = function() {
+ if (this.config.lineWithFocus) {
+ return 'lineWithFocusChart';
+ } else {
+ return 'lineChart';
+ }
+};
+
+zeppelin.LinechartVisualization.prototype.getTransformation = function() {
+ return this.pivot;
+};
+
+zeppelin.LinechartVisualization.prototype.render = function(tableData) {
+ this.tableData = tableData;
+ var pivot = this.pivot.transform(tableData);
+ var d3Data = this.d3DataFromPivot(
+ pivot.schema,
+ pivot.rows,
+ pivot.keys,
+ pivot.groups,
+ pivot.values,
+ false,
+ true,
+ false);
+
+ this.xLabels = d3Data.xLabels;
+ zeppelin.Nvd3ChartVisualization.prototype.render.call(this, d3Data);
+};
+
+/**
+ * Set new config
+ */
+zeppelin.LinechartVisualization.prototype.setConfig = function(config) {
+ zeppelin.Nvd3ChartVisualization.prototype.setConfig.call(this, config);
+ this.pivot.setConfig(config);
+
+ // change mode
+ if (this.currentMode !== config.lineWithFocus) {
+ zeppelin.Nvd3ChartVisualization.prototype.destroy.call(this);
+ this.currentMode = config.lineWithFocus;
+ }
+};
+
+zeppelin.LinechartVisualization.prototype.configureChart = function(chart) {
+ var self = this;
+ chart.xAxis.tickFormat(function(d) {return self.xAxisTickFormat(d, self.xLabels);});
+ chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d, self.xLabels);});
+ chart.yAxis.axisLabelDistance(50);
+ if (chart.useInteractiveGuideline) { // lineWithFocusChart hasn't got useInteractiveGuideline
+ chart.useInteractiveGuideline(true); // for better UX and performance issue. (https://github.com/novus/nvd3/issues/691)
+ }
+ if (this.config.forceY) {
+ chart.forceY([0]); // force y-axis minimum to 0 for line chart.
+ } else {
+ chart.forceY([]);
+ }
+};
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-nvd3chart.js b/zeppelin-web/src/app/visualization/builtins/visualization-nvd3chart.js
new file mode 100644
index 00000000000..5831a47cbd7
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-nvd3chart.js
@@ -0,0 +1,257 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * Visualize data in table format
+ */
+zeppelin.Nvd3ChartVisualization = function(targetEl, config) {
+ zeppelin.Visualization.call(this, targetEl, config);
+ this.targetEl.append('');
+};
+
+zeppelin.Nvd3ChartVisualization.prototype = Object.create(zeppelin.Visualization.prototype);
+
+zeppelin.Visualization.prototype.refresh = function() {
+ if (this.chart) {
+ this.chart.update();
+ }
+};
+
+zeppelin.Nvd3ChartVisualization.prototype.render = function(data) {
+ var type = this.type();
+ var d3g = data.d3g;
+
+ if (!this.chart) {
+ this.chart = nv.models[type]();
+ }
+
+ this.configureChart(this.chart);
+
+ var animationDuration = 300;
+ var numberOfDataThreshold = 150;
+ var 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 (ignoreErr) {
+ }
+
+ 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';
+};
+
+zeppelin.Nvd3ChartVisualization.prototype.type = function() {
+ // override this and return chart type
+};
+
+zeppelin.Nvd3ChartVisualization.prototype.configureChart = function(chart) {
+ // override this to configure chart
+};
+
+zeppelin.Nvd3ChartVisualization.prototype.groupedThousandsWith3DigitsFormatter = function(x) {
+ return d3.format(',')(d3.round(x, 3));
+};
+
+zeppelin.Nvd3ChartVisualization.prototype.customAbbrevFormatter = function(x) {
+ var s = d3.format('.3s')(x);
+ switch (s[s.length - 1]) {
+ case 'G': return s.slice(0, -1) + 'B';
+ }
+ return s;
+};
+
+zeppelin.Nvd3ChartVisualization.prototype.xAxisTickFormat = function(d, xLabels) {
+ if (xLabels[d] && (isNaN(parseFloat(xLabels[d])) || !isFinite(xLabels[d]))) { // to handle string type xlabel
+ return xLabels[d];
+ } else {
+ return d;
+ }
+};
+
+zeppelin.Nvd3ChartVisualization.prototype.yAxisTickFormat = function(d) {
+ if (Math.abs(d) >= Math.pow(10,6)) {
+ return this.customAbbrevFormatter(d);
+ }
+ return this.groupedThousandsWith3DigitsFormatter(d);
+};
+
+zeppelin.Nvd3ChartVisualization.prototype.d3DataFromPivot = function(
+ schema, rows, keys, groups, values, allowTextXAxis, fillMissingValues, multiBarChart) {
+ // construct table data
+ var d3g = [];
+
+ var concat = function(o, n) {
+ if (!o) {
+ return n;
+ } else {
+ return o + '.' + n;
+ }
+ };
+
+ var getSchemaUnderKey = function(key, s) {
+ for (var c in key.children) {
+ s[c] = {};
+ getSchemaUnderKey(key.children[c], s[c]);
+ }
+ };
+
+ var 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 (var c in s.children) {
+ if (fillMissingValues && s.children[c].type === 'group' && r[c] === undefined) {
+ var cs = {};
+ getSchemaUnderKey(s.children[c], cs);
+ traverse(c, s.children[c], c, cs, func, rowName, rowValue, colName);
+ continue;
+ }
+
+ for (var j in r) {
+ if (s.children[c].type === 'key' || c === j) {
+ traverse(c, s.children[c], j, r[j], func, rowName, rowValue, colName);
+ }
+ }
+ }
+ };
+
+ var valueOnly = (keys.length === 0 && groups.length === 0 && values.length > 0);
+ var noKey = (keys.length === 0);
+ var isMultiBarChart = multiBarChart;
+
+ var sKey = Object.keys(schema)[0];
+
+ var rowNameIndex = {};
+ var rowIdx = 0;
+ var colNameIndex = {};
+ var colIdx = 0;
+ var rowIndexValue = {};
+
+ for (var k in rows) {
+ 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++;
+ }
+ var i = colNameIndex[colName];
+ if (noKey && isMultiBarChart) {
+ i = 0;
+ }
+
+ if (!d3g[i]) {
+ d3g[i] = {
+ values: [],
+ key: (noKey && isMultiBarChart) ? 'values' : colName
+ };
+ }
+
+ var xVar = isNaN(rowValue) ? ((allowTextXAxis) ? rowValue : rowNameIndex[rowValue]) : parseFloat(rowValue);
+ var yVar = 0;
+ if (xVar === undefined) { xVar = colName; }
+ if (value !== undefined) {
+ yVar = isNaN(value.value) ? 0 : parseFloat(value.value) / parseFloat(value.count);
+ }
+ d3g[i].values.push({
+ x: xVar,
+ y: yVar
+ });
+ });
+ }
+
+ // clear aggregation name, if possible
+ var namesWithoutAggr = {};
+ var colName;
+ var withoutAggr;
+ // TODO - This part could use som refactoring - Weird if/else with similar actions and variable names
+ for (colName in colNameIndex) {
+ withoutAggr = colName.substring(0, colName.lastIndexOf('('));
+ if (!namesWithoutAggr[withoutAggr]) {
+ namesWithoutAggr[withoutAggr] = 1;
+ } else {
+ namesWithoutAggr[withoutAggr]++;
+ }
+ }
+
+ if (valueOnly) {
+ for (var 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 (var 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 (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.
+ */
+zeppelin.Visualization.prototype.destroy = function() {
+ if (this.chart) {
+ d3.selectAll('#' + this.targetEl[0].id + ' svg > *').remove();
+ this.chart = undefined;
+ }
+};
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-piechart.js b/zeppelin-web/src/app/visualization/builtins/visualization-piechart.js
new file mode 100644
index 00000000000..e600ae558af
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-piechart.js
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * Visualize data in pie chart
+ */
+zeppelin.PiechartVisualization = function(targetEl, config) {
+ zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
+
+ var PivotTransformation = zeppelin.PivotTransformation;
+ this.pivot = new PivotTransformation(config);
+};
+
+zeppelin.PiechartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
+
+zeppelin.PiechartVisualization.prototype.type = function() {
+ return 'pieChart';
+};
+
+zeppelin.PiechartVisualization.prototype.getTransformation = function() {
+ return this.pivot;
+};
+
+zeppelin.PiechartVisualization.prototype.render = function(tableData) {
+ var pivot = this.pivot.transform(tableData);
+ var d3Data = this.d3DataFromPivot(
+ pivot.schema,
+ pivot.rows,
+ pivot.keys,
+ pivot.groups,
+ pivot.values,
+ true,
+ false,
+ false);
+
+ var d = d3Data.d3g;
+ var d3g = [];
+ if (d.length > 0) {
+ for (var i = 0; i < d[0].values.length ; i++) {
+ var e = d[0].values[i];
+ d3g.push({
+ label: e.x,
+ value: e.y
+ });
+ }
+ }
+ zeppelin.Nvd3ChartVisualization.prototype.render.call(this, {d3g: d3g});
+};
+
+/**
+ * Set new config
+ */
+zeppelin.PiechartVisualization.prototype.setConfig = function(config) {
+ zeppelin.Nvd3ChartVisualization.prototype.setConfig.call(this, config);
+ this.pivot.setConfig(config);
+};
+
+zeppelin.PiechartVisualization.prototype.configureChart = function(chart) {
+ chart.x(function(d) { return d.label;}).y(function(d) { return d.value;});
+};
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-scatterchart.js b/zeppelin-web/src/app/visualization/builtins/visualization-scatterchart.js
new file mode 100644
index 00000000000..de699091432
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-scatterchart.js
@@ -0,0 +1,295 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * Visualize data in scatter char
+ */
+zeppelin.ScatterchartVisualization = function(targetEl, config) {
+ zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
+};
+
+zeppelin.ScatterchartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
+
+zeppelin.ScatterchartVisualization.prototype.type = function() {
+ return 'scatterChart';
+};
+
+zeppelin.ScatterchartVisualization.prototype.getTransformation = function() {
+ return this.pivot;
+};
+
+zeppelin.ScatterchartVisualization.prototype.render = function(tableData) {
+ this.tableData = tableData;
+ var d3Data = this.setScatterChart(tableData, true);
+ this.xLabels = d3Data.xLabels;
+ this.yLabels = d3Data.yLabels;
+
+ zeppelin.Nvd3ChartVisualization.prototype.render.call(this, d3Data);
+};
+
+zeppelin.ScatterchartVisualization.prototype.configureChart = function(chart) {
+ var self = this;
+
+ chart.xAxis.tickFormat(function(d) {return self.xAxisTickFormat(d, self.xLabels);});
+ chart.yAxis.tickFormat(function(d) {return self.yAxisTickFormat(d, self.yLabels);});
+
+ // configure how the tooltip looks.
+ chart.tooltipContent(function(key, x, y, graph, data) {
+ var tooltipContent = '' + key + '
';
+ if (self.config.scatter.size &&
+ self.isValidSizeOption(self.config.scatter, self.tableData.rows)) {
+ tooltipContent += '' + data.point.size + '
';
+ }
+
+ return tooltipContent;
+ });
+
+ chart.showDistX(true).showDistY(true);
+ //handle the problem of tooltip not showing when muliple points have same value.
+};
+
+zeppelin.ScatterchartVisualization.prototype.setScatterChart = function(data, refresh) {
+ var xAxis = this.config.scatter.xAxis;
+ var yAxis = this.config.scatter.yAxis;
+ var group = this.config.scatter.group;
+ var size = this.config.scatter.size;
+
+ var xValues = [];
+ var yValues = [];
+ var rows = {};
+ var d3g = [];
+
+ var rowNameIndex = {};
+ var colNameIndex = {};
+ var grpNameIndex = {};
+ var rowIndexValue = {};
+ var colIndexValue = {};
+ var grpIndexValue = {};
+ var rowIdx = 0;
+ var colIdx = 0;
+ var grpIdx = 0;
+ var grpName = '';
+
+ var xValue;
+ var yValue;
+ var row;
+
+ if (!xAxis && !yAxis) {
+ return {
+ d3g: []
+ };
+ }
+
+ for (var i = 0; i < data.rows.length; i++) {
+ row = data.rows[i];
+ if (xAxis) {
+ xValue = row[xAxis.index];
+ xValues[i] = xValue;
+ }
+ if (yAxis) {
+ yValue = row[yAxis.index];
+ yValues[i] = yValue;
+ }
+ }
+
+ var isAllDiscrete = ((xAxis && yAxis && this.isDiscrete(xValues) && this.isDiscrete(yValues)) ||
+ (!xAxis && this.isDiscrete(yValues)) ||
+ (!yAxis && this.isDiscrete(xValues)));
+
+ if (isAllDiscrete) {
+ rows = this.setDiscreteScatterData(data);
+ } else {
+ rows = data.rows;
+ }
+
+ if (!group && isAllDiscrete) {
+ grpName = 'count';
+ } else if (!group && !size) {
+ if (xAxis && yAxis) {
+ grpName = '(' + xAxis.name + ', ' + yAxis.name + ')';
+ } else if (xAxis && !yAxis) {
+ grpName = xAxis.name;
+ } else if (!xAxis && yAxis) {
+ grpName = yAxis.name;
+ }
+ } else if (!group && size) {
+ grpName = size.name;
+ }
+
+ for (i = 0; i < rows.length; i++) {
+ row = rows[i];
+ if (xAxis) {
+ xValue = row[xAxis.index];
+ }
+ if (yAxis) {
+ yValue = row[yAxis.index];
+ }
+ if (group) {
+ grpName = row[group.index];
+ }
+ var sz = (isAllDiscrete) ? row[row.length - 1] : ((size) ? row[size.index] : 1);
+
+ if (grpNameIndex[grpName] === undefined) {
+ grpIndexValue[grpIdx] = grpName;
+ grpNameIndex[grpName] = grpIdx++;
+ }
+
+ if (xAxis && rowNameIndex[xValue] === undefined) {
+ rowIndexValue[rowIdx] = xValue;
+ rowNameIndex[xValue] = rowIdx++;
+ }
+
+ if (yAxis && colNameIndex[yValue] === undefined) {
+ colIndexValue[colIdx] = yValue;
+ colNameIndex[yValue] = colIdx++;
+ }
+
+ if (!d3g[grpNameIndex[grpName]]) {
+ d3g[grpNameIndex[grpName]] = {
+ key: grpName,
+ values: []
+ };
+ }
+
+ d3g[grpNameIndex[grpName]].values.push({
+ x: xAxis ? (isNaN(xValue) ? rowNameIndex[xValue] : parseFloat(xValue)) : 0,
+ y: yAxis ? (isNaN(yValue) ? colNameIndex[yValue] : parseFloat(yValue)) : 0,
+ size: isNaN(parseFloat(sz)) ? 1 : parseFloat(sz)
+ });
+ }
+
+ return {
+ xLabels: rowIndexValue,
+ yLabels: colIndexValue,
+ d3g: d3g
+ };
+};
+zeppelin.ScatterchartVisualization.prototype.setDiscreteScatterData = function(data) {
+ var xAxis = this.config.scatter.xAxis;
+ var yAxis = this.config.scatter.yAxis;
+ var group = this.config.scatter.group;
+
+ var xValue;
+ var yValue;
+ var grp;
+
+ var rows = {};
+
+ for (var i = 0; i < data.rows.length; i++) {
+ var row = data.rows[i];
+ if (xAxis) {
+ xValue = row[xAxis.index];
+ }
+ if (yAxis) {
+ yValue = row[yAxis.index];
+ }
+ if (group) {
+ grp = row[group.index];
+ }
+
+ var key = xValue + ',' + yValue + ',' + grp;
+
+ if (!rows[key]) {
+ rows[key] = {
+ x: xValue,
+ y: yValue,
+ group: grp,
+ size: 1
+ };
+ } else {
+ rows[key].size++;
+ }
+ }
+
+ // change object into array
+ var newRows = [];
+ for (var r in rows) {
+ var newRow = [];
+ if (xAxis) { newRow[xAxis.index] = rows[r].x; }
+ if (yAxis) { newRow[yAxis.index] = rows[r].y; }
+ if (group) { newRow[group.index] = rows[r].group; }
+ newRow[data.rows[0].length] = rows[r].size;
+ newRows.push(newRow);
+ }
+ return newRows;
+};
+
+zeppelin.ScatterchartVisualization.prototype.isDiscrete = function(field) {
+ var getUnique = function(f) {
+ var uniqObj = {};
+ var uniqArr = [];
+ var j = 0;
+ for (var i = 0; i < f.length; i++) {
+ var item = f[i];
+ if (uniqObj[item] !== 1) {
+ uniqObj[item] = 1;
+ uniqArr[j++] = item;
+ }
+ }
+ return uniqArr;
+ };
+
+ for (var i = 0; i < field.length; i++) {
+ if (isNaN(parseFloat(field[i])) &&
+ (typeof field[i] === 'string' || field[i] instanceof String)) {
+ return true;
+ }
+ }
+
+ var threshold = 0.05;
+ var unique = getUnique(field);
+ if (unique.length / field.length < threshold) {
+ return true;
+ } else {
+ return false;
+ }
+};
+
+zeppelin.ScatterchartVisualization.prototype.isValidSizeOption = function(options) {
+ var xValues = [];
+ var yValues = [];
+ var rows = this.tableData.rows;
+
+ for (var i = 0; i < rows.length; i++) {
+ var row = rows[i];
+ var size = row[options.size.index];
+
+ //check if the field is numeric
+ if (isNaN(parseFloat(size)) || !isFinite(size)) {
+ return false;
+ }
+
+ if (options.xAxis) {
+ var x = row[options.xAxis.index];
+ xValues[i] = x;
+ }
+ if (options.yAxis) {
+ var y = row[options.yAxis.index];
+ yValues[i] = y;
+ }
+ }
+
+ //check if all existing fields are discrete
+ var isAllDiscrete = ((options.xAxis && options.yAxis && this.isDiscrete(xValues) && this.isDiscrete(yValues)) ||
+ (!options.xAxis && this.isDiscrete(yValues)) ||
+ (!options.yAxis && this.isDiscrete(xValues)));
+
+ if (isAllDiscrete) {
+ return false;
+ }
+
+ return true;
+};
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-table.js b/zeppelin-web/src/app/visualization/builtins/visualization-table.js
new file mode 100644
index 00000000000..b8016491e26
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-table.js
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+/**
+ * Visualize data in table format
+ */
+zeppelin.TableVisualization = function(targetEl) {
+ zeppelin.Visualization.call(this, targetEl);
+ console.log('Init table viz');
+ targetEl.addClass('table');
+};
+
+zeppelin.TableVisualization.prototype = Object.create(zeppelin.Visualization.prototype);
+
+zeppelin.TableVisualization.prototype.refresh = function() {
+ this.hot.render();
+};
+
+zeppelin.TableVisualization.prototype.render = function(tableData) {
+ var height = this.targetEl.height();
+ var container = this.targetEl.css('height', height).get(0);
+ var resultRows = tableData.rows;
+ var columnNames = _.pluck(tableData.columns, 'name');
+
+ if (this.hot) {
+ this.hot.destroy();
+ }
+
+ this.hot = new Handsontable(container, {
+ colHeaders: columnNames,
+ data: resultRows,
+ rowHeaders: false,
+ stretchH: 'all',
+ sortIndicator: true,
+ columnSorting: true,
+ contextMenu: false,
+ manualColumnResize: true,
+ manualRowResize: true,
+ readOnly: true,
+ readOnlyCellClassName: '', // don't apply any special class so we can retain current styling
+ fillHandle: false,
+ fragmentSelection: true,
+ disableVisualSelection: true,
+ cells: function(row, col, prop) {
+ var cellProperties = {};
+ cellProperties.renderer = function(instance, td, row, col, prop, value, cellProperties) {
+ if (value instanceof moment) {
+ td.innerHTML = value._i;
+ } else if (!isNaN(value)) {
+ cellProperties.format = '0,0.[00000]';
+ td.style.textAlign = 'left';
+ Handsontable.renderers.NumericRenderer.apply(this, arguments);
+ } else if (value.length > '%html'.length && '%html ' === value.substring(0, '%html '.length)) {
+ td.innerHTML = value.substring('%html'.length);
+ } else {
+ Handsontable.renderers.TextRenderer.apply(this, arguments);
+ }
+ };
+ return cellProperties;
+ }
+ });
+};
+
+zeppelin.TableVisualization.prototype.destroy = function() {
+ if (this.hot) {
+ this.hot.destroy();
+ }
+};
diff --git a/zeppelin-web/src/app/visualization/visualization.js b/zeppelin-web/src/app/visualization/visualization.js
new file mode 100644
index 00000000000..e415b8aefe3
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/visualization.js
@@ -0,0 +1,103 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+var zeppelin = zeppelin || {};
+
+/**
+ * Base class for visualization
+ */
+zeppelin.Visualization = function(targetEl, config) {
+ this.targetEl = targetEl;
+ this.config = config;
+ this._resized = false;
+ this._active = false;
+};
+
+/**
+ * get transformation
+ */
+zeppelin.Visualization.prototype.getTransformation = function() {
+ // override this
+};
+
+/**
+ * Method will be invoked when data or configuration changed
+ */
+zeppelin.Visualization.prototype.render = function(tableData) {
+ // override this
+};
+
+/**
+ * Refresh visualization.
+ */
+zeppelin.Visualization.prototype.refresh = function() {
+ // override this
+};
+
+/**
+ * Activate. invoked when visualization is selected
+ */
+zeppelin.Visualization.prototype.activate = function() {
+ console.log('active');
+ if (!this._active && this._resized) {
+ var self = this;
+ // give some time for element ready
+ setTimeout(function() {self.refresh();}, 300);
+ this._resized = false;
+ }
+ this._active = true;
+};
+
+/**
+ * Activate. invoked when visualization is de selected
+ */
+zeppelin.Visualization.prototype.deactivate = function() {
+ console.log('deactive');
+ this._active = false;
+};
+
+/**
+ * Is active
+ */
+zeppelin.Visualization.prototype.isActive = function() {
+ return this._active;
+};
+
+/**
+ * When window or paragraph is resized
+ */
+zeppelin.Visualization.prototype.resize = function() {
+ if (this.isActive()) {
+ this.refresh();
+ } else {
+ this._resized = true;
+ }
+};
+
+/**
+ * Set new config
+ */
+zeppelin.Visualization.prototype.setConfig = function(config) {
+ this.config = config;
+};
+
+/**
+ * method will be invoked when visualization need to be destroyed.
+ * Don't need to destroy this.targetEl.
+ */
+zeppelin.Visualization.prototype.destroy = function() {
+ // override this
+};
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index 4ffec037a39..c4d644ec9e3 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -153,6 +153,17 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/zeppelin-web/test/.jshintrc b/zeppelin-web/test/.jshintrc
index b1be025b81d..c82b6daceeb 100644
--- a/zeppelin-web/test/.jshintrc
+++ b/zeppelin-web/test/.jshintrc
@@ -30,7 +30,8 @@
"inject": false,
"it": false,
"jasmine": false,
- "spyOn": false
+ "spyOn": false,
+ "zeppelin" : false
}
}
diff --git a/zeppelin-web/test/karma.conf.js b/zeppelin-web/test/karma.conf.js
index f9f03a413fa..cc5a63ad2bf 100644
--- a/zeppelin-web/test/karma.conf.js
+++ b/zeppelin-web/test/karma.conf.js
@@ -69,13 +69,16 @@ module.exports = function(config) {
// endbower
'src/app/app.js',
'src/app/app.controller.js',
+ 'src/app/tabledata/transformation.js',
'src/app/**/*.js',
'src/components/**/*.js',
'test/spec/**/*.js'
],
// list of files / patterns to exclude
- exclude: [],
+ exclude: [
+ 'src/app/visualization/builtins/*.js'
+ ],
// web server port
port: 9002,
diff --git a/zeppelin-web/test/spec/controllers/paragraph.js b/zeppelin-web/test/spec/controllers/paragraph.js
index bb483f4b3c0..1ba51e428e2 100644
--- a/zeppelin-web/test/spec/controllers/paragraph.js
+++ b/zeppelin-web/test/spec/controllers/paragraph.js
@@ -36,7 +36,7 @@ describe('Controller: ParagraphCtrl', function() {
'closeTable', 'openTable', 'showTitle', 'hideTitle', 'setTitle', 'showLineNumbers', 'hideLineNumbers',
'changeColWidth', 'columnWidthClass', 'toggleGraphOption', 'toggleOutput', 'loadForm',
'aceChanged', 'aceLoaded', 'getEditorValue', 'getProgress', 'getExecutionTime', 'isResultOutdated',
- 'getResultType', 'loadTableData', 'setGraphMode', 'isGraphMode', 'onGraphOptionChange',
+ 'getResultType', 'setGraphMode', 'isGraphMode', 'onGraphOptionChange',
'removeGraphOptionKeys', 'removeGraphOptionValues', 'removeGraphOptionGroups', 'setGraphOptionValueAggr',
'removeScatterOptionXaxis', 'removeScatterOptionYaxis', 'removeScatterOptionGroup',
'removeScatterOptionSize'];
@@ -64,10 +64,8 @@ describe('Controller: ParagraphCtrl', function() {
scope.getResultType = jasmine.createSpy('getResultType spy').andCallFake(function() {
return 'TABLE';
});
- spyOn(scope, 'loadTableData');
spyOn(scope, 'setGraphMode');
scope.init(paragraphMock);
- expect(scope.loadTableData).toHaveBeenCalled();
expect(scope.setGraphMode).toHaveBeenCalled();
expect(scope.getGraphMode()).toEqual('table');
});
diff --git a/zeppelin-web/test/spec/tabledata/tabledata.js b/zeppelin-web/test/spec/tabledata/tabledata.js
new file mode 100644
index 00000000000..90909f132bf
--- /dev/null
+++ b/zeppelin-web/test/spec/tabledata/tabledata.js
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+'use strict';
+
+describe('TableData build', function() {
+ var td;
+
+ beforeEach(function() {
+ var TableData = zeppelin.TableData;
+ td = new TableData();
+ });
+
+ it('should initialize the default value', function() {
+ expect(td.columns.length).toBe(0);
+ expect(td.rows.length).toBe(0);
+ expect(td.comment).toBe('');
+ });
+
+ it('should able to create Tabledata from paragraph result', function() {
+ td.loadParagraphResult({
+ type: 'TABLE',
+ msg: 'key\tvalue\na\t10\nb\t20\n\nhello'
+ });
+
+ expect(td.columns.length).toBe(2);
+ expect(td.rows.length).toBe(2);
+ expect(td.comment).toBe('hello');
+ });
+});