diff --git a/zeppelin-web/app/fonts/custom-font.eot b/zeppelin-web/app/fonts/custom-font.eot new file mode 100644 index 00000000..e5de86b4 Binary files /dev/null and b/zeppelin-web/app/fonts/custom-font.eot differ diff --git a/zeppelin-web/app/fonts/custom-font.svg b/zeppelin-web/app/fonts/custom-font.svg new file mode 100644 index 00000000..fb2769ad --- /dev/null +++ b/zeppelin-web/app/fonts/custom-font.svg @@ -0,0 +1,25 @@ + + + + + +{ + "fontFamily": "custom-font", + "majorVersion": 1, + "minorVersion": 0, + "version": "Version 1.0", + "fontId": "custom-font", + "psName": "custom-font", + "subFamily": "Regular", + "fullName": "custom-font", + "description": "Font generated by IcoMoon." +} + + + + + + + + + \ No newline at end of file diff --git a/zeppelin-web/app/fonts/custom-font.ttf b/zeppelin-web/app/fonts/custom-font.ttf new file mode 100644 index 00000000..bea0a6cf Binary files /dev/null and b/zeppelin-web/app/fonts/custom-font.ttf differ diff --git a/zeppelin-web/app/fonts/custom-font.woff b/zeppelin-web/app/fonts/custom-font.woff new file mode 100644 index 00000000..0d720bd2 Binary files /dev/null and b/zeppelin-web/app/fonts/custom-font.woff differ diff --git a/zeppelin-web/app/index.html b/zeppelin-web/app/index.html index d4a7aef6..b4eaf4e0 100644 --- a/zeppelin-web/app/index.html +++ b/zeppelin-web/app/index.html @@ -47,6 +47,7 @@ + @@ -147,6 +148,7 @@ + diff --git a/zeppelin-web/app/scripts/controllers/paragraph.js b/zeppelin-web/app/scripts/controllers/paragraph.js index 78a10dd8..ce67f9f0 100644 --- a/zeppelin-web/app/scripts/controllers/paragraph.js +++ b/zeppelin-web/app/scripts/controllers/paragraph.js @@ -110,6 +110,10 @@ angular.module('zeppelinWebApp') if (!config.graph.groups) { config.graph.groups = []; } + + if (!config.graph.scatter) { + config.graph.scatter = {}; + } }; $scope.getIframeDimensions = function () { @@ -786,46 +790,81 @@ angular.module('zeppelinWebApp') $scope.chart[type] = chart; } - var p = pivot(data); - - var xColIndexes = $scope.paragraph.config.graph.keys; - var yColIndexes = $scope.paragraph.config.graph.values; - var d3g = []; - // select yColumns. - if (type==='pieChart') { - var d = pivotDataToD3ChartFormat(p, true).d3g; + if (type === 'scatterChart') { + var scatterData = setScatterChart(data, refresh); - $scope.chart[type].x(function(d) { return d.label;}) - .y(function(d) { return d.value;}); + var xLabels = scatterData.xLabels; + var yLabels = scatterData.yLabels; + d3g = scatterData.d3g; - if ( d.length > 0 ) { - for ( var i=0; i'; + 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. + .scatter.useVoronoi(false); + } 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 0) { + 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.values.length===0 && $scope.paragraph.result.columnNames.length > 1) { + 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.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) { @@ -1279,6 +1334,240 @@ angular.module('zeppelinWebApp') }; }; + + 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]; + } + 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; + } 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.setGraphHeight = function() { var height = $('#p'+$scope.paragraph.id+'_graph').height(); diff --git a/zeppelin-web/app/scripts/directives/popover-html-unsafe.js b/zeppelin-web/app/scripts/directives/popover-html-unsafe.js new file mode 100644 index 00000000..95eeb209 --- /dev/null +++ b/zeppelin-web/app/scripts/directives/popover-html-unsafe.js @@ -0,0 +1,15 @@ +'use strict'; + +angular.module('zeppelinWebApp') + .directive('popoverHtmlUnsafePopup', function() { + return { + restrict: 'EA', + replace: true, + scope: { title: '@', content: '@', placement: '@', animation: '&', isOpen: '&' }, + templateUrl: 'views/popover-html-unsafe-popup.html' + }; + }) + + .directive('popoverHtmlUnsafe', ['$tooltip', function($tooltip) { + return $tooltip('popoverHtmlUnsafe', 'popover', 'click'); + }]); diff --git a/zeppelin-web/app/styles/custom-font.css b/zeppelin-web/app/styles/custom-font.css new file mode 100644 index 00000000..040ba23a --- /dev/null +++ b/zeppelin-web/app/styles/custom-font.css @@ -0,0 +1,18 @@ +@font-face { + font-family: 'CustomFont'; + src: url('../fonts/custom-font.eot') format('embedded-opentype'), url('../fonts/custom-font.woff') format('woff'), url('../fonts/custom-font.ttf') format('truetype'), url('../fonts/custom-font.svg') format('svg'); + font-weight: normal; + font-style: normal; +} +.cf { + display: inline-block; + font: normal normal normal 14px/1 CustomFont; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.cf-scatter-chart:before { + content: "\e800"; +} diff --git a/zeppelin-web/app/styles/notebook.css b/zeppelin-web/app/styles/notebook.css index 5e324485..5b08f4b3 100644 --- a/zeppelin-web/app/styles/notebook.css +++ b/zeppelin-web/app/styles/notebook.css @@ -373,7 +373,7 @@ font-size:12px; height:auto; overflow : auto; - min-height: 200px; + /*min-height: 200px;*/ border-top: 1px solid #ecf0f1; } @@ -418,6 +418,13 @@ cursor: pointer; } +.tableDisplay .option .columns a:focus, +.tableDisplay .option .columns a:hover { + text-decoration: none; + outline: 0; + outline-offset: 0px; +} + .graphContainer { position:relative; margin-bottom: 5px; diff --git a/zeppelin-web/app/views/paragraph.html b/zeppelin-web/app/views/paragraph.html index 2f81cc7d..96bf7590 100644 --- a/zeppelin-web/app/views/paragraph.html +++ b/zeppelin-web/app/views/paragraph.html @@ -126,6 +126,10 @@ ng-class="{'active': isGraphMode('lineChart')}" ng-click="setGraphMode('lineChart', true)"> + @@ -157,7 +161,7 @@ -
+
Keys @@ -218,6 +222,79 @@
+
+
+ + xAxis +
    +
  • + +
  • +
+
+
+
+ + yAxis +
    +
  • + +
  • +
+
+
+
+ + group +
    +
  • + +
  • +
+
+
+
+ + size + +
    +
  • + +
  • +
+
+
+
+
@@ -252,6 +329,11 @@ id="p{{paragraph.id}}_lineChart"> + +
+ +
+
+ +
+

+
+
+
\ No newline at end of file