You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@zeppelin.apache.org by mo...@apache.org on 2016/12/03 16:03:03 UTC

zeppelin git commit: [ZEPPELIN-1721] Let each visualization define 'setting'.

Repository: zeppelin
Updated Branches:
  refs/heads/master 2cc4811f2 -> 5d0d0c269


[ZEPPELIN-1721] Let each visualization define 'setting'.

### What is this PR for?
ZEPPELIN-1003 made an abstraction layer for visualization and now visualizations are moved to [zeppelin-web/src/app/visualization/builtins] https://github.com/apache/zeppelin/tree/master/zeppelin-web/src/app/visualization/builtins from single monolithic place `paragraph.controller.js`.

However, 'setting' of each visualizations (i.e. pivot keys, groups, values selection, column selection in scatter chart, etc) are still in paragraph.controller.js and its html template is hardcoded.

This PR lets each visualization implement their own 'setting' and provide facilities to store/load configs for it.

https://github.com/apache/zeppelin/blob/d80a810184457b51b0d9229c663badca01199c26/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js#L86  can be an simple example how each visualization can have its own setting and save configs.

### What type of PR is it?
Improvement

### Todos
* [x] - Make each visualization provides setting template
* [x] - load / save configs for the setting

### What is the Jira issue?
https://issues.apache.org/jira/browse/ZEPPELIN-1721

### Questions:
* Does the licenses files need update? no
* Is there breaking changes for older versions? no
* Does this needs documentation? no

Author: Lee moon soo <mo...@apache.org>

Closes #1711 from Leemoonsoo/ZEPPELIN-1721 and squashes the following commits:

836b1d2 [Lee moon soo] performance optimize
c3c3106 [Lee moon soo] Allow per graph setting
b127949 [Lee moon soo] merge unnecessarily scattered html templates
0dfbaa0 [Lee moon soo] Abstract pivot setting
c0f3c0e [Lee moon soo] abstract transformation setting and apply scatter chart


Project: http://git-wip-us.apache.org/repos/asf/zeppelin/repo
Commit: http://git-wip-us.apache.org/repos/asf/zeppelin/commit/5d0d0c26
Tree: http://git-wip-us.apache.org/repos/asf/zeppelin/tree/5d0d0c26
Diff: http://git-wip-us.apache.org/repos/asf/zeppelin/diff/5d0d0c26

Branch: refs/heads/master
Commit: 5d0d0c2695abece01fef1e25e352bb7285c3090f
Parents: 2cc4811
Author: Lee moon soo <mo...@apache.org>
Authored: Fri Dec 2 15:50:07 2016 -0800
Committer: Lee moon soo <mo...@apache.org>
Committed: Sat Dec 3 08:02:59 2016 -0800

----------------------------------------------------------------------
 .../notebook/paragraph/result/result-graph.html |  23 --
 .../paragraph/result/result-graphOptions.html   |  31 ---
 .../notebook/paragraph/result/result-pivot.html | 168 -------------
 .../paragraph/result/result-results.html        |  62 -----
 .../paragraph/result/result.controller.js       | 237 +++++++------------
 .../app/notebook/paragraph/result/result.html   |  74 +++++-
 .../src/app/tabledata/columnselector.js         |  84 +++++++
 .../app/tabledata/columnselector_settings.html  |  51 ++++
 zeppelin-web/src/app/tabledata/passthrough.js   |  33 +++
 zeppelin-web/src/app/tabledata/pivot.js         | 106 ++++++++-
 .../src/app/tabledata/pivot_settings.html       |  88 +++++++
 .../src/app/tabledata/transformation.js         |  71 ++++++
 .../builtins/visualization-areachart.js         |   3 +-
 .../builtins/visualization-barchart.js          |   3 +-
 .../builtins/visualization-linechart.js         |  34 ++-
 .../builtins/visualization-piechart.js          |   3 +-
 .../builtins/visualization-scatterchart.js      |  54 ++++-
 .../builtins/visualization-table.js             |  10 +-
 .../src/app/visualization/visualization.js      |  83 ++++++-
 zeppelin-web/src/index.html                     |   2 +
 20 files changed, 756 insertions(+), 464 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/notebook/paragraph/result/result-graph.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result-graph.html b/zeppelin-web/src/app/notebook/paragraph/result/result-graph.html
deleted file mode 100644
index 568535f..0000000
--- a/zeppelin-web/src/app/notebook/paragraph/result/result-graph.html
+++ /dev/null
@@ -1,23 +0,0 @@
-<!--
-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.
--->
-<div id="p{{id}}_graph"
-     class="graphContainer"
-     ng-class="{'noOverflow': graphMode=='table'}"
-     ng-show="type =='TABLE'"
-     >
-  <div ng-repeat="viz in builtInTableDataVisualizationList track by $index"
-       id="p{{id}}_{{viz.id}}"
-       ng-show="graphMode == viz.id">
-  </div>
-</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/notebook/paragraph/result/result-graphOptions.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result-graphOptions.html b/zeppelin-web/src/app/notebook/paragraph/result/result-graphOptions.html
deleted file mode 100644
index e3ee069..0000000
--- a/zeppelin-web/src/app/notebook/paragraph/result/result-graphOptions.html
+++ /dev/null
@@ -1,31 +0,0 @@
-<!--
-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.
--->
-<div>
-  <div ng-if="graphMode=='lineChart' || graphMode=='lineWithFocusChart'">
-    <label>
-      <input type="checkbox"
-             ng-model="config.graph.forceY"
-             ng-click="onGraphOptionChange()" />
-      force Y to 0
-    </label>
-    <br/>
-
-    <label>
-      <input type="checkbox"
-             ng-model="config.graph.lineWithFocus"
-             ng-click="onGraphOptionChange()" />
-      show line chart with focus
-    </label>
-  </div>
-</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/notebook/paragraph/result/result-pivot.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result-pivot.html b/zeppelin-web/src/app/notebook/paragraph/result/result-pivot.html
deleted file mode 100644
index 623ee60..0000000
--- a/zeppelin-web/src/app/notebook/paragraph/result/result-pivot.html
+++ /dev/null
@@ -1,168 +0,0 @@
-<!--
-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.
--->
-<div class="option lightBold" style="overflow: visible;"
-   ng-if="type=='TABLE' && graphMode!='table'
-   && config.graph.optionOpen && !asIframe && !viewOnly">
-
-  <div ng-include src="'app/notebook/paragraph/result/result-graphOptions.html'"></div>
-
-  All fields:
-  <div class="allFields row">
-    <ul class="noDot">
-      <li class="liVertical" ng-repeat="col in tableDataColumns">
-        <div class="btn btn-default btn-xs"
-             data-drag="true"
-             data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
-             ng-model="tableDataColumns"
-             jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
-           {{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
-        </div>
-      </li>
-    </ul>
-  </div>
-
-  <div class="row" ng-if="graphMode!='scatterChart'">
-    <div class="col-md-4">
-      <span class="columns lightBold">
-        Keys
-        <ul data-drop="true"
-            ng-model="config.graph.keys"
-            jqyoui-droppable="{multiple:true, onDrop:'onGraphOptionChange()'}"
-            class="list-unstyled">
-          <li ng-repeat="item in config.graph.keys">
-            <div class="btn btn-primary btn-xs">
-              {{item.name}} <span class="fa fa-close" ng-click="removeGraphOptionKeys($index)"></span>
-            </div>
-          </li>
-        </ul>
-      </span>
-    </div>
-    <div class="col-md-4">
-      <span class="columns lightBold">
-        Groups
-        <ul data-drop="true"
-            ng-model="config.graph.groups"
-            jqyoui-droppable="{multiple:true, onDrop:'onGraphOptionChange()'}"
-            class="list-unstyled">
-          <li ng-repeat="item in config.graph.groups">
-            <div class="btn btn-success btn-xs">
-              {{item.name}} <span class="fa fa-close" ng-click="removeGraphOptionGroups($index)"></span>
-            </div>
-          </li>
-        </ul>
-      </span>
-    </div>
-    <div class="col-md-4">
-      <span class="columns lightBold">
-        Values
-        <ul data-drop="true"
-            ng-model="config.graph.values"
-            jqyoui-droppable="{multiple:true, onDrop:'onGraphOptionChange()'}"
-            class="list-unstyled">
-          <li ng-repeat="item in config.graph.values">
-            <div class="btn-group">
-              <div class="btn btn-info btn-xs dropdown-toggle"
-                   type="button"
-                   data-toggle="dropdown">
-                {{item.name | limitTo: 30}}{{item.name.length > 30 ? '...' : ''}}
-                <font style="color:#EEEEEE;"><span class="lightBold" style="text-transform: uppercase;">{{item.aggr}}</span></font>
-                <span class="fa fa-close" ng-click="removeGraphOptionValues($index)"></span>
-              </div>
-              <ul class="dropdown-menu" role="menu">
-                <li ng-click="setGraphOptionValueAggr($index, 'sum')"><a>sum</a></li>
-                <li ng-click="setGraphOptionValueAggr($index, 'count')"><a>count</a></li>
-                <li ng-click="setGraphOptionValueAggr($index, 'avg')"><a>avg</a></li>
-                <li ng-click="setGraphOptionValueAggr($index, 'min')"><a>min</a></li>
-                <li ng-click="setGraphOptionValueAggr($index, 'max')"><a>max</a></li>
-              </ul>
-            </div>
-          </li>
-        </ul>
-      </span>
-    </div>
-  </div>
-
-  <div class="row" ng-if="graphMode=='scatterChart'">
-    <div class="col-md-3">
-      <span class="columns lightBold">
-        xAxis
-        <ul data-drop="true"
-            ng-model="config.graph.scatter.xAxis"
-            jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
-            class="list-unstyled"
-            style="height:36px">
-          <li ng-if="config.graph.scatter.xAxis">
-            <div class="btn btn-primary btn-xs">
-              {{config.graph.scatter.xAxis.name}} <span class="fa fa-close" ng-click="removeScatterOptionXaxis($index)"></span>
-            </div>
-          </li>
-        </ul>
-      </span>
-    </div>
-    <div class="col-md-3">
-      <span class="columns lightBold">
-        yAxis
-        <ul data-drop="true"
-            ng-model="config.graph.scatter.yAxis"
-            jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
-            class="list-unstyled"
-            style="height:36px">
-          <li ng-if="config.graph.scatter.yAxis">
-            <div class="btn btn-success btn-xs">
-              {{config.graph.scatter.yAxis.name}} <span class="fa fa-close" ng-click="removeScatterOptionYaxis($index)"></span>
-            </div>
-          </li>
-        </ul>
-      </span>
-    </div>
-    <div class="col-md-3">
-      <span class="columns lightBold">
-        group
-        <ul data-drop="true"
-            ng-model="config.graph.scatter.group"
-            jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
-            class="list-unstyled"
-            style="height:36px">
-          <li ng-if="config.graph.scatter.group">
-            <div class="btn btn-info btn-xs">
-              {{config.graph.scatter.group.name}} <span class="fa fa-close" ng-click="removeScatterOptionGroup($index)"></span>
-            </div>
-          </li>
-        </ul>
-      </span>
-    </div>
-    <div class="col-md-3">
-      <span class="columns lightBold">
-        size
-        <a tabindex="0" class="fa fa-info-circle" role="button" popover-placement="top"
-           popover-trigger="focus"
-           popover-html-unsafe="<li>Size option is valid only when you drop numeric field here.</li>
-                                <li>When data in each axis are discrete, 'number of values in corresponding coordinate' will be used as size.</li>
-                                <li>Zeppelin consider values as discrete when the values contain string value or the number of distinct values are bigger than 5% of total number of values.</li>
-                                <li>Size field button turns to grey when the option you chose is not valid.</li>"></a>
-        <ul data-drop="true"
-            ng-model="config.graph.scatter.size"
-            jqyoui-droppable="{onDrop:'onGraphOptionChange()'}"
-            class="list-unstyled"
-            style="height:36px">
-          <li ng-if="config.graph.scatter.size">
-            <div class="btn btn-xs" style="color:white" ng-class="{'btn-warning': isValidSizeOption(config.graph.scatter)}">
-              {{config.graph.scatter.size.name}} <span class="fa fa-close" ng-click="removeScatterOptionSize($index)"></span>
-            </div>
-          </li>
-        </ul>
-      </span>
-    </div>
-  </div>
-</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/notebook/paragraph/result/result-results.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result-results.html b/zeppelin-web/src/app/notebook/paragraph/result/result-results.html
deleted file mode 100644
index 4a6d76b..0000000
--- a/zeppelin-web/src/app/notebook/paragraph/result/result-results.html
+++ /dev/null
@@ -1,62 +0,0 @@
-<!--
-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.
--->
-<div
-  id="p{{id}}_resize"
-  ng-if="!config.helium.activeApp"
-  style="padding-bottom: 5px;"
-  resize='{"allowresize": "{{!asIframe && !viewOnly}}", "graphType": "{{type}}"}'
-     resizable on-resize="resize(width, height);">
-  <div ng-include src="'app/notebook/paragraph/result/result-graph.html'"></div>
-
-  <div id="{{id}}_comment"
-       class="text"
-       ng-if="type == 'TABLE' && tableDataComment"
-       ng-bind-html="tableDataComment">
-  </div>
-
-  <div id="p{{id}}_text"
-       ng-if="type == 'TEXT'">
-    <div class="fa fa-level-down scroll-paragraph-down"
-         ng-show="showScrollDownIcon()"
-         ng-click="scrollParagraphDown()"
-         tooltip="Follow Output"></div>
-    <div id="p{{id}}_text"
-         style="max-height: {{config.graph.height}}px; overflow: auto"
-         class="text"></div>
-    <div class="fa fa-chevron-up scroll-paragraph-up"
-         ng-show="showScrollUpIcon()"
-         ng-click="scrollParagraphUp()"
-         tooltip="Scroll Top"></div>
-  </div>
-
-  <div id="p{{id}}_html"
-       class="resultContained"
-       ng-if="type == 'HTML'">
-  </div>
-
-  <div id="p{{id}}_angular"
-       class="resultContained"
-       ng-if="type == 'ANGULAR'">
-  </div>
-
-  <img id="{{id}}_img"
-       ng-if="type == 'IMG'"
-       ng-src="{{getBase64ImageSrc(result.data)}}" />
-</div>
-
-<div ng-repeat="app in apps">
-  <div id="p{{app.id}}"
-       ng-show="config.helium.activeApp == app.id">
-  </div>
-</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
index 8ffb89b..b05ad15 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
@@ -28,6 +28,7 @@
     '$compile',
     '$http',
     '$q',
+    '$templateRequest',
     'websocketMsgSrv',
     'baseUrlSrv',
     'ngToast',
@@ -36,7 +37,7 @@
   ];
 
   function ResultCtrl($scope, $rootScope, $route, $window, $routeParams, $location,
-                      $timeout, $compile, $http, $q, websocketMsgSrv,
+                      $timeout, $compile, $http, $q, $templateRequest, websocketMsgSrv,
                       baseUrlSrv, ngToast, saveAsService, noteVarShareService) {
 
     /**
@@ -153,9 +154,12 @@
         return;
       }
       console.log('updateResult %o %o %o %o', result, newConfig, paragraphRef, index);
+      var refresh = !angular.equals(newConfig, $scope.config) ||
+                    !angular.equals(result.type, $scope.type) ||
+                    !angular.equals(result.data, data);
 
       updateData(result, newConfig, paragraph, resultIndex);
-      renderResult($scope.type, true);
+      renderResult($scope.type, refresh);
     });
 
     $scope.$on('appendParagraphOutput', function(event, data) {
@@ -208,22 +212,6 @@
         config.graph.optionOpen = false;
       }
 
-      if (!config.graph.keys) {
-        config.graph.keys = [];
-      }
-
-      if (!config.graph.values) {
-        config.graph.values = [];
-      }
-
-      if (!config.graph.groups) {
-        config.graph.groups = [];
-      }
-
-      if (!config.graph.scatter) {
-        config.graph.scatter = {};
-      }
-
       $scope.graphMode = config.graph.mode;
       $scope.config = angular.copy(config);
 
@@ -236,8 +224,6 @@
         tableData.loadParagraphResult({type: $scope.type, msg: data});
         $scope.tableDataColumns = tableData.columns;
         $scope.tableDataComment = tableData.comment;
-
-        selectDefaultColsForGraphOption();
       } else if ($scope.type === 'IMG') {
         $scope.imageData = data;
       }
@@ -367,7 +353,6 @@
     };
 
     $scope.renderGraph = function(type, refresh) {
-      clearUnknownColsFromGraphOption();
       // set graph height
       var height = $scope.config.graph.height;
       var graphContainerEl = angular.element('#p' + $scope.id + '_graph');
@@ -382,6 +367,7 @@
         // deactive previsouly active visualization
         for (var t in builtInVisualizations) {
           var v = builtInVisualizations[t].instance;
+
           if (t !== type && v && v.isActive()) {
             v.deactivate();
             break;
@@ -392,16 +378,36 @@
           // render when targetEl is available
           var retryRenderer = function() {
             var targetEl = angular.element('#p' + $scope.id + '_' + type);
-
+            var transformationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + type);
+            var visualizationSettingTargetEl = angular.element('#vizsetting' + $scope.id + '_' + type);
             if (targetEl.length) {
               try {
                 // set height
                 targetEl.height(height);
 
                 // instantiate visualization
+                var config = getVizConfig(type);
                 var Visualization = builtInViz.class;
-                builtInViz.instance = new Visualization(targetEl, $scope.config.graph);
-                builtInViz.instance.render(tableData);
+                builtInViz.instance = new Visualization(targetEl, config);
+
+                // inject emitter, $templateRequest
+                var emitter = function(graphSetting) {
+                  commitVizConfigChange(graphSetting, type);
+                };
+                builtInViz.instance._emitter = emitter;
+                builtInViz.instance._compile = $compile;
+                builtInViz.instance._createNewScope = createNewScope;
+                var transformation = builtInViz.instance.getTransformation();
+                transformation._emitter = emitter;
+                transformation._templateRequest = $templateRequest;
+                transformation._compile = $compile;
+                transformation._createNewScope = createNewScope;
+
+                // render
+                var transformed = transformation.transform(tableData);
+                transformation.renderSetting(transformationSettingTargetEl);
+                builtInViz.instance.render(transformed);
+                builtInViz.instance.renderSetting(visualizationSettingTargetEl);
                 builtInViz.instance.activate();
                 angular.element(window).resize(function() {
                   builtInViz.instance.resize();
@@ -419,10 +425,18 @@
           // when graph options or data are changed
           var retryRenderer = function() {
             var targetEl = angular.element('#p' + $scope.id + '_' + type);
+            var transformationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + type);
+            var visualizationSettingTargetEl = angular.element('#trsetting' + $scope.id + '_' + type);
             if (targetEl.length) {
+              var config = getVizConfig(type);
               targetEl.height(height);
-              builtInViz.instance.setConfig($scope.config.graph);
-              builtInViz.instance.render(tableData);
+              var transformation = builtInViz.instance.getTransformation();
+              transformation.setConfig(config);
+              var transformed = transformation.transform(tableData);
+              transformation.renderSetting(transformationSettingTargetEl);
+              builtInViz.instance.setConfig(config);
+              builtInViz.instance.render(transformed);
+              builtInViz.instance.renderSetting(visualizationSettingTargetEl);
             } else {
               $timeout(retryRenderer, 10);
             }
@@ -455,6 +469,10 @@
       commitParagraphResult(paragraph.title, paragraph.text, newConfig, newParams);
     };
 
+    var createNewScope = function() {
+      return $rootScope.$new(true);
+    };
+
     var commitParagraphResult = function(title, text, config, params) {
       var newParagraphConfig = angular.copy(paragraph.config);
       newParagraphConfig.results = newParagraphConfig.results || [];
@@ -474,144 +492,63 @@
       commitParagraphResult(paragraph.title, paragraph.text, newConfig, newParams);
     };
 
-    var commitConfigChange = function(config) {
-      clearUnknownColsFromGraphOption();
-      var newConfig = angular.copy(config);
-      var newParams = angular.copy(paragraph.settings.params);
-
-      commitParagraphResult(paragraph.title, paragraph.text, newConfig, newParams);
-    };
-
-    $scope.onGraphOptionChange = function() {
-      commitConfigChange($scope.config);
-    };
-
-    $scope.removeGraphOptionKeys = function(idx) {
-      $scope.config.graph.keys.splice(idx, 1);
-      commitConfigChange($scope.config);
-    };
-
-    $scope.removeGraphOptionValues = function(idx) {
-      $scope.config.graph.values.splice(idx, 1);
-      commitConfigChange($scope.config);
-    };
-
-    $scope.removeGraphOptionGroups = function(idx) {
-      $scope.config.graph.groups.splice(idx, 1);
-      commitConfigChange($scope.config);
-    };
-
-    $scope.setGraphOptionValueAggr = function(idx, aggr) {
-      $scope.config.graph.values[idx].aggr = aggr;
-      commitConfigChange($scope.config);
-    };
-
-    $scope.removeScatterOptionXaxis = function(idx) {
-      $scope.config.graph.scatter.xAxis = null;
-      commitConfigChange($scope.config);
-    };
-
-    $scope.removeScatterOptionYaxis = function(idx) {
-      $scope.config.graph.scatter.yAxis = null;
-      commitConfigChange($scope.config);
-    };
-
-    $scope.removeScatterOptionGroup = function(idx) {
-      $scope.config.graph.scatter.group = null;
-      commitConfigChange($scope.config);
-    };
-
-    $scope.removeScatterOptionSize = function(idx) {
-      $scope.config.graph.scatter.size = null;
-      commitConfigChange($scope.config);
-    };
-
-    var clearUnknownColsFromGraphOption = function() {
-      var unique = function(list) {
-        for (var i = 0; i < list.length; i++) {
-          for (var j = i + 1; j < list.length; j++) {
-            if (angular.equals(list[i], list[j])) {
-              list.splice(j, 1);
-            }
-          }
+    var getVizConfig = function(vizId) {
+      var config;
+      var graph = $scope.config.graph;
+      if (graph) {
+        // copy setting for vizId
+        if (graph.setting) {
+          config = angular.copy(graph.setting[vizId]);
         }
-      };
 
-      var removeUnknown = function(list) {
-        for (var i = 0; i < list.length; i++) {
-          // remove non existing column
-          var found = false;
-          for (var j = 0; j < tableData.columns.length; j++) {
-            var a = list[i];
-            var b = tableData.columns[j];
-            if (a.index === b.index && a.name === b.name) {
-              found = true;
-              break;
-            }
-          }
-          if (!found) {
-            list.splice(i, 1);
-          }
-        }
-      };
-
-      var removeUnknownFromFields = function(fields) {
-        for (var f in fields) {
-          if (fields[f]) {
-            var found = false;
-            for (var i = 0; i < tableData.columns.length; i++) {
-              var a = fields[f];
-              var b = tableData.columns[i];
-              if (a.index === b.index && a.name === b.name) {
-                found = true;
-                break;
-              }
-            }
-            if (!found && (fields[f] instanceof Object) && !(fields[f] instanceof Array)) {
-              fields[f] = null;
-            }
-          }
+        if (!config) {
+          config = {};
         }
-      };
 
-      unique($scope.config.graph.keys);
-      removeUnknown($scope.config.graph.keys);
+        // copy common setting
+        config.common = angular.copy(graph.commonSetting) || {};
 
-      removeUnknown($scope.config.graph.values);
-
-      unique($scope.config.graph.groups);
-      removeUnknown($scope.config.graph.groups);
-
-      removeUnknownFromFields($scope.config.graph.scatter);
+        // copy pivot setting
+        if (graph.keys) {
+          config.common.pivot = {
+            keys: angular.copy(graph.keys),
+            groups: angular.copy(graph.groups),
+            values: angular.copy(graph.values)
+          };
+        }
+      }
+      console.log('getVizConfig', config);
+      return config;
     };
 
-    /* select default key and value if there're none selected */
-    var selectDefaultColsForGraphOption = function() {
-      if ($scope.config.graph.keys.length === 0 && tableData.columns.length > 0) {
-        $scope.config.graph.keys.push(tableData.columns[0]);
+    var commitVizConfigChange = function(config, vizId) {
+      var newConfig = angular.copy($scope.config);
+      if (!newConfig.graph) {
+        newConfig.graph = {};
       }
 
-      if ($scope.config.graph.values.length === 0 && tableData.columns.length > 1) {
-        $scope.config.graph.values.push(tableData.columns[1]);
+      // copy setting for vizId
+      if (!newConfig.graph.setting) {
+        newConfig.graph.setting = {};
       }
+      newConfig.graph.setting[vizId] = angular.copy(config);
 
-      if (!$scope.config.graph.scatter.xAxis && !$scope.config.graph.scatter.yAxis) {
-        if (tableData.columns.length > 1) {
-          $scope.config.graph.scatter.xAxis = tableData.columns[0];
-          $scope.config.graph.scatter.yAxis = tableData.columns[1];
-        } else if (tableData.columns.length === 1) {
-          $scope.config.graph.scatter.xAxis = tableData.columns[0];
-        }
+      // copy common setting
+      if (newConfig.graph.setting[vizId]) {
+        newConfig.graph.commonSetting = newConfig.graph.setting[vizId].common;
+        delete newConfig.graph.setting[vizId].common;
       }
-    };
 
-    $scope.isValidSizeOption = function(options) {
-      var builtInViz = builtInVisualizations.scatterChart;
-      if (builtInViz && builtInViz.instance) {
-        return builtInViz.instance.isValidSizeOption(options);
-      } else {
-        return false;
+      // copy pivot setting
+      if (newConfig.graph.commonSetting && newConfig.graph.commonSetting.pivot) {
+        newConfig.graph.keys = newConfig.graph.commonSetting.pivot.keys;
+        newConfig.graph.groups = newConfig.graph.commonSetting.pivot.groups;
+        newConfig.graph.values = newConfig.graph.commonSetting.pivot.values;
+        delete newConfig.graph.commonSetting.pivot;
       }
+      console.log('committVizConfig', newConfig);
+      var newParams = angular.copy(paragraph.settings.params);
+      commitParagraphResult(paragraph.title, paragraph.text, newConfig, newParams);
     };
 
     $scope.$on('paragraphResized', function(event, paragraphId) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/notebook/paragraph/result/result.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.html b/zeppelin-web/src/app/notebook/paragraph/result/result.html
index d45dc78..feb0036 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.html
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.html
@@ -14,6 +14,76 @@ limitations under the License.
 
 <div>
   <div ng-include src="'app/notebook/paragraph/result/result-chart-selector.html'"></div>
-  <div ng-include src="'app/notebook/paragraph/result/result-pivot.html'"></div>
-  <div ng-include src="'app/notebook/paragraph/result/result-results.html'"></div>
+  <div
+    id="p{{id}}_resize"
+    ng-if="!config.helium.activeApp"
+    style="padding-bottom: 5px;"
+    resize='{"allowresize": "{{!asIframe && !viewOnly}}", "graphType": "{{type}}"}'
+       resizable on-resize="resize(width, height);">
+
+    <!-- graph setting -->
+    <div class="option lightBold" style="overflow: visible;"
+         ng-show="type=='TABLE' && graphMode!='table'
+                  && config.graph.optionOpen && !asIframe && !viewOnly">
+      <div ng-repeat="viz in builtInTableDataVisualizationList track by $index"
+           id="trsetting{{id}}_{{viz.id}}"
+           ng-show="graphMode == viz.id"></div>
+      <div ng-repeat="viz in builtInTableDataVisualizationList track by $index"
+           id="vizsetting{{id}}_{{viz.id}}"
+           ng-show="graphMode == viz.id"></div>
+    </div>
+
+    <!-- graph -->
+    <div id="p{{id}}_graph"
+         class="graphContainer"
+         ng-class="{'noOverflow': graphMode=='table'}"
+         ng-show="type =='TABLE'"
+         >
+      <div ng-repeat="viz in builtInTableDataVisualizationList track by $index"
+           id="p{{id}}_{{viz.id}}"
+           ng-show="graphMode == viz.id">
+      </div>
+    </div>
+
+    <div id="{{id}}_comment"
+         class="text"
+         ng-if="type == 'TABLE' && tableDataComment"
+         ng-bind-html="tableDataComment">
+    </div>
+
+    <div id="p{{id}}_text"
+         ng-if="type == 'TEXT'">
+      <div class="fa fa-level-down scroll-paragraph-down"
+           ng-show="showScrollDownIcon()"
+           ng-click="scrollParagraphDown()"
+           tooltip="Follow Output"></div>
+      <div id="p{{id}}_text"
+           style="max-height: {{config.graph.height}}px; overflow: auto"
+           class="text"></div>
+      <div class="fa fa-chevron-up scroll-paragraph-up"
+           ng-show="showScrollUpIcon()"
+           ng-click="scrollParagraphUp()"
+           tooltip="Scroll Top"></div>
+    </div>
+
+    <div id="p{{id}}_html"
+         class="resultContained"
+         ng-if="type == 'HTML'">
+    </div>
+
+    <div id="p{{id}}_angular"
+         class="resultContained"
+         ng-if="type == 'ANGULAR'">
+    </div>
+
+    <img id="{{id}}_img"
+         ng-if="type == 'IMG'"
+         ng-src="{{getBase64ImageSrc(result.data)}}" />
+  </div>
+
+  <div ng-repeat="app in apps">
+    <div id="p{{app.id}}"
+         ng-show="config.helium.activeApp == app.id">
+    </div>
+  </div>
 </div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/tabledata/columnselector.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/columnselector.js b/zeppelin-web/src/app/tabledata/columnselector.js
new file mode 100644
index 0000000..1a0d77f
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/columnselector.js
@@ -0,0 +1,84 @@
+/*
+ * 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 || {};
+
+/**
+ * select columns
+ * - columnSelectorProp
+ *   [
+ *     {
+ *       "name":
+ *       "tooltip":
+ *     },
+ *     ...
+ *   ]
+ */
+zeppelin.ColumnselectorTransformation = function(config, columnSelectorProp) {
+  zeppelin.Transformation.call(this, config);
+  this.props = columnSelectorProp;
+};
+
+zeppelin.ColumnselectorTransformation.prototype = Object.create(zeppelin.Transformation.prototype);
+
+zeppelin.ColumnselectorTransformation.prototype.getSetting = function() {
+  var self = this;
+  var configObj = self.config;
+  return {
+    template: 'app/tabledata/columnselector_settings.html',
+    scope: {
+      config: self.config,
+      props: self.props,
+      tableDataColumns: self.tableDataColumns,
+      save: function() {
+        self.emitConfig(configObj);
+      },
+      remove: function(selectorName) {
+        configObj[selectorName] = null;
+        self.emitConfig(configObj);
+      }
+    }
+  };
+};
+
+/**
+ * Method will be invoked when tableData or config changes
+ */
+zeppelin.ColumnselectorTransformation.prototype.transform = function(tableData) {
+  this.tableDataColumns = tableData.columns;
+  this.removeUnknown();
+  return tableData;
+};
+
+zeppelin.ColumnselectorTransformation.prototype.removeUnknown = function() {
+  var fields = this.config;
+  for (var f in fields) {
+    if (fields[f]) {
+      var found = false;
+      for (var i = 0; i < this.tableDataColumns.length; i++) {
+        var a = fields[f];
+        var b = this.tableDataColumns[i];
+        if (a.index === b.index && a.name === b.name) {
+          found = true;
+          break;
+        }
+      }
+      if (!found && (fields[f] instanceof Object) && !(fields[f] instanceof Array)) {
+        fields[f] = null;
+      }
+    }
+  }
+};

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/tabledata/columnselector_settings.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/columnselector_settings.html b/zeppelin-web/src/app/tabledata/columnselector_settings.html
new file mode 100644
index 0000000..538cc3a
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/columnselector_settings.html
@@ -0,0 +1,51 @@
+<!--
+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.
+-->
+All fields:
+<div class="allFields row">
+  <ul class="noDot">
+    <li class="liVertical" ng-repeat="col in tableDataColumns">
+      <div class="btn btn-default btn-xs"
+           data-drag="true"
+           data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
+           ng-model="tableDataColumns"
+           jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
+        {{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
+      </div>
+    </li>
+  </ul>
+</div>
+
+<div class="row">
+  <div class="col-md-3"
+       ng-repeat="prop in props">
+    <span class="columns lightBold">
+      {{prop.name}}
+      <a tabindex="0" class="fa fa-info-circle" role="button" popover-placement="top"
+         ng-if="prop.tooltip"
+         popover-trigger="focus"
+         popover-html-unsafe="{{prop.tooltip}}"></a>
+      <ul data-drop="true"
+          ng-model="config[prop.name]"
+          jqyoui-droppable="{onDrop:'save()'}"
+          class="list-unstyled"
+          style="height:36px">
+        <li ng-if="config[prop.name]">
+          <div class="btn btn-primary btn-xs">
+            {{config[prop.name].name}} <span class="fa fa-close" ng-click="remove(prop.name)"></span>
+          </div>
+        </li>
+      </ul>
+    </span>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/tabledata/passthrough.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/passthrough.js b/zeppelin-web/src/app/tabledata/passthrough.js
new file mode 100644
index 0000000..c577393
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/passthrough.js
@@ -0,0 +1,33 @@
+/*
+ * 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 || {};
+
+/**
+ * passthough the data
+ */
+zeppelin.PassthroughTransformation = function(config) {
+  zeppelin.Transformation.call(this, config);
+};
+
+zeppelin.PassthroughTransformation.prototype = Object.create(zeppelin.Transformation.prototype);
+
+/**
+ * Method will be invoked when tableData or config changes
+ */
+zeppelin.PassthroughTransformation.prototype.transform = function(tableData) {
+  return tableData;
+};

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/tabledata/pivot.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/pivot.js b/zeppelin-web/src/app/tabledata/pivot.js
index af46fcb..e1dd291 100644
--- a/zeppelin-web/src/app/tabledata/pivot.js
+++ b/zeppelin-web/src/app/tabledata/pivot.js
@@ -25,15 +25,115 @@ zeppelin.PivotTransformation = function(config) {
 
 zeppelin.PivotTransformation.prototype = Object.create(zeppelin.Transformation.prototype);
 
+zeppelin.PivotTransformation.prototype.getSetting = function() {
+  var self = this;
+
+  var configObj = self.config;
+  console.log('getSetting', configObj);
+  return {
+    template: 'app/tabledata/pivot_settings.html',
+    scope: {
+      config: configObj.common.pivot,
+      tableDataColumns: self.tableDataColumns,
+      save: function() {
+        self.emitConfig(configObj);
+      },
+      removeKey: function(idx) {
+        configObj.common.pivot.keys.splice(idx, 1);
+        self.emitConfig(configObj);
+      },
+      removeGroup: function(idx) {
+        configObj.common.pivot.groups.splice(idx, 1);
+        self.emitConfig(configObj);
+      },
+      removeValue: function(idx) {
+        configObj.common.pivot.values.splice(idx, 1);
+        self.emitConfig(configObj);
+      },
+      setValueAggr: function(idx, aggr) {
+        configObj.common.pivot.values[idx].aggr = aggr;
+        self.emitConfig(configObj);
+      }
+    }
+  };
+};
+
 /**
  * Method will be invoked when tableData or config changes
  */
 zeppelin.PivotTransformation.prototype.transform = function(tableData) {
+  this.tableDataColumns = tableData.columns;
+  this.config.common = this.config.common || {};
+  this.config.common.pivot = this.config.common.pivot || {};
+  var config = this.config.common.pivot;
+  var firstTime = (!config.keys && !config.groups && !config.values);
+
+  config.keys = config.keys || [];
+  config.groups = config.groups || [];
+  config.values = config.values || [];
+
+  this.removeUnknown();
+  if (firstTime) {
+    this.selectDefault();
+  }
   return this.pivot(
     tableData,
-    this.config.keys,
-    this.config.groups,
-    this.config.values);
+    config.keys,
+    config.groups,
+    config.values);
+};
+
+zeppelin.PivotTransformation.prototype.removeUnknown = function() {
+  var config = this.config.common.pivot;
+  var tableDataColumns = this.tableDataColumns;
+  var unique = function(list) {
+    for (var i = 0; i < list.length; i++) {
+      for (var j = i + 1; j < list.length; j++) {
+        if (angular.equals(list[i], list[j])) {
+          list.splice(j, 1);
+        }
+      }
+    }
+  };
+
+  var removeUnknown = function(list) {
+    for (var i = 0; i < list.length; i++) {
+      // remove non existing column
+      var found = false;
+      for (var j = 0; j < tableDataColumns.length; j++) {
+        var a = list[i];
+        var b = tableDataColumns[j];
+        if (a.index === b.index && a.name === b.name) {
+          found = true;
+          break;
+        }
+      }
+      if (!found) {
+        list.splice(i, 1);
+      }
+    }
+  };
+
+  unique(config.keys);
+  removeUnknown(config.keys);
+  unique(config.groups);
+  removeUnknown(config.groups);
+  removeUnknown(config.values);
+};
+
+zeppelin.PivotTransformation.prototype.selectDefault = function() {
+  var config = this.config.common.pivot;
+  if (config.keys.length === 0 &&
+      config.groups.length === 0 &&
+      config.values.length === 0) {
+    if (config.keys.length === 0 && this.tableDataColumns.length > 0) {
+      config.keys.push(this.tableDataColumns[0]);
+    }
+
+    if (config.values.length === 0 && this.tableDataColumns.length > 1) {
+      config.values.push(this.tableDataColumns[1]);
+    }
+  }
 };
 
 zeppelin.PivotTransformation.prototype.pivot = function(data, keys, groups, values) {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/tabledata/pivot_settings.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/pivot_settings.html b/zeppelin-web/src/app/tabledata/pivot_settings.html
new file mode 100644
index 0000000..5ca2224
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/pivot_settings.html
@@ -0,0 +1,88 @@
+<!--
+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.
+-->
+All fields:
+<div class="allFields row">
+  <ul class="noDot">
+    <li class="liVertical" ng-repeat="col in tableDataColumns">
+      <div class="btn btn-default btn-xs"
+           data-drag="true"
+           data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
+           ng-model="tableDataColumns"
+           jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
+         {{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
+      </div>
+    </li>
+  </ul>
+</div>
+
+<div class="row" ng-if="graphMode!='scatterChart'">
+  <div class="col-md-4">
+    <span class="columns lightBold">
+      Keys
+      <ul data-drop="true"
+          ng-model="config.keys"
+          jqyoui-droppable="{multiple:true, onDrop:'save()'}"
+          class="list-unstyled">
+        <li ng-repeat="item in config.keys">
+          <div class="btn btn-primary btn-xs">
+            {{item.name}} <span class="fa fa-close" ng-click="removeKey($index)"></span>
+          </div>
+        </li>
+      </ul>
+    </span>
+  </div>
+  <div class="col-md-4">
+    <span class="columns lightBold">
+      Groups
+      <ul data-drop="true"
+          ng-model="config.groups"
+          jqyoui-droppable="{multiple:true, onDrop:'save()'}"
+          class="list-unstyled">
+        <li ng-repeat="item in config.groups">
+          <div class="btn btn-success btn-xs">
+            {{item.name}} <span class="fa fa-close" ng-click="removeGroup($index)"></span>
+          </div>
+        </li>
+      </ul>
+    </span>
+  </div>
+  <div class="col-md-4">
+    <span class="columns lightBold">
+      Values
+      <ul data-drop="true"
+          ng-model="config.values"
+          jqyoui-droppable="{multiple:true, onDrop:'save()'}"
+          class="list-unstyled">
+        <li ng-repeat="item in config.values">
+          <div class="btn-group">
+            <div class="btn btn-info btn-xs dropdown-toggle"
+                 type="button"
+                 data-toggle="dropdown">
+              {{item.name | limitTo: 30}}{{item.name.length > 30 ? '...' : ''}}
+              <font style="color:#EEEEEE;"><span class="lightBold" style="text-transform: uppercase;">{{item.aggr}}</span></font>
+              <span class="fa fa-close" ng-click="removeValue($index)"></span>
+            </div>
+            <ul class="dropdown-menu" role="menu">
+              <li ng-click="setValueAggr($index, 'sum')"><a>sum</a></li>
+              <li ng-click="setValueAggr($index, 'count')"><a>count</a></li>
+              <li ng-click="setValueAggr($index, 'avg')"><a>avg</a></li>
+              <li ng-click="setValueAggr($index, 'min')"><a>min</a></li>
+              <li ng-click="setValueAggr($index, 'max')"><a>max</a></li>
+            </ul>
+          </div>
+        </li>
+      </ul>
+    </span>
+  </div>
+</div>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/tabledata/transformation.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/transformation.js b/zeppelin-web/src/app/tabledata/transformation.js
index f8d3cc7..6950bd5 100644
--- a/zeppelin-web/src/app/tabledata/transformation.js
+++ b/zeppelin-web/src/app/tabledata/transformation.js
@@ -21,6 +21,17 @@ var zeppelin = zeppelin || {};
  */
 zeppelin.Transformation = function(config) {
   this.config = config;
+  this._emitter;
+};
+
+/**
+ * return {
+ *   template : angular template string or url (url should end with .html),
+ *   scope : an object to bind to template scope
+ * }
+ */
+zeppelin.Transformation.prototype.getSetting = function() {
+  // override this
 };
 
 /**
@@ -30,6 +41,66 @@ zeppelin.Transformation.prototype.transform = function(tableData) {
   // override this
 };
 
+/**
+ * render setting
+ */
+zeppelin.Transformation.prototype.renderSetting = function(targetEl) {
+  var setting = this.getSetting();
+  if (!setting) {
+    return;
+  }
+
+  // already readered
+  if (this._scope) {
+    var self = this;
+    console.log('%o %o', self._scope, setting.scope);
+    this._scope.$apply(function() {
+      for (var k in setting.scope) {
+        self._scope[k] = setting.scope[k];
+      }
+
+      for (var k in self._prevSettingScope) {
+        if (!setting.scope[k]) {
+          self._scope[k] = setting.scope[k];
+        }
+      }
+    });
+    return;
+  } else {
+    this._prevSettingScope = setting.scope;
+  }
+
+  var scope = this._createNewScope();
+  for (var k in setting.scope) {
+    scope[k] = setting.scope[k];
+  }
+  var template = setting.template;
+
+  if (template.split('\n').length === 1 &&
+      template.endsWith('.html')) { // template is url
+    var self = this;
+    this._templateRequest(template).then(function(t) {
+      self._render(targetEl, t, scope);
+    });
+  } else {
+    this._render(targetEl, template, scope);
+  }
+};
+
+zeppelin.Transformation.prototype._render = function(targetEl, template, scope) {
+  this._targetEl = targetEl;
+  targetEl.html(template);
+  this._compile(targetEl.contents())(scope);
+  this._scope = scope;
+};
+
 zeppelin.Transformation.prototype.setConfig = function(config) {
   this.config = config;
 };
+
+/**
+ * Emit config. config will sent to server and saved.
+ */
+zeppelin.Transformation.prototype.emitConfig = function(config) {
+  this._emitter(config);
+};

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/visualization/builtins/visualization-areachart.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-areachart.js b/zeppelin-web/src/app/visualization/builtins/visualization-areachart.js
index b91bc16..3a73b53 100644
--- a/zeppelin-web/src/app/visualization/builtins/visualization-areachart.js
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-areachart.js
@@ -35,8 +35,7 @@ zeppelin.AreachartVisualization.prototype.getTransformation = function() {
   return this.pivot;
 };
 
-zeppelin.AreachartVisualization.prototype.render = function(tableData) {
-  var pivot = this.pivot.transform(tableData);
+zeppelin.AreachartVisualization.prototype.render = function(pivot) {
   var d3Data = this.d3DataFromPivot(
     pivot.schema,
     pivot.rows,

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/visualization/builtins/visualization-barchart.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-barchart.js b/zeppelin-web/src/app/visualization/builtins/visualization-barchart.js
index 6480ee0..3475cdd 100644
--- a/zeppelin-web/src/app/visualization/builtins/visualization-barchart.js
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-barchart.js
@@ -34,8 +34,7 @@ zeppelin.BarchartVisualization.prototype.getTransformation = function() {
   return this.pivot;
 };
 
-zeppelin.BarchartVisualization.prototype.render = function(tableData) {
-  var pivot = this.pivot.transform(tableData);
+zeppelin.BarchartVisualization.prototype.render = function(pivot) {
   var d3Data = this.d3DataFromPivot(
     pivot.schema,
     pivot.rows,

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js b/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js
index 6b2c6d4..f513a37 100644
--- a/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-linechart.js
@@ -39,9 +39,7 @@ zeppelin.LinechartVisualization.prototype.getTransformation = function() {
   return this.pivot;
 };
 
-zeppelin.LinechartVisualization.prototype.render = function(tableData) {
-  this.tableData = tableData;
-  var pivot = this.pivot.transform(tableData);
+zeppelin.LinechartVisualization.prototype.render = function(pivot) {
   var d3Data = this.d3DataFromPivot(
     pivot.schema,
     pivot.rows,
@@ -84,3 +82,33 @@ zeppelin.LinechartVisualization.prototype.configureChart = function(chart) {
     chart.forceY([]);
   }
 };
+
+zeppelin.LinechartVisualization.prototype.getSetting = function(chart) {
+  var self = this;
+  var configObj = self.config;
+
+  return {
+    template: `<div>
+      <label>
+        <input type="checkbox"
+             ng-model="config.forceY"
+             ng-click="save()" />
+        force Y to 0
+      </label>
+      <br/>
+
+      <label>
+        <input type="checkbox"
+             ng-model="config.lineWithFocus"
+             ng-click="save()" />
+        show line chart with focus
+      </label>
+    </div>`,
+    scope: {
+      config: configObj,
+      save: function() {
+        self.emitConfig(configObj);
+      }
+    }
+  };
+};

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/visualization/builtins/visualization-piechart.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-piechart.js b/zeppelin-web/src/app/visualization/builtins/visualization-piechart.js
index e600ae5..d685cfd 100644
--- a/zeppelin-web/src/app/visualization/builtins/visualization-piechart.js
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-piechart.js
@@ -34,8 +34,7 @@ zeppelin.PiechartVisualization.prototype.getTransformation = function() {
   return this.pivot;
 };
 
-zeppelin.PiechartVisualization.prototype.render = function(tableData) {
-  var pivot = this.pivot.transform(tableData);
+zeppelin.PiechartVisualization.prototype.render = function(pivot) {
   var d3Data = this.d3DataFromPivot(
     pivot.schema,
     pivot.rows,

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/visualization/builtins/visualization-scatterchart.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-scatterchart.js b/zeppelin-web/src/app/visualization/builtins/visualization-scatterchart.js
index de69909..3dcf78c 100644
--- a/zeppelin-web/src/app/visualization/builtins/visualization-scatterchart.js
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-scatterchart.js
@@ -19,6 +19,28 @@
  */
 zeppelin.ScatterchartVisualization = function(targetEl, config) {
   zeppelin.Nvd3ChartVisualization.call(this, targetEl, config);
+  var ColumnselectorTransformation = zeppelin.ColumnselectorTransformation;
+  this.columnselectorProps = [
+    {
+      name: 'xAxis'
+    },
+    {
+      name: 'yAxis'
+    },
+    {
+      name: 'group'
+    },
+    {
+      name: 'size',
+      tooltip: `<li>Size option is valid only when you drop numeric field here.</li>
+                <li>When data in each axis are discrete,
+                'number of values in corresponding coordinate' will be used as size.</li>
+                <li>Zeppelin consider values as discrete when the values contain string value
+                or the number of distinct values are bigger than 5% of total number of values.</li>
+                <li>Size field button turns to grey when the option you chose is not valid.</li>`
+    }
+  ];
+  this.columnselector = new ColumnselectorTransformation(config, this.columnselectorProps);
 };
 
 zeppelin.ScatterchartVisualization.prototype = Object.create(zeppelin.Nvd3ChartVisualization.prototype);
@@ -28,11 +50,12 @@ zeppelin.ScatterchartVisualization.prototype.type = function() {
 };
 
 zeppelin.ScatterchartVisualization.prototype.getTransformation = function() {
-  return this.pivot;
+  return this.columnselector;
 };
 
 zeppelin.ScatterchartVisualization.prototype.render = function(tableData) {
   this.tableData = tableData;
+  this.selectDefault();
   var d3Data = this.setScatterChart(tableData, true);
   this.xLabels = d3Data.xLabels;
   this.yLabels = d3Data.yLabels;
@@ -49,8 +72,8 @@ zeppelin.ScatterchartVisualization.prototype.configureChart = function(chart) {
   // configure how the tooltip looks.
   chart.tooltipContent(function(key, x, y, graph, data) {
     var tooltipContent = '<h3>' + key + '</h3>';
-    if (self.config.scatter.size &&
-      self.isValidSizeOption(self.config.scatter, self.tableData.rows)) {
+    if (self.config.size &&
+      self.isValidSizeOption(self.config, self.tableData.rows)) {
       tooltipContent += '<p>' + data.point.size + '</p>';
     }
 
@@ -61,11 +84,22 @@ zeppelin.ScatterchartVisualization.prototype.configureChart = function(chart) {
   //handle the problem of tooltip not showing when muliple points have same value.
 };
 
+zeppelin.ScatterchartVisualization.prototype.selectDefault = function() {
+  if (!this.config.xAxis && !this.config.yAxis) {
+    if (this.tableData.columns.length > 1) {
+      this.config.xAxis = this.tableData.columns[0];
+      this.config.yAxis = this.tableData.columns[1];
+    } else if (this.tableData.columns.length === 1) {
+      this.config.xAxis = this.tableData.columns[0];
+    }
+  }
+};
+
 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 xAxis = this.config.xAxis;
+  var yAxis = this.config.yAxis;
+  var group = this.config.group;
+  var size = this.config.size;
 
   var xValues = [];
   var yValues = [];
@@ -178,9 +212,9 @@ zeppelin.ScatterchartVisualization.prototype.setScatterChart = function(data, re
   };
 };
 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 xAxis = this.config.xAxis;
+  var yAxis = this.config.yAxis;
+  var group = this.config.group;
 
   var xValue;
   var yValue;

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/visualization/builtins/visualization-table.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/builtins/visualization-table.js b/zeppelin-web/src/app/visualization/builtins/visualization-table.js
index 7406cd8..69a7938 100644
--- a/zeppelin-web/src/app/visualization/builtins/visualization-table.js
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-table.js
@@ -17,10 +17,12 @@
 /**
  * Visualize data in table format
  */
-zeppelin.TableVisualization = function(targetEl) {
-  zeppelin.Visualization.call(this, targetEl);
+zeppelin.TableVisualization = function(targetEl, config) {
+  zeppelin.Visualization.call(this, targetEl, config);
   console.log('Init table viz');
   targetEl.addClass('table');
+  var PassthroughTransformation = zeppelin.PassthroughTransformation;
+  this.passthrough = new PassthroughTransformation(config);
 };
 
 zeppelin.TableVisualization.prototype = Object.create(zeppelin.Visualization.prototype);
@@ -57,3 +59,7 @@ zeppelin.TableVisualization.prototype.destroy = function() {
     this.hot.destroy();
   }
 };
+
+zeppelin.TableVisualization.prototype.getTransformation = function() {
+  return this.passthrough;
+};

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/app/visualization/visualization.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/visualization/visualization.js b/zeppelin-web/src/app/visualization/visualization.js
index 33364e8..37b1af6 100644
--- a/zeppelin-web/src/app/visualization/visualization.js
+++ b/zeppelin-web/src/app/visualization/visualization.js
@@ -22,8 +22,9 @@ var zeppelin = zeppelin || {};
 zeppelin.Visualization = function(targetEl, config) {
   this.targetEl = targetEl;
   this.config = config;
-  this._resized = false;
+  this._dirty = false;
   this._active = false;
+  this._emitter;
 };
 
 /**
@@ -51,9 +52,9 @@ zeppelin.Visualization.prototype.refresh = function() {
  * Activate. invoked when visualization is selected
  */
 zeppelin.Visualization.prototype.activate = function() {
-  if (!this._active || this._resized) {
+  if (!this._active || this._dirty) {
     this.refresh();
-    this._resized = false;
+    this._dirty = false;
   }
   this._active = true;
 };
@@ -79,7 +80,7 @@ zeppelin.Visualization.prototype.resize = function() {
   if (this.isActive()) {
     this.refresh();
   } else {
-    this._resized = true;
+    this._dirty = true;
   }
 };
 
@@ -88,6 +89,18 @@ zeppelin.Visualization.prototype.resize = function() {
  */
 zeppelin.Visualization.prototype.setConfig = function(config) {
   this.config = config;
+  if (this.isActive()) {
+    this.refresh();
+  } else {
+    this._dirty = true;
+  }
+};
+
+/**
+ * Emit config. config will sent to server and saved.
+ */
+zeppelin.Visualization.prototype.emitConfig = function(config) {
+  this._emitter(config);
 };
 
 /**
@@ -97,3 +110,65 @@ zeppelin.Visualization.prototype.setConfig = function(config) {
 zeppelin.Visualization.prototype.destroy = function() {
   // override this
 };
+
+/**
+ * return {
+ *   template : angular template string or url (url should end with .html),
+ *   scope : an object to bind to template scope
+ * }
+ */
+zeppelin.Visualization.prototype.getSetting = function() {
+  // override this
+};
+
+/**
+ * render setting
+ */
+zeppelin.Visualization.prototype.renderSetting = function(targetEl) {
+  var setting = this.getSetting();
+  if (!setting) {
+    return;
+  }
+
+  // already readered
+  if (this._scope) {
+    var self = this;
+    this._scope.$apply(function() {
+      for (var k in setting.scope) {
+        self._scope[k] = setting.scope[k];
+      }
+
+      for (var k in self._prevSettingScope) {
+        if (!setting.scope[k]) {
+          self._scope[k] = setting.scope[k];
+        }
+      }
+    });
+    return;
+  } else {
+    this._prevSettingScope = setting.scope;
+  }
+
+  var scope = this._createNewScope();
+  for (var k in setting.scope) {
+    scope[k] = setting.scope[k];
+  }
+  var template = setting.template;
+
+  if (template.split('\n').length === 1 &&
+      template.endsWith('.html')) { // template is url
+    var self = this;
+    this._templateRequest(template).then(function(t) {
+      self._renderSetting(targetEl, t, scope);
+    });
+  } else {
+    this._renderSetting(targetEl, template, scope);
+  }
+};
+
+zeppelin.Visualization.prototype._renderSetting = function(targetEl, template, scope) {
+  this._targetEl = targetEl;
+  targetEl.html(template);
+  this._compile(targetEl.contents())(scope);
+  this._scope = scope;
+};

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/5d0d0c26/zeppelin-web/src/index.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/index.html b/zeppelin-web/src/index.html
index d6feabc..738a6ba 100644
--- a/zeppelin-web/src/index.html
+++ b/zeppelin-web/src/index.html
@@ -175,6 +175,8 @@ limitations under the License.
     <script src="app/tabledata/transformation.js"></script>
     <script src="app/tabledata/pivot.js"></script>
     <script src="app/handsontable/handsonHelper.js"></script>
+    <script src="app/tabledata/passthrough.js"></script>
+    <script src="app/tabledata/columnselector.js"></script>
     <script src="app/visualization/visualization.js"></script>
     <script src="app/visualization/builtins/visualization-table.js"></script>
     <script src="app/visualization/builtins/visualization-nvd3chart.js"></script>