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 2017/04/14 22:34:37 UTC

[3/3] zeppelin git commit: [ZEPPELIN-2217] AdvancedTransformation for Visualization

[ZEPPELIN-2217] AdvancedTransformation for Visualization

### What is this PR for?

`AdvancedTransformation` has more detailed options while providing existing features of `PivotTransformation` and `ColumnselectorTransformation` which Zeppelin already has

![av_in_30sec](https://cloud.githubusercontent.com/assets/4968473/24037330/c9478e86-0b40-11e7-9886-1ffb85042a7a.gif)

Here are some features which advanced-transformation can provide.

1. **(screenshot)** multiple sub charts
2. **(screenshot)** parameter widgets: `input`, `checkbox`, `option`, `textarea`
3. **(screenshot)** expand/fold axis and parameter panels
4. **(screenshot)** clear axis and parameter panels
5. **(screenshot)** remove duplicated columns in an axis
6. **(screenshot)** limit column count in an axis
7. configurable char axes: `valueType`, `axisType`, `description`, ...
8. configurable chart parameters
9. lazy transformation
10. parsing parameters automatically based on their type: `int`, `float`, `string`, `JSON`
11. multiple transformation methods
12. re-initialize whole configuration based on spec hash.
13. **(screenshot)** shared axis

#### API Details: Spec

`AdvancedTransformation` requires `spec` which includes axis and parameter details for charts.

- Let's create 2 sub-charts called `simple-line` and `step-line`.
- Each sub chart can have different `axis` and `parameter` depending on their requirements.

```js
  constructor(targetEl, config) {
    super(targetEl, config)

    const spec = {
      charts: {
        'simple-line': {
          sharedAxis: true, /** set if you want to share axes between sub charts, default is `false` */
          axis: {
            'xAxis': { dimension: 'multiple', axisType: 'key', },
            'yAxis': { dimension: 'multiple', axisType: 'aggregator'},
            'category': { dimension: 'multiple', axisType: 'group', },
          },
          parameter: {
            'xAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of xAxis', },
            'yAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of yAxis', },
            'dashLength': { valueType: 'int', defaultValue: 0, description: 'the length of dash', },
          },
        },

        'step-line': {
          axis: {
            'xAxis': { dimension: 'single', axisType: 'unique', },
            'yAxis': { dimension: 'multiple', axisType: 'value', },
          },
          parameter: {
            'xAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of xAxis', },
            'yAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of yAxis', },
            'noStepRisers': { valueType: 'boolean', defaultValue: false, description: 'no risers in step line', widget: 'checkbox', },
        },

      },
    }

    this.transformation = new AdvancedTransformation(config, spec)
  }
```

####  API Details: Axis Spec

| Field Name | Available Values (type) | Description |
| --- | --- | --- |
|`dimension` | `single` | Axis can contains only 1 column |
|`dimension` | `multiple` | Axis can contains multiple columns |
|`axisType` | `key` | Column(s) in this axis will be used as `key` like in `PivotTransformation`. These columns will be served in `column.key` |
|`axisType` | `aggregator` | Column(s) in this axis will be used as `value` like in `PivotTransformation`. These columns will be served in `column.aggregator` |
|`axisType` | `group` | Column(s) in this axis will be used as `group` like in `PivotTransformation`. These columns will be served in `column.group` |
|`axisType` | (string) | Any string value can be used here. These columns will be served in `column.custom` |
|`maxAxisCount` | (int) | The maximum column count that this axis can contains. (unlimited if `undefined`) |
|`valueType` | (string) | Describe the value type just for annotation |

Here is an example.

```js
          axis: {
            'xAxis': { dimension: 'multiple', axisType: 'key',  },
            'yAxis': { dimension: 'multiple', axisType: 'aggregator'},
            'category': { dimension: 'multiple', axisType: 'group', maxAxisCount: 2, valueType: 'string', },
          },
```

####  API Details: Parameter Spec

| Field Name | Available Values (type) | Description |
| --- | --- | --- |
|`valueType` | `string` | Parameter which has string value |
|`valueType` | `int` | Parameter which has int value |
|`valueType` | `float` | Parameter which has float value |
|`valueType` | `boolean` | Parameter which has boolean value used with `checkbox` widget usually |
|`valueType` | `JSON` | Parameter which has JSON value used with `textarea` widget usually. `defaultValue` should be `""` (empty string). This ||`defaultValue` | (any) | Default value of this parameter. `JSON` type should have `""` (empty string) |
|`description` | (string) | Description of this parameter. This value will be parsed as HTML for pretty output |
|`widget` | `input` |  Use [input](https://developer.mozilla.org/en/docs/Web/HTML/Element/input) widget. This is the default widget (if `widget` is undefined)|
|`widget` | `checkbox` |  Use [checkbox](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox) widget. |
|`widget` | `textarea` |  Use [textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) widget. |
|`widget` | `option` |  Use [select + option](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) widget. This parameter should have `optionValues` field as well. |
|`optionValues` | (Array<string>) |  Available option values used with the `option` widget |

Here is an example.

```js
parameter: {
  // string type, input widget
  'xAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of xAxis', },

  // boolean type, checkbox widget
  'inverted': { widget: 'checkbox', valueType: 'boolean', defaultValue: false, description: 'invert x and y axes', },

  // string type, option widget with `optionValues`
  'graphType': { widget: 'option', valueType: 'string', defaultValue: 'line', description: 'graph type', optionValues: [ 'line', 'smoothedLine', 'step', ], },

  // HTML in `description`
  'dateFormat': { valueType: 'string', defaultValue: '', description: 'format of date (<a href="https://docs.amcharts.com/3/javascriptcharts/AmGraph#dateFormat">doc</a>) (e.g YYYY-MM-DD)', },

  // JSON type, textarea widget
  'yAxisGuides': { widget: 'textarea', valueType: 'JSON', defaultValue: '', description: 'guides of yAxis ', },
```

#### API Details: Transformer Spec

`AdvancedTransformation` supports 3 transformation methods. The return value will depend on the transformation method type.

```js
    const spec = {
      charts: {
        'simple': {
          /** default value of `transform.method` is the flatten cube.  */
          axis: { ... },
          parameter: { ... }
        },

        'cube-group': {
          transform: { method: 'cube', },
          axis: { ... },
          parameter: { ... },
        }

        'no-group': {
          transform: { method: 'raw', },
          axis: { ... },
          parameter: { ... },
        }
```

| Field Name | Available Values (type) | Description |
| --- | --- | --- |
|`method` | `object` |  designed for [amcharts: serial](https://www.amcharts.com/demos/date-based-data/) |
|`method` | `array` | designed for [highcharts: column](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/column-basic/) |
|`method` | `drill-down` | designed for [highcharts: drill-down](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/demo/column-drilldown/) |
|`method` | `raw` | will return the original `tableData.rows` |

Whatever you specified as `transform.method`, the `transformer` value will be always function for lazy computation.

```js
// advanced-transformation.util#getTransformer

  if (transformSpec.method === 'raw') {
    transformer = () => { return rows; }
  } else if (transformSpec.method === 'array') {
    transformer = () => {
      ...
      return { ... }
    }
  }

```

#### Feature Details: Automatic parameter parsing

Advanced transformation will parse parameter values automatically based on their type: `int`, `float`, `string`, `JSON`

- See also `advanced-transformation-util.js#parseParameter`

#### Feature Details: re-initialize the whole configuration based on spec hash

```js
// advanced-transformation-util#initializeConfig

  const currentVersion = JSON.stringify(spec)
  if (!config.spec || !config.spec.version || config.spec.version !== currentVersion) {
    spec.version = currentVersion
    // reset config...
  }
```

#### Feature Details: Shared Axes

If you set `sharedAxis` to `true` in chart specification, then these charts will share their axes. (default is `false`)

```js
    const spec = {
      charts: {
        'column': {
          transform: { method: 'array', },
          sharedAxis: true,
          axis: { ... },
          parameter: { ... },
        },

        'stacked': {
          transform: { method: 'array', },
          sharedAxis: true,
          axis: { ... }
          parameter: { ... },
        },
```

![sharedaxis](https://cloud.githubusercontent.com/assets/4968473/24207116/6999ad8a-0f63-11e7-8b61-273b712612fc.gif)

#### API Details: Usage in Visualization#render()

Let's assume that we want to create 2 sub-charts called `basic` and `no-group`.

- https://github.com/1ambda/zeppelin-ultimate-line-chart (an practical example)

```js
  drawBasicChart(parameter, column, transformer) {
    const { ... } = transformer()
  }

  drawNoGroupChart(parameter, column, transformer) {
    const { ... } = transformer()
  }

  render(data) {
    const { chart, parameter, column, transformer, } = data

    if (chart === 'basic') {
      this.drawBasicChart(parameter, column, transformer)
    } else if (chart === 'no-group') {
      this.drawNoGroupChart(parameter, column, transformer)
    }
  }
```

### What type of PR is it?
[Feature]

### Todos

NONE

### What is the Jira issue?

[ZEPPELIN-2217](https://issues.apache.org/jira/browse/ZEPPELIN-2217)

### How should this be tested?

1. Clone https://github.com/1ambda/zeppelin-ultimate-line-chart
2. Create a symbolic link `ultimate-line-chart.json` into `$ZEPPELIN_HOME/helium`
3. Modify the `artifact` value to proper absolute path considering your local machine.
4. Install the above visualization in `localhost:9000/#helium`
5. Test it

### Screenshots (if appropriate)

#### 1. *(screenshot)* multiple sub charts

![av_multiple_charts](https://cloud.githubusercontent.com/assets/4968473/24034638/7b84dba0-0b35-11e7-989d-059ccc87f968.gif)

#### 2. *(screenshot)* parameter widgets: `input`, `checkbox`, `option`, `textarea`

![av_widgets_new](https://cloud.githubusercontent.com/assets/4968473/24034652/88679d6c-0b35-11e7-835a-3970d7124850.gif)

#### 3. *(screenshot)* expand/fold axis and parameter panels

![av_fold_expand](https://cloud.githubusercontent.com/assets/4968473/24034653/8a634ddc-0b35-11e7-9851-15280a6b5fd3.gif)

#### 4. *(screenshot)* clear axis and parameter panels

![av_clean_buttons](https://cloud.githubusercontent.com/assets/4968473/24034654/8d3dc14a-0b35-11e7-98c7-3aeddce6d80a.gif)

#### 5. *(screenshot)* remove duplicated columns in an axis

![av_duplicated_columns](https://cloud.githubusercontent.com/assets/4968473/24034657/910f4d20-0b35-11e7-9e9b-d9e2f799a5dd.gif)

#### 6. *(screenshot)* limit column count in an axis

![av_maxaxiscount](https://cloud.githubusercontent.com/assets/4968473/24034679/a5e8eb34-0b35-11e7-89cd-070f3790d511.gif)

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

Author: 1ambda <1a...@gmail.com>

Closes #2098 from 1ambda/ZEPPELIN-2217/advanced-transformation and squashes the following commits:

6cde7c9 [1ambda] fix reset params when spec change
c75a3f2 [1ambda] fix: Reset persisted axis
6a2130a [1ambda] fix: clear config only when axis changed
5464e84 [1ambda] fix: Optimize array 2 key method
9beb1e7 [1ambda] fix: Type error
2408225 [1ambda] test: Add test for array 2key
bf56761 [1ambda] feat: Add array:2-key transform method
7c6768f [1ambda] feat: Use axisSpec.desc as tooltip
f98d4c9 [1ambda] fix: Remove invalid key  prop
5cf2ece [1ambda] feat: Add minAxisCount
4887800 [1ambda] fix: Remove local module yarn caches
3e29572 [1ambda] refactor: copyModule func
c91a033 [1ambda] fix: Set yarn cache dir in helium-bundles
04b5140 [1ambda] fix: Import a-tr
0a876cf [1ambda] docs: Update index.md
380b1af [1ambda] docs: Fix typo and add desc for existing trs
908214b [1ambda] docs: Move experimental tags
a009627 [1ambda] feat: Allow dup aggr axis
3b44e92 [1ambda] fix: Remove unuse const
ab6c22e [1ambda] test: Add test for drill-down method
756107a [1ambda] test: Add array transformation method
d819c73 [1ambda] test: Add object method
bf00fba [1ambda] test: Add MockTableData
39fe5ae [1ambda] test: Add test for getColumnsFromAxis
4c393b4 [1ambda] fix: Add polyfill for es6 funcs in test
e92c787 [1ambda] test: Add test for rmDup, aplMaxAxisCount
843f45d [1ambda] test: Add test for getCurrent* funcs
ae5277c [1ambda] test: Add test for initializeConfig
c14a9dc7 [1ambda] test: Add tests for widget, params
c510af1 [1ambda] docs: Add doc for Transformation
52db37b [1ambda] feat: Show panel menus only when opened
17ad4a4 [1ambda] feat: Support chartChanged, parameterChanged
c0d33d3 [1ambda] fix: Sort selectors in drilldown method
cfd6fef [1ambda] feat: sharedAxis
9af80ce [1ambda] style: Indent
79b5654 [1ambda] fix: return the same info in transform
7bee464 [1ambda] fix: Keynames
ee8788e [1ambda] feat: Support drill-down
666025a [1ambda] fix: DON'T reset current chart
ae1891f [1ambda] add array:key transform
4167a2e [1ambda] fix: Sort keyNames
912b5b7 [1ambda] fix: Persist initialized config
f1f6b0c [1ambda] feat: Support ARRAY transform.method
812f9a2 [1ambda] fix: Set proper aggr value when 0 group
20f9437 [1ambda] fix: getCube func
25d51a9 [1ambda] DON'T display aggr.name when aggrColumns.length == 1
f37e13d [1ambda] fix: Add 'object' transform.method
da2370c [1ambda] fix: Add resetAxis, Param funcs
2370682 [1ambda] fix: average is not caculated correctly
dd08e38 [1ambda] fix: Set param panel height to 400
881695a [1ambda] feat: clear chart, param separately
4d0d62b [1ambda] fix: DON'T clean panel config
92676d1 [1ambda] fix: limit parameter panel height to 370
cc29060 [1ambda] feat: parse param description as HTML
9a2d227 [1ambda] fix: Stop event propagation in widgets
fcc625c [1ambda] feat: Automatic param parsing
b4d774c [1ambda] fix: Dont close param panel when enter
088705b [1ambda] refactor: Remove util and add Widget funcs
bf88b4f [1ambda] feat: textare widget and update hook
4e73012 [1ambda] feat: widget checkbox
11b7eaa [1ambda] feat: option widget
5d3efc9 [1ambda] fix: Change panel header
b1d9d31 [1ambda] feat: Save and close with enter key
53f508c [1ambda] feat: custom axisSpec
0dbc431 [1ambda] feat: Support transformer
94d837a [1ambda] feat: Automatic spec versioning
74b8b4e [1ambda] fix: Duplicated radio btn id, name
5b88f08 [1ambda] fix: Modify margin of subchart radio btns
019892c [1ambda] feat: Support transform: flatten
0484e1e [1ambda] feat: Support maxAxisCount in axisSpec
936901b [1ambda] feat: Support undefined valueType in axisSpec
7a454ff [1ambda] feat: Cube Transformation
f0ed02f [1ambda] feat: Support same axis types
49985c6 [1ambda] refactor: Refine axis, param spec
d89e223 [1ambda] feat: advanced-transformation-api
75569ce [1ambda] feat: Support multiple charts in UI
e1fcc2e [1ambda] feat: Support multiple charts
97be629 [1ambda] fix: Add singleDimensionAggregatorChanged
676bd7e [1ambda] refactor: Refine transform API
9fb398e [1ambda] feat: Add clearConfig
a8a4fb1 [1ambda] refactor: Add getAxisInSingleDimension func
9768ecf [1ambda] feat: Add groupBase axis option
91ae54d [1ambda] fix: Overflow issue in single aggr
10c80fc [1ambda] feat: AdvancedTransformation


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

Branch: refs/heads/master
Commit: 45cc8a9e8a23a57271dec384245d0012a0e5e608
Parents: 2173b40
Author: 1ambda <1a...@gmail.com>
Authored: Tue Apr 11 23:14:46 2017 +0900
Committer: Lee moon soo <mo...@apache.org>
Committed: Sat Apr 15 07:34:27 2017 +0900

----------------------------------------------------------------------
 docs/_includes/themes/zeppelin/_navigation.html |    9 +-
 docs/development/writingzeppelinapplication.md  |    4 +-
 docs/development/writingzeppelinspell.md        |    4 +-
 .../development/writingzeppelinvisualization.md |    4 +-
 ...itingzeppelinvisualization_transformation.md |  281 +++
 docs/index.md                                   |    9 +-
 zeppelin-web/karma.conf.js                      |    6 +-
 zeppelin-web/package.json                       |    2 +-
 .../advanced-transformation-setting.html        |  280 +++
 .../tabledata/advanced-transformation-util.js   | 1259 +++++++++++++
 .../advanced-transformation-util.test.js        | 1746 ++++++++++++++++++
 .../app/tabledata/advanced-transformation.js    |  230 +++
 zeppelin-web/src/index.js                       |    1 +
 .../zeppelin/helium/HeliumBundleFactory.java    |  101 +-
 .../helium/HeliumBundleFactoryTest.java         |    2 +-
 15 files changed, 3878 insertions(+), 60 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/docs/_includes/themes/zeppelin/_navigation.html
----------------------------------------------------------------------
diff --git a/docs/_includes/themes/zeppelin/_navigation.html b/docs/_includes/themes/zeppelin/_navigation.html
index 623fac3..4e49a1a 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -117,10 +117,11 @@
                 <li><a href="{{BASE_PATH}}/security/notebook_authorization.html">Notebook Authorization</a></li>
                 <li><a href="{{BASE_PATH}}/security/datasource_authorization.html">Data Source Authorization</a></li>
                 <li role="separator" class="divider"></li>
-                <li class="title"><span><b>Helium Framework</b><span></li>
-                <li><a href="{{BASE_PATH}}/development/writingzeppelinapplication.html">Writing Zeppelin Application (Experimental)</a></li>
-                <li><a href="{{BASE_PATH}}/development/writingzeppelinspell.html">Writing Zeppelin Spell (Experimental)</a></li>
-                <li><a href="{{BASE_PATH}}/development/writingzeppelinvisualization.html">Writing Zeppelin Visualization (Experimental)</a></li>
+                <li class="title"><span><b>Helium Framework (Experimental)</b></span></li>
+                <li><a href="{{BASE_PATH}}/development/writingzeppelinapplication.html">Writing Zeppelin Application</a></li>
+                <li><a href="{{BASE_PATH}}/development/writingzeppelinspell.html">Writing Zeppelin Spell</a></li>
+                <li><a href="{{BASE_PATH}}/development/writingzeppelinvisualization.html">Writing Zeppelin Visualization: Basics</a></li>
+                <li><a href="{{BASE_PATH}}/development/writingzeppelinvisualization_transformation.html">Writing Zeppelin Visualization: Transformation</a></li>
                 <li role="separator" class="divider"></li>
                 <li class="title"><span><b>Advanced</b><span></li>
                 <li><a href="{{BASE_PATH}}/install/virtual_machine.html">Zeppelin on Vagrant VM</a></li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/docs/development/writingzeppelinapplication.md
----------------------------------------------------------------------
diff --git a/docs/development/writingzeppelinapplication.md b/docs/development/writingzeppelinapplication.md
index 7048cb3..59f980a 100644
--- a/docs/development/writingzeppelinapplication.md
+++ b/docs/development/writingzeppelinapplication.md
@@ -1,6 +1,6 @@
 ---
 layout: page
-title: "Writing a new Application(Experimental)"
+title: "Writing a new Application"
 description: "Apache Zeppelin Application is a package that runs on Interpreter process and displays it's output inside of the notebook. Make your own Application in Apache Zeppelin is quite easy."
 group: development
 ---
@@ -19,7 +19,7 @@ limitations under the License.
 -->
 {% include JB/setup %}
 
-# Writing a new Application (Experimental)
+# Writing a new Application
 
 <div id="toc"></div>
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/docs/development/writingzeppelinspell.md
----------------------------------------------------------------------
diff --git a/docs/development/writingzeppelinspell.md b/docs/development/writingzeppelinspell.md
index 02a2301..1eefe83 100644
--- a/docs/development/writingzeppelinspell.md
+++ b/docs/development/writingzeppelinspell.md
@@ -1,6 +1,6 @@
 ---
 layout: page
-title: "Writing a new Spell(Experimental)"
+title: "Writing a new Spell"
 description: "Spell is a kind of interpreter that runs on browser not on backend. So, technically it's the frontend interpreter. "
 group: development
 ---
@@ -19,7 +19,7 @@ limitations under the License.
 -->
 {% include JB/setup %}
 
-# Writing a new Spell (Experimental)
+# Writing a new Spell
 
 <div id="toc"></div>
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/docs/development/writingzeppelinvisualization.md
----------------------------------------------------------------------
diff --git a/docs/development/writingzeppelinvisualization.md b/docs/development/writingzeppelinvisualization.md
index 5ccd970..5a0601f 100644
--- a/docs/development/writingzeppelinvisualization.md
+++ b/docs/development/writingzeppelinvisualization.md
@@ -1,6 +1,6 @@
 ---
 layout: page
-title: "Writing a new Visualization(Experimental)"
+title: "Writing a new Visualization"
 description: "Apache Zeppelin Visualization is a pluggable package that can be loaded/unloaded on runtime through Helium framework in Zeppelin. A Visualization is a javascript npm package and user can use them just like any other built-in visualization in a note."
 group: development
 ---
@@ -19,7 +19,7 @@ limitations under the License.
 -->
 {% include JB/setup %}
 
-# Writing a new Visualization (Experimental)
+# Writing a new Visualization
 
 <div id="toc"></div>
 

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/docs/development/writingzeppelinvisualization_transformation.md
----------------------------------------------------------------------
diff --git a/docs/development/writingzeppelinvisualization_transformation.md b/docs/development/writingzeppelinvisualization_transformation.md
new file mode 100644
index 0000000..22bf130
--- /dev/null
+++ b/docs/development/writingzeppelinvisualization_transformation.md
@@ -0,0 +1,281 @@
+---
+layout: page
+title: "Transformations for Zeppelin Visualization"
+description: "Description for Transformations"
+group: development
+---
+<!--
+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.
+-->
+{% include JB/setup %}
+
+# Transformations for Zeppelin Visualization
+
+<div id="toc"></div>
+
+## Overview 
+
+Transformations 
+
+- **renders** setting which allows users to set columns and 
+- **transforms** table rows according to the configured columns.
+
+Zeppelin provides 4 types of transformations.
+
+## 1. PassthroughTransformation
+
+`PassthroughTransformation` is the simple transformation which does not convert original tabledata at all.
+
+See [passthrough.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/tabledata/passthrough.js)
+
+## 2. ColumnselectorTransformation
+
+`ColumnselectorTransformation` is uses when you need `N` axes but do not need aggregation. 
+
+See [columnselector.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/tabledata/columnselector.js)
+
+## 3. PivotTransformation
+
+`PivotTransformation` provides group by and aggregation. Every chart using `PivotTransformation` has 3 axes. `Keys`, `Groups` and `Values`.
+
+See [pivot.js](https://github.com/apache/zeppelin/blob/master/zeppelin-web/src/app/tabledata/pivot.js)
+
+## 4. AdvancedTransformation
+
+`AdvancedTransformation` has more detailed options while providing existing features of `PivotTransformation` and `ColumnselectorTransformation`
+
+- multiple sub charts
+- configurable chart axes
+- parameter widgets: `input`, `checkbox`, `option`, `textarea`
+- parsing parameters automatically based on their types
+- expand / fold axis and parameter panels
+- multiple transformation methods while supporting lazy converting 
+- re-initialize the whole configuration based on spec hash.
+
+### Spec 
+
+`AdvancedTransformation` requires `spec` which includes axis and parameter details for charts.
+
+Let's create 2 sub-charts called `line` and `no-group`. Each sub chart can have different axis and parameter depending on their requirements.
+
+<br/>
+
+```js
+class AwesomeVisualization extends Visualization {
+  constructor(targetEl, config) {
+    super(targetEl, config)
+  
+    const spec = {
+      charts: {
+        'line': {
+          transform: { method: 'object', },
+          sharedAxis: false, /** set if you want to share axes between sub charts, default is `false` */
+          axis: {
+            'xAxis': { dimension: 'multiple', axisType: 'key', description: 'serial', },
+            'yAxis': { dimension: 'multiple', axisType: 'aggregator', description: 'serial', },
+            'category': { dimension: 'multiple', axisType: 'group', description: 'categorical', },
+          },
+          parameter: {
+            'xAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of xAxis', },
+            'yAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of yAxis', },
+            'lineWidth': { valueType: 'int', defaultValue: 0, description: 'width of line', },
+          },
+        },
+  
+        'no-group': {
+          transform: { method: 'object', },
+          sharedAxis: false,
+          axis: {
+            'xAxis': { dimension: 'single', axisType: 'key', },
+            'yAxis': { dimension: 'multiple', axisType: 'value', },
+          },
+          parameter: {
+            'xAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of xAxis', },
+            'yAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of yAxis', },
+        },
+      },
+    }
+
+    this.transformation = new AdvancedTransformation(config, spec)
+  }
+  
+  ...
+  
+  // `render` will be called whenever `axis` or `parameter` is changed 
+  render(data) {
+    const { chart, parameter, column, transformer, } = data
+   
+    if (chart === 'line') {
+      const transformed = transformer()
+      // draw line chart 
+    } else if (chart === 'no-group') {
+      const transformed = transformer()
+      // draw no-group chart 
+    }
+  }
+}
+```
+
+<br/>
+
+### Spec: `axis` 
+
+| Field Name | Available Values (type) | Description |
+| --- | --- | --- |
+|`dimension` | `single` | Axis can contains only 1 column |
+|`dimension` | `multiple` | Axis can contains multiple columns |
+|`axisType` | `key` | Column(s) in this axis will be used as `key` like in `PivotTransformation`. These columns will be served in `column.key` |
+|`axisType` | `aggregator` | Column(s) in this axis will be used as `value` like in `PivotTransformation`. These columns will be served in `column.aggregator` |
+|`axisType` | `group` | Column(s) in this axis will be used as `group` like in `PivotTransformation`. These columns will be served in `column.group` |
+|`axisType` | (string) | Any string value can be used here. These columns will be served in `column.custom` |
+|`maxAxisCount` (optional) | (int) | The max number of columns that this axis can contain. (unlimited if `undefined`) |
+|`minAxisCount` (optional) | (int) | The min number of columns that this axis should contain to draw chart. (`1` in case of single dimension) |
+|`description` (optional) | (string) | Description for the axis. |
+
+<br/>
+
+Here is an example.
+
+```js
+axis: {
+  'xAxis': { dimension: 'multiple', axisType: 'key',  },
+  'yAxis': { dimension: 'multiple', axisType: 'aggregator'},
+  'category': { dimension: 'multiple', axisType: 'group', maxAxisCount: 2, valueType: 'string', },
+},
+```
+
+<br/>
+
+### Spec: `sharedAxis` 
+
+If you set `sharedAxis: false` for sub charts, then their axes are persisted in global space (shared). It's useful for when you creating multiple sub charts sharing their axes but have different parameters. For example, 
+
+- `basic-column`, `stacked-column`, `percent-column`
+- `pie` and `donut`
+
+<br/>
+
+Here is an example.
+
+```js
+    const spec = {
+      charts: {
+        'column': {
+          transform: { method: 'array', },
+          sharedAxis: true,
+          axis: { ... },
+          parameter: { ... },
+        },
+
+        'stacked': {
+          transform: { method: 'array', },
+          sharedAxis: true,
+          axis: { ... }
+          parameter: { ... },
+        },
+```
+
+<br/>
+
+### Spec: `parameter` 
+
+| Field Name | Available Values (type) | Description |
+| --- | --- | --- |
+|`valueType` | `string` | Parameter which has string value |
+|`valueType` | `int` | Parameter which has int value |
+|`valueType` | `float` | Parameter which has float value |
+|`valueType` | `boolean` | Parameter which has boolean value used with `checkbox` widget usually |
+|`valueType` | `JSON` | Parameter which has JSON value used with `textarea` widget usually. `defaultValue` should be `""` (empty string). This ||`defaultValue` | (any) | Default value of this parameter. `JSON` type should have `""` (empty string) |
+|`description` | (string) | Description of this parameter. This value will be parsed as HTML for pretty output |
+|`widget` | `input` |  Use [input](https://developer.mozilla.org/en/docs/Web/HTML/Element/input) widget. This is the default widget (if `widget` is undefined)|
+|`widget` | `checkbox` |  Use [checkbox](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/checkbox) widget. |
+|`widget` | `textarea` |  Use [textarea](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/textarea) widget. |
+|`widget` | `option` |  Use [select + option](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) widget. This parameter should have `optionValues` field as well. |
+|`optionValues` | (Array<string>) |  Available option values used with the `option` widget |
+
+<br/>
+
+Here is an example.
+
+```js
+parameter: {
+  // string type, input widget
+  'xAxisUnit': { valueType: 'string', defaultValue: '', description: 'unit of xAxis', },
+
+  // boolean type, checkbox widget
+  'inverted': { widget: 'checkbox', valueType: 'boolean', defaultValue: false, description: 'invert x and y axes', },
+
+  // string type, option widget with `optionValues`
+  'graphType': { widget: 'option', valueType: 'string', defaultValue: 'line', description: 'graph type', optionValues: [ 'line', 'smoothedLine', 'step', ], },
+
+  // HTML in `description`
+  'dateFormat': { valueType: 'string', defaultValue: '', description: 'format of date (<a href="https://docs.amcharts.com/3/javascriptcharts/AmGraph#dateFormat">doc</a>) (e.g YYYY-MM-DD)', },
+ 
+  // JSON type, textarea widget
+  'yAxisGuides': { widget: 'textarea', valueType: 'JSON', defaultValue: '', description: 'guides of yAxis ', },
+```
+
+<br/>
+
+### Spec: `transform`
+
+| Field Name | Available Values (type) | Description |
+| --- | --- | --- |
+|`method` | `object` |  designed for rows requiring object manipulation | 
+|`method` | `array` |  designed for rows requiring array manipulation | 
+|`method` | `array:2-key` |  designed for xyz charts (e.g bubble chart) | 
+|`method` | `drill-down` |  designed for drill-down charts | 
+|`method` | `raw` | will return the original `tableData.rows` | 
+
+<br/>
+
+Whatever you specified as `transform.method`, the `transformer` value will be always function for lazy computation. 
+
+```js
+// advanced-transformation.util#getTransformer
+
+if (transformSpec.method === 'raw') {
+  transformer = () => { return rows; }
+} else if (transformSpec.method === 'array') {
+  transformer = () => {
+    ...
+    return { ... }
+  }
+}
+```
+
+Here is actual usage.
+
+```js
+class AwesomeVisualization extends Visualization {
+  constructor(...) { /** setup your spec */ }
+  
+  ... 
+  
+  // `render` will be called whenever `axis` or `parameter` are changed
+  render(data) {
+    const { chart, parameter, column, transformer, } = data
+   
+    if (chart === 'line') {
+      const transformed = transformer()
+      // draw line chart 
+    } else if (chart === 'no-group') {
+      const transformed = transformer()
+      // draw no-group chart 
+    }
+  }
+  
+  ...
+}
+```
+

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/docs/index.md
----------------------------------------------------------------------
diff --git a/docs/index.md b/docs/index.md
index 8065aef..043538d 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -172,10 +172,11 @@ Join to our [Mailing list](https://zeppelin.apache.org/community.html) and repor
   * [Shiro Authentication](./security/shiroauthentication.html)
   * [Notebook Authorization](./security/notebook_authorization.html)
   * [Data Source Authorization](./security/datasource_authorization.html)
-* Helium Framework
-  * [Writing Zeppelin Application (Experimental)](./development/writingzeppelinapplication.html)
-  * [Writing Zeppelin Spell (Experimental)](./development/writingzeppelinspell.html)
-  * [Writing Zeppelin Visualization (Experimental)](./development/writingzeppelinvisualization.html)
+* Helium Framework (Experimental)
+  * [Writing Zeppelin Application](./development/writingzeppelinapplication.html)
+  * [Writing Zeppelin Spell](./development/writingzeppelinspell.html)
+  * [Writing Zeppelin Visualization: Basic](./development/writingzeppelinvisualization.html)
+  * [Writing Zeppelin Visualization: Transformation](./development/writingzeppelinvisualization_transformation.html)
 * Advanced
   * [Apache Zeppelin on Vagrant VM](./install/virtual_machine.html)
   * [Zeppelin on Spark Cluster Mode (Standalone via Docker)](./install/spark_cluster_mode.html#spark-standalone-mode)

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/zeppelin-web/karma.conf.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/karma.conf.js b/zeppelin-web/karma.conf.js
index b24e36a..f47741a 100644
--- a/zeppelin-web/karma.conf.js
+++ b/zeppelin-web/karma.conf.js
@@ -37,6 +37,9 @@ module.exports = function(config) {
 
     // list of files / patterns to load in the browser
     files: [
+      // for polyfill
+      'node_modules/babel-polyfill/dist/polyfill.js',
+
       // bower:js
       'bower_components/jquery/dist/jquery.js',
       'bower_components/es5-shim/es5-shim.js',
@@ -124,8 +127,7 @@ module.exports = function(config) {
 
     preprocessors: {
       'src/*/{*.js,!(test)/**/*.js}': 'coverage',
-      'src/index.js': ['webpack', 'sourcemap',],
-      'src/**/*.test.js': ['webpack', 'sourcemap',],
+      'src/**/*.js': ['webpack', 'sourcemap',],
     },
 
     coverageReporter: {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/zeppelin-web/package.json
----------------------------------------------------------------------
diff --git a/zeppelin-web/package.json b/zeppelin-web/package.json
index 6e1252b..99dc058 100644
--- a/zeppelin-web/package.json
+++ b/zeppelin-web/package.json
@@ -16,7 +16,7 @@
     "dev:watch": "grunt watch-webpack-dev",
     "dev": "npm-run-all --parallel dev:server dev:watch",
     "visdev": "npm-run-all --parallel visdev:server dev:watch",
-    "pretest": "npm install karma-phantomjs-launcher",
+    "pretest": "npm install karma-phantomjs-launcher babel-polyfill",
     "test": "karma start karma.conf.js"
   },
   "dependencies": {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/45cc8a9e/zeppelin-web/src/app/tabledata/advanced-transformation-setting.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/advanced-transformation-setting.html b/zeppelin-web/src/app/tabledata/advanced-transformation-setting.html
new file mode 100644
index 0000000..8393bf3
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/advanced-transformation-setting.html
@@ -0,0 +1,280 @@
+<!--
+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="panel panel-default" style="margin-top: 10px; margin-bottom: 11px;">
+
+  <!-- panel: axis (configured column) information -->
+  <div class="panel-heading"
+       style="padding: 6px 12px 6px 12px; font-size: 13px;">
+    <span style="vertical-align: middle; display: inline-block; margin-top: 3px;">Charts</span>
+    <span style="float: right;">
+       <div class="btn-group" role="group" aria-label="...">
+         <div type="button" ng-click="resetAxisConfig()"
+              ng-if="config.panel.columnPanelOpened"
+              class="btn btn-default" style="padding: 2px 5px 2px 5px;">
+           <i class="fa fa-trash-o" aria-hidden="true"></i>
+         </div>
+         <div type="button" ng-if="config.panel.columnPanelOpened"
+              ng-click="toggleColumnPanel()"
+              class="btn btn-default" style="padding: 2px 5px 2px 5px;">
+           <i class="fa fa-minus" style="font-size: 12px;" aria-hidden="true"></i>
+         </div>
+         <div type="button" ng-if="!config.panel.columnPanelOpened"
+              ng-click="toggleColumnPanel()"
+              class="btn btn-default" style="padding: 2px 5px 2px 5px;">
+           <i class="fa fa-expand" style="font-size: 11px;" aria-hidden="true"></i>
+         </div>
+       </div>
+    </span>
+    <div style="clear: both;"></div> <!-- to fix previous span which has float: right -->
+  </div>
+  <div class="panel-body" ng-if="config.panel.columnPanelOpened"
+       style="padding: 8px; margin-top: 3px;">
+    <ul class="noDot">
+      <li class="liVertical" ng-repeat="chart in config.chart.available">
+        <label class="radio-inline">
+          <input type="radio" style="margin-top: 1px; margin-left: -17px;"
+                 ng-checked="config.chart.current === chart"
+                 ng-click="chartChanged(chart)" value="{{chart}}" />
+          <span style="vertical-align: middle;">
+            {{chart}} {{useSharedAxis(chart) ? '(shared)' : ''}}
+          </span>
+        </label>
+      </li>
+    </ul>
+  </div>
+
+  <!-- panel: available columns -->
+  <div class="panel-heading" ng-if="config.panel.columnPanelOpened"
+       style="padding: 6px 12px 6px 12px; font-size: 13px; border-top: 1px solid #ddd; border-top-left-radius: 0px; border-top-right-radius: 0px;">
+    <span>Available Columns</span>
+  </div>
+  <div class="panel-body" ng-if="config.panel.columnPanelOpened"
+       style="padding: 8px; margin-top: 3px;">
+    <ul class="noDot">
+      <li class="liVertical" ng-repeat="column in columns">
+        <div class="btn btn-default btn-xs"
+             style="background-color: #EFEFEF;"
+             data-drag="true"
+             data-jqyoui-options="{revert: 'invalid', helper: 'clone'}"
+             ng-model="columns"
+             jqyoui-draggable="{index: {{$index}}, placeholder: 'keep'}">
+          {{column.name | limitTo: 30}}{{column.name.length > 30 ? '...' : ''}}
+        </div>
+      </li>
+    </ul>
+  </div>
+
+  <!-- panel: axis (configured columns) -->
+  <hr style="margin: 1px;" ng-if="config.panel.columnPanelOpened" />
+  <div class="panel-body" ng-if="config.panel.columnPanelOpened"
+       style="margin-top: 7px; padding-top: 9px; padding-bottom: 4px;">
+    <div class="row">
+      <div class="col-sm-4 col-md-3"
+           ng-repeat="axisSpec in config.axisSpecs[config.chart.current]">
+        <div class="columns lightBold">
+          <!-- axis name -->
+          <span class="label label-default"
+                uib-tooltip="{{axisSpec.description ? axisSpec.description : ''}}"
+                style="font-weight: 300; font-size: 13px; margin-left: 1px;">
+            {{getAxisAnnotation(axisSpec)}}
+          </span>
+          <span class="label label-default"
+                ng-style="getAxisTypeAnnotationColor(axisSpec)"
+                style="font-weight: 300; font-size: 13px; margin-left: 3px;">
+            {{getAxisTypeAnnotation(axisSpec)}}
+          </span>
+
+          <!-- axis box: in case of single dimension -->
+          <ul data-drop="true"
+              ng-if="isSingleDimensionAxis(axisSpec)"
+              ng-model="config.axis[config.chart.current][axisSpec.name]"
+              jqyoui-droppable="{onDrop:'axisChanged(axisSpec)'}"
+              class="list-unstyled"
+              style="height:36px; border-radius: 6px; margin-top: 7px; overflow: visible !important;">
+            <li ng-if="config.axis[config.chart.current][axisSpec.name]">
+
+              <!-- in case of axis is single dimension and not aggregator -->
+              <div ng-if="!isAggregatorAxis(axisSpec)"
+                   class="btn btn-default btn-xs"
+                   style="background-color: #EFEFEF;">
+                {{ getSingleDimensionAxis(axisSpec).name }}
+                <span class="fa fa-close" ng-click="removeFromAxis(null, axisSpec)"></span>
+              </div>
+
+              <!-- in case of axis is single dimension and aggregator -->
+              <div class="btn-group">
+                <div ng-if="isAggregatorAxis(axisSpec)"
+                     class="btn btn-default btn-xs dropdown-toggle"
+                     style="background-color: #EFEFEF; "
+                     type="button" data-toggle="dropdown">
+                  {{getSingleDimensionAxis(axisSpec).name | limitTo: 30}}{{getSingleDimensionAxis(axisSpec).name > 30 ? '...' : ''}}
+                  <span style="color:#717171;">
+                    <span class="lightBold" style="text-transform: uppercase;">{{getSingleDimensionAxis(axisSpec).aggr}}</span>
+                  </span>
+                  <span class="fa fa-close" ng-click="removeFromAxis(null, axisSpec)"></span>
+                </div>
+                <ul class="dropdown-menu" role="menu">
+                  <li ng-click="aggregatorChanged(null, axisSpec, 'sum')"><a>sum</a></li>
+                  <li ng-click="aggregatorChanged(null, axisSpec, 'count')"><a>count</a></li>
+                  <li ng-click="aggregatorChanged(null, axisSpec, 'avg')"><a>avg</a></li>
+                  <li ng-click="aggregatorChanged(null, axisSpec, 'min')"><a>min</a></li>
+                  <li ng-click="aggregatorChanged(null, axisSpec, 'max')"><a>max</a></li>
+                </ul>
+              </div>
+
+            </li>
+          </ul>
+
+          <!-- axis box: in case of multiple dimensions -->
+          <ul data-drop="true"
+              ng-if="!isSingleDimensionAxis(axisSpec) "
+              ng-model="config.axis[config.chart.current][axisSpec.name]"
+              jqyoui-droppable="{multiple: true, onDrop:'axisChanged(axisSpec)'}"
+              class="list-unstyled"
+              style="height: 108px; border-radius: 6px; margin-top: 7px; overflow: auto !important;">
+
+            <span ng-repeat="col in config.axis[config.chart.current][axisSpec.name]">
+
+              <!-- in case of axis is multiple dimensions and not aggregator -->
+              <span ng-if="!isAggregatorAxis(axisSpec)"
+                    class="btn btn-default btn-xs"
+                    style="background-color: #EFEFEF; margin: 2px 0px 0px 2px;">
+                {{col.name}}
+                <span class="fa fa-close" ng-click="removeFromAxis($index, axisSpec)"></span>
+              </span>
+
+              <!-- in case of axis is multiple dimension and aggregator -->
+              <span class="btn-group">
+                <span ng-if="isAggregatorAxis(axisSpec)"
+                      class="btn btn-default btn-xs dropdown-toggle"
+                      style="background-color: #EFEFEF; margin: 2px 0px 0px 2px;"
+                      type="button" data-toggle="dropdown">
+                  {{col.name | limitTo: 30}}{{col.name.length > 30 ? '...' : ''}}
+                  <span style="color:#717171; margin: 0px;">
+                    <span class="lightBold"
+                          style="text-transform: uppercase; margin: 0px;">{{col.aggr}}
+                    </span>
+                  </span>
+                  <span class="fa fa-close" style="margin: 0px;" ng-click="removeFromAxis($index, axisSpec)"></span>
+                </span>
+                <ul class="dropdown-menu" role="menu">
+                  <li ng-click="aggregatorChanged($index, axisSpec, 'sum')"><a>sum</a></li>
+                  <li ng-click="aggregatorChanged($index, axisSpec, 'count')"><a>count</a></li>
+                  <li ng-click="aggregatorChanged($index, axisSpec, 'avg')"><a>avg</a></li>
+                  <li ng-click="aggregatorChanged($index, axisSpec, 'min')"><a>min</a></li>
+                  <li ng-click="aggregatorChanged($index, axisSpec, 'max')"><a>max</a></li>
+                </ul>
+              </span>
+
+            </span>
+          </ul>
+
+        </div>
+      </div>
+    </div>
+  </div>
+
+</div>
+
+<!-- panel: parameter information -->
+<div class="panel panel-default">
+
+  <div class="panel-heading" style="padding: 6px 12px 6px 12px; font-size: 13px;">
+    <span style="vertical-align: middle; display: inline-block; margin-top: 3px;">Parameters</span>
+    <span style="float: right;">
+      <div class="btn-group" role="group" aria-label="...">
+        <div type="button" ng-click="parameterChanged()"
+             ng-if="config.panel.parameterPanelOpened"
+             class="btn btn-default" style="padding: 2px 5px 2px 5px;">
+          <i class="fa fa-floppy-o" aria-hidden="true"></i>
+        </div>
+        <div type="button" ng-click="resetParameterConfig()"
+             ng-if="config.panel.parameterPanelOpened"
+             class="btn btn-default" style="padding: 2px 5px 2px 5px;">
+          <i class="fa fa-trash-o" aria-hidden="true"></i>
+        </div>
+        <div type="button" ng-if="config.panel.parameterPanelOpened"
+             ng-click="toggleParameterPanel()"
+             class="btn btn-default" style="padding: 2px 5px 2px 5px;">
+          <i class="fa fa-minus" style="font-size: 12px;" aria-hidden="true"></i>
+        </div>
+        <div type="button" ng-if="!config.panel.parameterPanelOpened"
+             ng-click="toggleParameterPanel()"
+             class="btn btn-default" style="padding: 2px 5px 2px 5px;">
+          <i class="fa fa-expand" style="font-size: 11px;" aria-hidden="true"></i>
+        </div>
+      </div>
+    </span>
+    <div style="clear: both;"></div> <!-- to fix previous span which has float: right -->
+  </div>
+  <div class="panel-body"
+       ng-if="config.panel.parameterPanelOpened"
+       style="padding-top: 13px; padding-bottom: 13px; height: 400px; overflow: auto;">
+    <table class="table table-striped">
+      <tr>
+        <th style="font-size: 12px; font-style: italic">Name</th>
+        <th style="font-size: 12px; font-style: italic">Type</th>
+        <th style="font-size: 12px; font-style: italic">Description</th>
+        <th style="font-size: 12px; font-style: italic">Value</th>
+      </tr>
+      <tr>
+      </tr>
+
+      <tr data-ng-repeat="paramSpec in config.paramSpecs[config.chart.current]">
+        <td style="font-weight: 400; vertical-align: middle;">{{paramSpec.name}}</td>
+        <td style="font-weight: 400; vertical-align: middle;">{{paramSpec.valueType}}</td>
+        <td ng-bind-html="paramSpec.description"
+          style="font-weight: 400; vertical-align: middle;"></td>
+        <td>
+          <div ng-if="isInputWidget(paramSpec)"
+               class="input-group">
+            <input type="text" class="form-control input-sm"
+                   style="font-weight: 400; font-size: 12px; vertical-align:middle; border-radius: 5px;"
+                   ng-keydown="parameterOnKeyDown($event, paramSpec)"
+                   data-ng-model="config.parameter[config.chart.current][paramSpec.name]" />
+          </div>
+          <div class="btn-group"
+               ng-if="isOptionWidget(paramSpec)">
+            <select class="form-control input-sm"
+                    ng-keydown="parameterOnKeyDown($event, paramSpec)"
+                    ng-change="parameterChanged(paramSpec)"
+                    data-ng-model="config.parameter[config.chart.current][paramSpec.name]"
+                    data-ng-options="optionValue for optionValue in paramSpec.optionValues"
+                    style="font-weight: 400; font-size: 12px;">
+            </select>
+          </div>
+
+          <div ng-if="isCheckboxWidget(paramSpec)">
+            <input type="checkbox"
+                   ng-keydown="parameterOnKeyDown($event, paramSpec)"
+                   ng-click="parameterChanged(paramSpec)"
+                   data-ng-model="config.parameter[config.chart.current][paramSpec.name]"
+                   data-ng-checked="config.parameter[config.chart.current][paramSpec.name]" />
+          </div>
+
+          <div ng-if="isTextareaWidget(paramSpec)">
+            <textarea class="form-control" rows="3"
+                      ng-keydown="parameterOnKeyDown($event, paramSpec)"
+                      data-ng-model="config.parameter[config.chart.current][paramSpec.name]"
+                      style="font-weight: 400; font-size: 12px;">
+            </textarea>
+          </div>
+
+        </td>
+      </tr>
+    </table>
+  </div>
+
+</div>