Repository: zeppelin
Updated Branches:
  refs/heads/master c8e2d9fa3 -> 43926485c


[ZEPPELIN-2222] Add Network Visualization

### What is this PR for?
This issue is about a new network visualization that can leverage the Property 
Graph Model 
(https://github.com/tinkerpop/gremlin/wiki/Defining-a-Property-Graph), but also 
simple graphs in order to provide a set of base apis that can be used by graph 
dbs (like Neo4j) or graph processing frameworks (like GraphX or Giraph).

### What type of PR is it?
[Feature]
Is related to the #1582

### Todos
* [x] - Added the intepreter apis to manage graphs (under the pakage 
**org.apache.zeppelin.interpreter.graph**)
* [x] - Added the frontend apis to manage graphs (via d3js)

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

### How should this be tested?
You can download [this 
notebook](https://gist.github.com/conker84/9574127c2389d08164423894aa93b67f) to 
test the PR

### Screenshots (if appropriate)
![zeppelin-pr-screen](https://cloud.githubusercontent.com/assets/1833335/23830683/b883e916-0710-11e7-980d-c8ab6bf6d26b.PNG)

### Video
![zeppelin](https://cloud.githubusercontent.com/assets/1833335/23902630/1e121a0c-08c2-11e7-9f28-134866dba077.gif)

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

Author: conker84 <sant...@gmail.com>

Closes #2125 from conker84/master and squashes the following commits:

b6062a0b0 [conker84] Removed package org.apache.zeppelin.interpreter.graph
e98ca7a67 [conker84] Comments of review 14/03/2017
b31b7b7ac [conker84] Rebase of 07/04/2017
3257bea24 [conker84] Rebase 30/4
6e74eb9f3 [conker84] Rebase 30/04


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

Branch: refs/heads/master
Commit: 43926485cd022871374ab0421a1fec75492e9526
Parents: c8e2d9f
Author: conker84 <sant...@gmail.com>
Authored: Thu Jun 8 14:48:01 2017 +0200
Committer: Jongyoul Lee <jongy...@apache.org>
Committed: Fri Jun 9 15:37:43 2017 +0900

----------------------------------------------------------------------
 docs/_includes/themes/zeppelin/_navigation.html |   1 +
 .../img/screenshots/display_complex_network.png | Bin 0 -> 48817 bytes
 .../img/screenshots/display_network.png         | Bin 0 -> 52981 bytes
 .../img/screenshots/display_network_flatten.png | Bin 0 -> 42001 bytes
 .../img/screenshots/display_simple_network.png  | Bin 0 -> 31928 bytes
 docs/displaysystem/basicdisplaysystem.md        | 101 +++++++
 .../zeppelin/interpreter/InterpreterResult.java |   3 +-
 zeppelin-web/.eslintrc                          |   3 +-
 .../src/app/notebook/paragraph/paragraph.css    |   4 +
 .../paragraph/result/result-chart-selector.html |  11 +-
 .../paragraph/result/result.controller.js       |  44 +++-
 .../app/notebook/paragraph/result/result.css    |  56 ++++
 .../app/notebook/paragraph/result/result.html   |  28 +-
 zeppelin-web/src/app/spell/spell-result.js      |   2 +
 zeppelin-web/src/app/tabledata/dataset.js       |  36 +++
 .../src/app/tabledata/datasetfactory.js         |  33 +++
 .../src/app/tabledata/datasetfactory.test.js    |  46 ++++
 zeppelin-web/src/app/tabledata/network.js       |  48 ++++
 .../src/app/tabledata/network_settings.html     |  74 ++++++
 zeppelin-web/src/app/tabledata/networkdata.js   | 145 ++++++++++
 .../src/app/tabledata/networkdata.test.js       |  46 ++++
 zeppelin-web/src/app/tabledata/tabledata.js     |   6 +-
 .../builtins/visualization-d3network.js         | 263 +++++++++++++++++++
 .../components/resizable/resizable.directive.js |   2 +-
 24 files changed, 930 insertions(+), 22 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/docs/_includes/themes/zeppelin/_navigation.html
----------------------------------------------------------------------
diff --git a/docs/_includes/themes/zeppelin/_navigation.html 
b/docs/_includes/themes/zeppelin/_navigation.html
index 296be25..dd710d0 100644
--- a/docs/_includes/themes/zeppelin/_navigation.html
+++ b/docs/_includes/themes/zeppelin/_navigation.html
@@ -88,6 +88,7 @@
                 <li><a 
href="{{BASE_PATH}}/displaysystem/basicdisplaysystem.html#text">Text</a></li>
                 <li><a 
href="{{BASE_PATH}}/displaysystem/basicdisplaysystem.html#html">Html</a></li>
                 <li><a 
href="{{BASE_PATH}}/displaysystem/basicdisplaysystem.html#table">Table</a></li>
+                <li><a 
href="{{BASE_PATH}}/displaysystem/basicdisplaysystem.html#network">Network</a></li>
                 <li role="separator" class="divider"></li>
                 <li class="title"><span><b>Angular API</b><span></li>
                 <li><a 
href="{{BASE_PATH}}/displaysystem/back-end-angular.html">Angular (backend 
API)</a></li>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/docs/assets/themes/zeppelin/img/screenshots/display_complex_network.png
----------------------------------------------------------------------
diff --git 
a/docs/assets/themes/zeppelin/img/screenshots/display_complex_network.png 
b/docs/assets/themes/zeppelin/img/screenshots/display_complex_network.png
new file mode 100644
index 0000000..c788c26
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/screenshots/display_complex_network.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/docs/assets/themes/zeppelin/img/screenshots/display_network.png
----------------------------------------------------------------------
diff --git a/docs/assets/themes/zeppelin/img/screenshots/display_network.png 
b/docs/assets/themes/zeppelin/img/screenshots/display_network.png
new file mode 100644
index 0000000..516c853
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/screenshots/display_network.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/docs/assets/themes/zeppelin/img/screenshots/display_network_flatten.png
----------------------------------------------------------------------
diff --git 
a/docs/assets/themes/zeppelin/img/screenshots/display_network_flatten.png 
b/docs/assets/themes/zeppelin/img/screenshots/display_network_flatten.png
new file mode 100644
index 0000000..743e666
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/screenshots/display_network_flatten.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/docs/assets/themes/zeppelin/img/screenshots/display_simple_network.png
----------------------------------------------------------------------
diff --git 
a/docs/assets/themes/zeppelin/img/screenshots/display_simple_network.png 
b/docs/assets/themes/zeppelin/img/screenshots/display_simple_network.png
new file mode 100644
index 0000000..86a08fe
Binary files /dev/null and 
b/docs/assets/themes/zeppelin/img/screenshots/display_simple_network.png differ

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/docs/displaysystem/basicdisplaysystem.md
----------------------------------------------------------------------
diff --git a/docs/displaysystem/basicdisplaysystem.md 
b/docs/displaysystem/basicdisplaysystem.md
index 7c42432..15cefef 100644
--- a/docs/displaysystem/basicdisplaysystem.md
+++ b/docs/displaysystem/basicdisplaysystem.md
@@ -61,3 +61,104 @@ If table contents start with `%html`, it is interpreted as 
an HTML.
 <img src="../assets/themes/zeppelin/img/screenshots/display_table_html.png" />
 
 > **Note :** Display system is backend independent.
+
+## Network
+
+With the `%network` directive, Zeppelin treats your output as a graph. 
Zeppelin can leverage the Property Graph Model.
+
+### What is the Labelled Property Graph Model?
+
+A [Property 
Graph](https://github.com/tinkerpop/gremlin/wiki/Defining-a-Property-Graph) is 
a graph that has these elements:
+
+* a set of vertices
+    * each vertex has a unique identifier.
+    * each vertex has a set of outgoing edges.
+    * each vertex has a set of incoming edges.
+    * each vertex has a collection of properties defined by a map from key to 
value
+* a set of edges
+    * each edge has a unique identifier.
+    * each edge has an outgoing tail vertex.
+    * each edge has an incoming head vertex.
+    * each edge has a label that denotes the type of relationship between its 
two vertices.
+    * each edge has a collection of properties defined by a map from key to 
value.
+
+<img 
src="https://github.com/tinkerpop/gremlin/raw/master/doc/images/graph-example-1.jpg";
 />
+
+A [Labelled Property 
Graph](https://neo4j.com/developer/graph-database/#property-graph) is a 
Property Graph where the nodes can be tagged with **labels** representing their 
different roles in the graph model
+
+<img 
src="http://s3.amazonaws.com/dev.assets.neo4j.com/wp-content/uploads/property_graph_model.png";
 />
+
+### What are the APIs?
+
+The new NETWORK visualization is based on json with the following params:
+
+* "nodes" (mandatory): list of nodes of the graph every node can have the 
following params:
+    * "id" (mandatory): the id of the node (must be unique);
+    * "label": the main Label of the node;
+    * "labels": the list of the labels of the node;
+    * "data": the data attached to the node;
+* "edges": list of the edges of the graph;
+    * "id" (mandatory): the id of the edge (must be unique);
+    * "source" (mandatory): the id of source node of the edge;
+    * "target" (mandatory): the id of target node of the edge;
+    * "label": the main type of the edge;
+    * "data": the data attached to the edge;
+* "labels": a map (K, V) where K is the node label and V is the color of the 
node;
+* "directed": (true/false, default false) wich tells if is directed graph or 
not;
+* "types": a *distinct* list of the edge types of the graph
+
+If you click on a node or edge on the bottom of the paragraph you find a list 
of entity properties
+
+<img src="../assets/themes/zeppelin/img/screenshots/display_network.png" />
+
+This kind of graph can be easily *flatten* in order to support other 
visualization formats provided by Zeppelin.
+
+<img 
src="../assets/themes/zeppelin/img/screenshots/display_network_flatten.png" />
+
+### How to use it?
+
+An example of a simple graph
+
+```
+%spark
+print(s"""
+%network {
+    "nodes": [
+        {"id": 1},
+        {"id": 2},
+        {"id": 3}
+    ],
+    "edges": [
+               {"source": 1, "target": 2, "id" : 1},
+               {"source": 2, "target": 3, "id" : 2},
+               {"source": 1, "target": 2, "id" : 3},
+               {"source": 1, "target": 2, "id" : 4},
+               {"source": 2, "target": 1, "id" : 5},
+               {"source": 2, "target": 1, "id" : 6}
+       ]
+}
+""")
+```
+
+that will look like:
+
+<img 
src="../assets/themes/zeppelin/img/screenshots/display_simple_network.png" />
+
+A little more complex graph:
+
+```
+%spark
+print(s"""
+%network {
+    "nodes": [{"id": 1, "label": "User", "data": {"fullName":"Andrea 
Santurbano"}},{"id": 2, "label": "User", "data": {"fullName":"Lee Moon 
Soo"}},{"id": 3, "label": "Project", "data": {"name":"Zeppelin"}}],
+    "edges": [{"source": 2, "target": 1, "id" : 1, "label": 
"HELPS"},{"source": 2, "target": 3, "id" : 2, "label": "CREATE"},{"source": 1, 
"target": 3, "id" : 3, "label": "CONTRIBUTE_TO", "data": {"oldPR": 
"https://github.com/apache/zeppelin/pull/1582"}}],
+       "labels": {"User": "#8BC34A", "Project": "#3071A9"},
+       "directed": true,
+       "types": ["HELPS", "CREATE", "CONTRIBUTE_TO"]
+}
+""")
+```
+
+that will look like:
+
+<img 
src="../assets/themes/zeppelin/img/screenshots/display_complex_network.png" />
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterResult.java
----------------------------------------------------------------------
diff --git 
a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterResult.java
 
b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterResult.java
index 2316490..7aab8a3 100644
--- 
a/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterResult.java
+++ 
b/zeppelin-interpreter/src/main/java/org/apache/zeppelin/interpreter/InterpreterResult.java
@@ -50,7 +50,8 @@ public class InterpreterResult implements Serializable {
     TABLE,
     IMG,
     SVG,
-    NULL
+    NULL,
+    NETWORK
   }
 
   Code code;

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/.eslintrc
----------------------------------------------------------------------
diff --git a/zeppelin-web/.eslintrc b/zeppelin-web/.eslintrc
index 1fe3fa5..6dca5c8 100644
--- a/zeppelin-web/.eslintrc
+++ b/zeppelin-web/.eslintrc
@@ -58,6 +58,7 @@
     "no-undef": 2,
     "no-unused-vars": [2, { "vars": "local", "args": "none" }],
     "strict": [2, "global"],
-    "max-len": [2, {"code": 120, "ignoreComments": true, 
"ignoreRegExpLiterals": true}]
+    "max-len": [2, {"code": 120, "ignoreComments": true, 
"ignoreRegExpLiterals": true}],
+       "linebreak-style": 0
   }
 }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/notebook/paragraph/paragraph.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/paragraph.css 
b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
index 1a5e992..bebea60 100644
--- a/zeppelin-web/src/app/notebook/paragraph/paragraph.css
+++ b/zeppelin-web/src/app/notebook/paragraph/paragraph.css
@@ -560,3 +560,7 @@ table.table-striped {
 .markdown-body h4 {
   font-size: 16px;
 }
+
+.network-labels {
+  margin: 0.2em;
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html 
b/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
index 4becdc6..9592d80 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result-chart-selector.html
@@ -13,12 +13,13 @@ limitations under the License.
 -->
 
 <div id="{{id}}_switch"
-     ng-if="(type == 'TABLE' || apps.length > 0 || suggestion.available && 
suggestion.available.length > 0) && !asIframe && !viewOnly"
+     ng-if="(type == 'TABLE' || type == 'NETWORK' || apps.length > 0 || 
suggestion.available && suggestion.available.length > 0) && !asIframe && 
!viewOnly"
      class="result-chart-selector">
 
-  <div ng-if="type == 'TABLE'" class="btn-group">
+  <div ng-if="type == 'TABLE' || type == 'NETWORK'" class="btn-group">
     <button type="button" class="btn btn-default btn-sm"
             ng-repeat="viz in builtInTableDataVisualizationList track by 
$index"
+            ng-if="viz.supports.indexOf(type) > -1"
             ng-class="{'active' : viz.id == graphMode && 
!config.helium.activeApp}"
             ng-click="switchViz(viz.id)"
             tooltip-placement="bottom" uib-tooltip="{{viz.name ? viz.name : 
''}}"
@@ -28,7 +29,7 @@ limitations under the License.
 
   <div class="btn-group">
     <button type="button"
-            ng-if="type != 'TABLE'"
+            ng-if="type != 'TABLE' && type != 'NETWORK'"
             ng-click="switchApp()"
             ng-class="{'active' : !config.helium.activeApp}"
             class="btn btn-default btn-sm"><i class="fa fa-terminal"></i>
@@ -73,7 +74,7 @@ limitations under the License.
 </div>
 
 <div class="btn-group"
-     ng-if="type == 'TABLE' && !asIframe && !viewOnly"
+     ng-if="(type == 'TABLE' || type == 'NETWORK') && !asIframe && !viewOnly"
      style="margin-bottom: 10px;">
   <button type="button" class="btn btn-default btn-sm"
           style="margin-left:10px"
@@ -93,7 +94,7 @@ limitations under the License.
 </div>
 
 <span
-   ng-if="type=='TABLE' && !config.helium.activeApp && !asIframe && !viewOnly"
+   ng-if="(type == 'TABLE' || type == 'NETWORK') && !config.helium.activeApp 
&& graphMode!='table' && !asIframe && !viewOnly"
    style="margin-left:10px; cursor:pointer; display: inline-block; 
vertical-align:top; position: relative; line-height:30px;">
   <a class="btnText" ng-click="toggleGraphSetting()">
     settings <span ng-class="config.graph.optionOpen ? 'fa fa-caret-up' : 'fa 
fa-caret-down'"></span>

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/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 8237c78..c2ec7f2 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.controller.js
@@ -14,13 +14,14 @@
 
 import moment from 'moment'
 
-import TableData from '../../../tabledata/tabledata'
+import DatasetFactory from '../../../tabledata/datasetfactory'
 import TableVisualization from 
'../../../visualization/builtins/visualization-table'
 import BarchartVisualization from 
'../../../visualization/builtins/visualization-barchart'
 import PiechartVisualization from 
'../../../visualization/builtins/visualization-piechart'
 import AreachartVisualization from 
'../../../visualization/builtins/visualization-areachart'
 import LinechartVisualization from 
'../../../visualization/builtins/visualization-linechart'
 import ScatterchartVisualization from 
'../../../visualization/builtins/visualization-scatterchart'
+import NetworkVisualization from 
'../../../visualization/builtins/visualization-d3network'
 import {
   DefaultDisplayType,
   SpellResult,
@@ -44,36 +45,48 @@ function ResultCtrl ($scope, $rootScope, $route, $window, 
$routeParams, $locatio
     {
       id: 'table',   // paragraph.config.graph.mode
       name: 'Table', // human readable name. tooltip
-      icon: '<i class="fa fa-table"></i>'
+      icon: '<i class="fa fa-table"></i>',
+      supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
     },
     {
       id: 'multiBarChart',
       name: 'Bar Chart',
       icon: '<i class="fa fa-bar-chart"></i>',
-      transformation: 'pivot'
+      transformation: 'pivot',
+      supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
     },
     {
       id: 'pieChart',
       name: 'Pie Chart',
       icon: '<i class="fa fa-pie-chart"></i>',
-      transformation: 'pivot'
+      transformation: 'pivot',
+      supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
     },
     {
       id: 'stackedAreaChart',
       name: 'Area Chart',
       icon: '<i class="fa fa-area-chart"></i>',
-      transformation: 'pivot'
+      transformation: 'pivot',
+      supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
     },
     {
       id: 'lineChart',
       name: 'Line Chart',
       icon: '<i class="fa fa-line-chart"></i>',
-      transformation: 'pivot'
+      transformation: 'pivot',
+      supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
     },
     {
       id: 'scatterChart',
       name: 'Scatter Chart',
-      icon: '<i class="cf cf-scatter-chart"></i>'
+      icon: '<i class="cf cf-scatter-chart"></i>',
+      supports: [DefaultDisplayType.TABLE, DefaultDisplayType.NETWORK]
+    },
+    {
+      id: 'network',
+      name: 'Network',
+      icon: '<i class="fa fa-share-alt"></i>',
+      supports: [DefaultDisplayType.NETWORK]
     }
   ]
 
@@ -104,6 +117,10 @@ function ResultCtrl ($scope, $rootScope, $route, $window, 
$routeParams, $locatio
     'scatterChart': {
       class: ScatterchartVisualization,
       instance: undefined
+    },
+    'network': {
+      class: NetworkVisualization,
+      instance: undefined
     }
   }
 
@@ -253,18 +270,23 @@ function ResultCtrl ($scope, $rootScope, $route, $window, 
$routeParams, $locatio
     // enable only when it is last result
     enableHelium = (index === paragraphRef.results.msg.length - 1)
 
-    if ($scope.type === 'TABLE') {
-      tableData = new TableData()
+    if ($scope.type === 'TABLE' || $scope.type === 'NETWORK') {
+      tableData = new DatasetFactory().createDataset($scope.type)
       tableData.loadParagraphResult({type: $scope.type, msg: data})
       $scope.tableDataColumns = tableData.columns
       $scope.tableDataComment = tableData.comment
+      if ($scope.type === 'NETWORK') {
+        $scope.networkNodes = tableData.networkNodes
+        $scope.networkRelationships = tableData.networkRelationships
+        $scope.networkProperties = tableData.networkProperties
+      }
     } else if ($scope.type === 'IMG') {
       $scope.imageData = data
     }
   }
 
   $scope.createDisplayDOMId = function (baseDOMId, type) {
-    if (type === DefaultDisplayType.TABLE) {
+    if (type === DefaultDisplayType.TABLE || type === 
DefaultDisplayType.NETWORK) {
       return `${baseDOMId}_graph`
     } else if (type === DefaultDisplayType.HTML) {
       return `${baseDOMId}_html`
@@ -281,7 +303,7 @@ function ResultCtrl ($scope, $rootScope, $route, $window, 
$routeParams, $locatio
 
   $scope.renderDefaultDisplay = function (targetElemId, type, data, refresh) {
     const afterLoaded = () => {
-      if (type === DefaultDisplayType.TABLE) {
+      if (type === DefaultDisplayType.TABLE || type === 
DefaultDisplayType.NETWORK) {
         renderGraph(targetElemId, $scope.graphMode, refresh)
       } else if (type === DefaultDisplayType.HTML) {
         renderHtml(targetElemId, data)

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/notebook/paragraph/result/result.css
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/notebook/paragraph/result/result.css 
b/zeppelin-web/src/app/notebook/paragraph/result/result.css
index 3e864ef..9f9b967 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.css
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.css
@@ -57,3 +57,59 @@
   font-weight: 400;
   text-align: center;
 }
+
+/* D3 Graph Configuration */
+marker {
+  fill: #D3D3D3;
+}
+path.link {
+  fill: none;
+  stroke-width: 3px;
+  stroke: #D3D3D3;
+}
+path.textpath {
+ fill: none;
+ stroke: none;
+}
+
+text {
+  font-size: 12px;
+  pointer-events: none;
+}
+text.shadow {
+  stroke: #fff;
+  stroke-width: 3px;
+  stroke-opacity: .8;
+}
+text.nodeLabel {
+  font-size: 1em;
+  pointer-events: none;
+}
+
+/* D3 Graph Configuration */
+marker {
+  fill: #D3D3D3;
+}
+path.link {
+  fill: none;
+  stroke-width: 3px;
+  stroke: #D3D3D3;
+}
+path.textpath {
+ fill: none;
+ stroke: none;
+}
+
+text {
+  font-size: 12px;
+  pointer-events: none;
+}
+text.shadow {
+  stroke: #fff;
+  stroke-width: 3px;
+  stroke-opacity: .8;
+}
+text.nodeLabel {
+  font-size: 1em;
+  pointer-events: none;
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/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 faac9cd..5099cfa 100644
--- a/zeppelin-web/src/app/notebook/paragraph/result/result.html
+++ b/zeppelin-web/src/app/notebook/paragraph/result/result.html
@@ -23,7 +23,7 @@ limitations under the License.
     resize='{"allowresize": "{{!asIframe && !viewOnly}}", "graphType": 
"{{type}}"}'
     resizable on-resize="resize(width, height);">
 
-    <div ng-if="type=='TABLE'"
+    <div ng-if="type=='TABLE' || type == 'NETWORK'"
          ng-style="getPointerEvent()">
       <!-- setting -->
       <div class="option lightBold" style="overflow: visible;"
@@ -36,6 +36,26 @@ limitations under the License.
              ng-show="graphMode == viz.id"></div>
       </div>
 
+      <div id="p{{id}}_network_header"
+           ng-if="type == 'NETWORK' && graphMode == 'network' && networkNodes 
!= null">
+         <ul class="list-inline">
+           <li>Nodes <span class="badge">{{networkNodes.count}}</span>:</li>
+           <li ng-repeat="(labelName, labelColor) in networkNodes.labels" 
style="padding: 0">
+             <span style="background-color: {{labelColor}} !important;" 
class="label label-default network-badge">
+               {{labelName}}
+             </span>
+           </li>
+         </ul>
+         <ul class="list-inline">
+           <li ng-if="networkRelationships != null">Relationships <span 
class="badge">{{networkRelationships.count}}</span>:</li>
+           <li ng-repeat="type in networkRelationships.types" style="padding: 
0">
+             <span class="label label-default network-badge">
+               {{type}}
+             </span>
+           </li>
+         </ul>
+      </div>
+
       <!-- graph -->
       <div id="p{{id}}_graph"
            class="graphContainer"
@@ -46,6 +66,12 @@ limitations under the License.
         </div>
       </div>
 
+      <div id="p{{id}}_network_footer"
+           ng-if="type == 'NETWORK' && graphMode == 'network'">
+        <ul class="list-inline">
+        </ul>
+      </div>
+
       <div id="{{id}}_comment"
            class="text"
            ng-if="tableDataComment"

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/spell/spell-result.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/spell/spell-result.js 
b/zeppelin-web/src/app/spell/spell-result.js
index e8e2a0a..5ba65c2 100644
--- a/zeppelin-web/src/app/spell/spell-result.js
+++ b/zeppelin-web/src/app/spell/spell-result.js
@@ -21,6 +21,7 @@ export const DefaultDisplayType = {
   HTML: 'HTML',
   ANGULAR: 'ANGULAR',
   TEXT: 'TEXT',
+  NETWORK: 'NETWORK'
 }
 
 export const DefaultDisplayMagic = {
@@ -29,6 +30,7 @@ export const DefaultDisplayMagic = {
   '%html': DefaultDisplayType.HTML,
   '%angular': DefaultDisplayType.ANGULAR,
   '%text': DefaultDisplayType.TEXT,
+  '%network': DefaultDisplayType.NETWORK,
 }
 
 export class DataWithType {

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/tabledata/dataset.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/dataset.js 
b/zeppelin-web/src/app/tabledata/dataset.js
new file mode 100644
index 0000000..762e300
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/dataset.js
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+
+/**
+ * The abstract dataset rapresentation
+ */
+class Dataset {
+  /**
+   * Load the paragraph result, every Dataset implementation must override 
this method
+   * where is contained the business rules to convert the paragraphResult 
object to the related dataset type
+   */
+  loadParagraphResult(paragraphResult) {
+    // override this
+  }
+}
+
+/**
+ * Dataset types
+ */
+const DatasetType = Object.freeze({
+  NETWORK: 'NETWORK',
+  TABLE: 'TABLE'
+})
+
+export {Dataset, DatasetType}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/tabledata/datasetfactory.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/datasetfactory.js 
b/zeppelin-web/src/app/tabledata/datasetfactory.js
new file mode 100644
index 0000000..f2f69c9
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/datasetfactory.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.
+ */
+
+import TableData from './tabledata'
+import NetworkData from './networkdata'
+import {DatasetType} from './dataset'
+
+/**
+ * Create table data object from paragraph table type result
+ */
+export default class DatasetFactory {
+  createDataset(datasetType) {
+    switch (datasetType) {
+      case DatasetType.NETWORK:
+        return new NetworkData()
+      case DatasetType.TABLE:
+        return new TableData()
+      default:
+        throw new Error('Dataset type not found')
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/tabledata/datasetfactory.test.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/datasetfactory.test.js 
b/zeppelin-web/src/app/tabledata/datasetfactory.test.js
new file mode 100644
index 0000000..0beb137
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/datasetfactory.test.js
@@ -0,0 +1,46 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import NetworkData from './networkdata.js'
+import TableData from './tabledata.js'
+import {DatasetType} from './dataset.js'
+import DatasetFactory from './datasetfactory.js'
+
+describe('DatasetFactory build', function() {
+  let df
+
+  beforeAll(function() {
+    df = new DatasetFactory()
+  })
+
+  it('should create a TableData instance', function() {
+    let td = df.createDataset(DatasetType.TABLE)
+    expect(td instanceof TableData).toBeTruthy()
+    expect(td.columns.length).toBe(0)
+    expect(td.rows.length).toBe(0)
+  })
+
+  it('should create a NetworkData instance', function() {
+    let nd = df.createDataset(DatasetType.NETWORK)
+    expect(nd instanceof NetworkData).toBeTruthy()
+    expect(nd.columns.length).toBe(0)
+    expect(nd.rows.length).toBe(0)
+    expect(nd.graph).toEqual({})
+  })
+
+  it('should thrown an Error', function() {
+    expect(function() { df.createDataset('text') })
+        .toThrow(new Error('Dataset type not found'))
+  })
+})

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/tabledata/network.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/network.js 
b/zeppelin-web/src/app/tabledata/network.js
new file mode 100644
index 0000000..403ea5b
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/network.js
@@ -0,0 +1,48 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Transformation from './transformation'
+
+/**
+ * trasformation settings for network visualization
+ */
+export default class NetworkTransformation extends Transformation {
+  getSetting() {
+    let self = this
+    let configObj = self.config
+    return {
+      template: 'app/tabledata/network_settings.html',
+      scope: {
+        config: configObj,
+        isEmptyObject: function(obj) {
+          obj = obj || {}
+          return angular.equals(obj, {})
+        },
+        setNetworkLabel: function(label, value) {
+          configObj.properties[label].selected = value
+        },
+        saveConfig: function() {
+          self.emitConfig(configObj)
+        }
+      }
+    }
+  }
+
+  setConfig(config) {
+  }
+
+  transform(networkData) {
+    return networkData
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/tabledata/network_settings.html
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/network_settings.html 
b/zeppelin-web/src/app/tabledata/network_settings.html
new file mode 100644
index 0000000..ad1daa9
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/network_settings.html
@@ -0,0 +1,74 @@
+<!--
+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="row">
+  <form>
+    <fieldset class=" col-xs-12">
+      <h4>Force Layout settings</h4>
+      <div class="form-check col-xs-4">
+        <label for="{{$id}}_timeout">Stop Force layout after</label>
+        <div class="input-group">
+          <input type="text" class="form-control" 
ng-model="config.d3Graph.forceLayout.timeout" id="{{$id}}_timeout" />
+          <span class="input-group-addon">ms</span>
+        </div>
+      </div>
+      <div class="form-check col-xs-4">
+        <div class="form-group">
+          <label for="{{$id}}_charge">Force Layout Charge</label>
+          <input type="text" class="form-control" 
ng-model="config.d3Graph.forceLayout.charge" id="{{$id}}_charge" />
+        </div>
+      </div>
+      <div class="form-check col-xs-4">
+        <div class="form-group">
+          <label for="{{$id}}_linkDistance">Force Layout Link Distance</label>
+          <input type="text" class="form-control" 
ng-model="config.d3Graph.forceLayout.linkDistance" id="{{$id}}_linkDistance" />
+        </div>
+      </div>
+    </fieldset>
+
+    <fieldset class=" col-xs-12">
+      <h4>Globals</h4>
+      <div class="form-check col-xs-4">
+        <div class="form-group">
+          <label for="{{$id}}_charge">Minimum scale to show node and edge 
labels</label>
+          <input type="text" class="form-control" 
ng-model="config.d3Graph.zoom.minScale" id="{{$id}}_minScale" />
+        </div>
+      </div>
+    </fieldset>
+
+    <fieldset class="form-group col-xs-12">
+      <h4>Choose node labels</h4>
+      <div ng-if="isEmptyObject(config.properties)">
+      No labels to set
+      </div>
+      <div class="btn-group network-labels network-badge-settings"
+          ng-repeat="(key, value) in config.properties track by key">
+        <button type="button" class="btn btn-default dropdown-toggle" 
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
+          {{key}}:<i>{{config.properties[key].selected}}</i> <div 
class="caret"></div>
+        </button>
+        <ul class="dropdown-menu">
+          <li ng-repeat="val in value.keys" ng-click="setNetworkLabel(key, 
val)">
+            <a><i ng-if="config.properties[key].selected == val" 
class="glyphicon glyphicon-ok">
+            </i> {{val}}</a>
+          </li>
+        </ul>
+      </div>
+    </fieldset>
+    <fieldset class="form-group col-xs-12">
+      <button type="submit" class="btn btn-primary btn-sm" 
ng-click="saveConfig()">
+        <span class="glyphicon glyphicon-floppy-disk"></span>
+        Save configuration
+      </button>
+    </fieldset>
+  </form>
+</div>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/tabledata/networkdata.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/networkdata.js 
b/zeppelin-web/src/app/tabledata/networkdata.js
new file mode 100644
index 0000000..7983d82
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/networkdata.js
@@ -0,0 +1,145 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import TableData from './tabledata'
+import {DatasetType} from './dataset'
+
+/**
+ * Create network data object from paragraph graph type result
+ */
+export default class NetworkData extends TableData {
+  constructor(graph) {
+    super()
+    this.graph = graph || {}
+    if (this.graph.nodes) {
+      this.loadParagraphResult({msg: JSON.stringify(graph), type: 
DatasetType.NETWORK})
+    }
+  }
+
+  loadParagraphResult(paragraphResult) {
+    if (!paragraphResult || paragraphResult.type !== DatasetType.NETWORK) {
+      console.log('Can not load paragraph result')
+      return
+    }
+
+    this.graph = JSON.parse(paragraphResult.msg.trim() || '{}')
+
+    if (!this.graph.nodes) {
+      console.log('Graph result is empty')
+      return
+    }
+
+    this.setNodesDefaults()
+    this.setEdgesDefaults()
+
+    this.networkNodes = angular.equals({}, this.graph.labels || {})
+            ? null : {count: this.graph.nodes.length, labels: 
this.graph.labels}
+    this.networkRelationships = angular.equals([], this.graph.types || [])
+            ? null : {count: this.graph.edges.length, types: this.graph.types}
+
+    let rows = []
+    let comment = ''
+    let entities = this.graph.nodes.concat(this.graph.edges)
+    let baseColumnNames = [{name: 'id', index: 0, aggr: 'sum'},
+                       {name: 'label', index: 1, aggr: 'sum'}]
+    let internalFieldsToJump = ['count', 'size', 'totalCount',
+      'data', 'x', 'y', 'labels']
+    let baseCols = _.map(baseColumnNames, function(col) { return col.name })
+    let keys = _.map(entities, function(elem) { return Object.keys(elem.data 
|| {}) })
+    keys = _.flatten(keys)
+    keys = _.uniq(keys).filter(function(key) {
+      return baseCols.indexOf(key) === -1
+    })
+    let columnNames = baseColumnNames.concat(_.map(keys, function(elem, i) {
+      return {name: elem, index: i + baseColumnNames.length, aggr: 'sum'}
+    }))
+    for (let i = 0; i < entities.length; i++) {
+      let entity = entities[i]
+      let col = []
+      let col2 = []
+      entity.data = entity.data || {}
+      for (let j = 0; j < columnNames.length; j++) {
+        let name = columnNames[j].name
+        let value = name in entity && internalFieldsToJump.indexOf(name) === -1
+            ? entity[name] : entity.data[name]
+        let parsedValue = value === null || value === undefined ? '' : value
+        col.push(parsedValue)
+        col2.push({key: name, value: parsedValue})
+      }
+      rows.push(col)
+    }
+
+    this.comment = comment
+    this.columns = columnNames
+    this.rows = rows
+  }
+
+  setNodesDefaults() {
+  }
+
+  setEdgesDefaults() {
+    this.graph.edges
+      .sort((a, b) => {
+        if (a.source > b.source) {
+          return 1
+        } else if (a.source < b.source) {
+          return -1
+        } else if (a.target > b.target) {
+          return 1
+        } else if (a.target < b.target) {
+          return -1
+        } else {
+          return 0
+        }
+      })
+    this.graph.edges
+      .forEach((edge, index) => {
+        let prevEdge = this.graph.edges[index - 1]
+        edge.count = (index > 0 && +edge.source === +prevEdge.source && 
+edge.target === +prevEdge.target
+            ? prevEdge.count : 0) + 1
+        edge.totalCount = this.graph.edges
+          .filter((innerEdge) => +edge.source === +innerEdge.source && 
+edge.target === +innerEdge.target)
+          .length
+      })
+    this.graph.edges
+      .forEach((edge) => {
+        if (typeof +edge.source === 'number') {
+          edge.source = this.graph.nodes.filter((node) => +edge.source === 
+node.id)[0] || null
+        }
+        if (typeof +edge.target === 'number') {
+          edge.target = this.graph.nodes.filter((node) => +edge.target === 
+node.id)[0] || null
+        }
+      })
+  }
+
+  getNetworkProperties() {
+    let baseCols = ['id', 'label']
+    let properties = {}
+    this.graph.nodes.forEach(function(node) {
+      let hasLabel = 'label' in node && node.label !== ''
+      if (!hasLabel) {
+        return
+      }
+      let label = node.label
+      let hasKey = hasLabel && label in properties
+      let keys = _.uniq(Object.keys(node.data || {})
+              .concat(hasKey ? properties[label].keys : baseCols))
+      if (!hasKey) {
+        properties[label] = {selected: 'label'}
+      }
+      properties[label].keys = keys
+    })
+    return properties
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/tabledata/networkdata.test.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/networkdata.test.js 
b/zeppelin-web/src/app/tabledata/networkdata.test.js
new file mode 100644
index 0000000..f8d98a8
--- /dev/null
+++ b/zeppelin-web/src/app/tabledata/networkdata.test.js
@@ -0,0 +1,46 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import NetworkData from './networkdata.js'
+import {DatasetType} from './dataset.js'
+
+describe('NetworkData build', function() {
+  let nd
+
+  beforeEach(function() {
+    nd = new NetworkData()
+  })
+
+  it('should initialize the default value', function() {
+    expect(nd.columns.length).toBe(0)
+    expect(nd.rows.length).toBe(0)
+    expect(nd.graph).toEqual({})
+  })
+
+  it('should able to create NetowkData from paragraph result', function() {
+    let jsonExpected = {nodes: [{id: 1}, {id: 2}], edges: [{source: 2, target: 
1, id: 1}]}
+    nd.loadParagraphResult({
+      type: DatasetType.NETWORK,
+      msg: JSON.stringify(jsonExpected)
+    })
+
+    expect(nd.columns.length).toBe(2)
+    expect(nd.rows.length).toBe(3)
+    expect(nd.graph.nodes[0].id).toBe(jsonExpected.nodes[0].id)
+    expect(nd.graph.nodes[1].id).toBe(jsonExpected.nodes[1].id)
+    expect(nd.graph.edges[0].id).toBe(jsonExpected.edges[0].id)
+    expect(nd.graph.edges[0].source.id).toBe(jsonExpected.nodes[1].id)
+    expect(nd.graph.edges[0].target.id).toBe(jsonExpected.nodes[0].id)
+  })
+})

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/tabledata/tabledata.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/app/tabledata/tabledata.js 
b/zeppelin-web/src/app/tabledata/tabledata.js
index 8e4e6b6..3fe01b7 100644
--- a/zeppelin-web/src/app/tabledata/tabledata.js
+++ b/zeppelin-web/src/app/tabledata/tabledata.js
@@ -11,19 +11,21 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+import {Dataset, DatasetType} from './dataset'
 
 /**
  * Create table data object from paragraph table type result
  */
-export default class TableData {
+export default class TableData extends Dataset {
   constructor (columns, rows, comment) {
+    super()
     this.columns = columns || []
     this.rows = rows || []
     this.comment = comment || ''
   }
 
   loadParagraphResult (paragraphResult) {
-    if (!paragraphResult || paragraphResult.type !== 'TABLE') {
+    if (!paragraphResult || paragraphResult.type !== DatasetType.TABLE) {
       console.log('Can not load paragraph result')
       return
     }

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js
----------------------------------------------------------------------
diff --git 
a/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js 
b/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js
new file mode 100644
index 0000000..506b1c5
--- /dev/null
+++ b/zeppelin-web/src/app/visualization/builtins/visualization-d3network.js
@@ -0,0 +1,263 @@
+/*
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Visualization from '../visualization'
+import NetworkTransformation from '../../tabledata/network'
+
+/**
+ * Visualize data in network format
+ */
+export default class NetworkVisualization extends Visualization {
+  constructor(targetEl, config) {
+    super(targetEl, config)
+    console.log('Init network viz')
+    if (!config.properties) {
+      config.properties = {}
+    }
+    if (!config.d3Graph) {
+      config.d3Graph = {
+        forceLayout: {
+          timeout: 10000,
+          charge: -120,
+          linkDistance: 80,
+        },
+        zoom: {
+          minScale: 1.3
+        }
+      }
+    }
+    this.targetEl.addClass('network')
+    this.containerId = this.targetEl.prop('id')
+    this.force = null
+    this.svg = null
+    this.$timeout = angular.injector(['ng']).get('$timeout')
+    this.$interpolate = angular.injector(['ng']).get('$interpolate')
+    this.transformation = new NetworkTransformation(config)
+  }
+
+  refresh() {
+    console.log('refresh')
+  }
+
+  render(networkData) {
+    if (!('graph' in networkData)) {
+      console.log('graph not found')
+      return
+    }
+    console.log('Render Graph Visualization')
+
+    let transformationConfig = this.transformation.getSetting().scope.config
+    console.log('cfg', transformationConfig)
+    if (transformationConfig && angular.equals({}, 
transformationConfig.properties)) {
+      transformationConfig.properties = networkData.getNetworkProperties()
+    }
+
+    this.targetEl.empty().append('<svg></svg>')
+
+    let width = this.targetEl.width()
+    let height = this.targetEl.height()
+    let self = this
+    let defaultOpacity = 0
+    let nodeSize = 10
+    let textOffset = 3
+    let linkSize = 10
+
+    let arcPath = (leftHand, d) => {
+      let start = leftHand ? d.source : d.target
+      let end = leftHand ? d.target : d.source
+      let dx = end.x - start.x
+      let dy = end.y - start.y
+      let dr = d.totalCount === 1
+              ? 0 : Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2)) / (1 + (1 / 
d.totalCount) * (d.count - 1))
+      let sweep = leftHand ? 0 : 1
+      return `M${start.x},${start.y}A${dr},${dr} 0 0,${sweep} 
${end.x},${end.y}`
+    }
+    // Use elliptical arc path segments to doubly-encode directionality.
+    let tick = () => {
+      // Links
+      linkPath.attr('d', function(d) {
+        return arcPath(true, d)
+      })
+      textPath.attr('d', function(d) {
+        return arcPath(d.source.x < d.target.x, d)
+      })
+      // Nodes
+      circle.attr('transform', (d) => `translate(${d.x},${d.y})`)
+      text.attr('transform', (d) => `translate(${d.x},${d.y})`)
+    }
+
+    let setOpacity = (scale) => {
+      let opacity = scale >= +transformationConfig.d3Graph.zoom.minScale ? 1 : 0
+      this.svg.selectAll('.nodeLabel')
+        .style('opacity', opacity)
+      this.svg.selectAll('textPath')
+        .style('opacity', opacity)
+    }
+
+    let zoom = d3.behavior.zoom()
+      .scaleExtent([1, 10])
+      .on('zoom', () => {
+        console.log('zoom')
+        setOpacity(d3.event.scale)
+        container.attr('transform', 
`translate(${d3.event.translate})scale(${d3.event.scale})`)
+      })
+
+    this.svg = d3.select(`#${this.containerId} svg`)
+      .attr('width', width)
+      .attr('height', height)
+      .call(zoom)
+
+    this.force = d3.layout.force()
+      .charge(transformationConfig.d3Graph.forceLayout.charge)
+      .linkDistance(transformationConfig.d3Graph.forceLayout.linkDistance)
+      .on('tick', tick)
+      .nodes(networkData.graph.nodes)
+      .links(networkData.graph.edges)
+      .size([width, height])
+      .on('start', () => {
+        console.log('force layout start')
+        this.$timeout(() => { this.force.stop() }, 
transformationConfig.d3Graph.forceLayout.timeout)
+      })
+      .on('end', () => {
+        console.log('force layout stop')
+        setOpacity(zoom.scale())
+      })
+      .start()
+
+    let renderFooterOnClick = (entity, type) => {
+      let footerId = this.containerId + '_footer'
+      let obj = {id: entity.id, label: entity.defaultLabel || entity.label, 
type: type}
+      let html = [this.$interpolate(['<li><b>{{type}}_id:</b>&nbsp{{id}}</li>',
+        '<li><b>{{type}}_type:</b>&nbsp{{label}}</li>'].join(''))(obj)]
+      html = html.concat(_.map(entity.data, (v, k) => {
+        return 
this.$interpolate('<li><b>{{field}}:</b>&nbsp{{value}}</li>')({field: k, value: 
v})
+      }))
+      angular.element('#' + footerId)
+        .find('.list-inline')
+        .empty()
+        .append(html.join(''))
+    }
+
+    let drag = d3.behavior.drag()
+      .origin((d) => d)
+      .on('dragstart', function(d) {
+        console.log('dragstart')
+        d3.event.sourceEvent.stopPropagation()
+        d3.select(this).classed('dragging', true)
+        self.force.stop()
+      })
+      .on('drag', function(d) {
+        console.log('drag')
+        d.px += d3.event.dx
+        d.py += d3.event.dy
+        d.x += d3.event.dx
+        d.y += d3.event.dy
+      })
+      .on('dragend', function(d) {
+        console.log('dragend')
+        d.fixed = true
+        d3.select(this).classed('dragging', false)
+        self.force.resume()
+      })
+
+    let container = this.svg.append('g')
+    if (networkData.graph.directed) {
+      container.append('svg:defs').selectAll('marker')
+        .data(['arrowMarker-' + this.containerId])
+        .enter()
+        .append('svg:marker')
+        .attr('id', String)
+        .attr('viewBox', '0 -5 10 10')
+        .attr('refX', 16)
+        .attr('refY', 0)
+        .attr('markerWidth', 4)
+        .attr('markerHeight', 4)
+        .attr('orient', 'auto')
+        .append('svg:path')
+        .attr('d', 'M0,-5L10,0L0,5')
+    }
+    // Links
+    let link = container.append('svg:g')
+      .on('click', () => {
+        renderFooterOnClick(d3.select(d3.event.target).datum(), 'edge')
+      })
+      .selectAll('g.link')
+      .data(self.force.links())
+      .enter()
+      .append('g')
+    let getPathId = (d) => this.containerId + '_' + d.source.index + '_' + 
d.target.index + '_' + d.count
+    let showLabel = (d) => this._showNodeLabel(d)
+    let linkPath = link.append('svg:path')
+      .attr('class', 'link')
+      .attr('size', linkSize)
+      .attr('marker-end', `url(#arrowMarker-${this.containerId})`)
+    let textPath = link.append('svg:path')
+      .attr('id', getPathId)
+      .attr('class', 'textpath')
+    container.append('svg:g')
+      .selectAll('.pathLabel')
+      .data(self.force.links())
+      .enter()
+      .append('svg:text')
+      .attr('class', 'pathLabel')
+      .append('svg:textPath')
+      .attr('startOffset', '50%')
+      .attr('text-anchor', 'middle')
+      .attr('xlink:href', (d) => '#' + getPathId(d))
+      .text((d) => d.label)
+      .style('opacity', defaultOpacity)
+    // Nodes
+    let circle = container.append('svg:g')
+      .on('click', () => {
+        renderFooterOnClick(d3.select(d3.event.target).datum(), 'node')
+      })
+      .selectAll('circle')
+      .data(self.force.nodes())
+      .enter().append('svg:circle')
+      .attr('r', (d) => nodeSize)
+      .attr('fill', (d) => networkData.graph.labels && d.label in 
networkData.graph.labels
+                  ? networkData.graph.labels[d.label] : '#000000')
+      .call(drag)
+    let text = container.append('svg:g').selectAll('g')
+      .data(self.force.nodes())
+      .enter().append('svg:g')
+    text.append('svg:text')
+        .attr('x', (d) => nodeSize + textOffset)
+        .attr('size', nodeSize)
+        .attr('y', '.31em')
+        .attr('class', (d) => 'nodeLabel shadow label-' + d.label)
+        .text(showLabel)
+        .style('opacity', defaultOpacity)
+    text.append('svg:text')
+        .attr('x', (d) => nodeSize + textOffset)
+        .attr('size', nodeSize)
+        .attr('y', '.31em')
+        .attr('class', (d) => 'nodeLabel label-' + d.label)
+        .text(showLabel)
+        .style('opacity', defaultOpacity)
+  }
+
+  destroy() {
+  }
+
+  _showNodeLabel(d) {
+    let transformationConfig = this.transformation.getSetting().scope.config
+    let selectedLabel = (transformationConfig.properties[d.label] || 
{selected: 'label'}).selected
+    return d.data[selectedLabel] || d[selectedLabel]
+  }
+
+  getTransformation() {
+    return this.transformation
+  }
+}

http://git-wip-us.apache.org/repos/asf/zeppelin/blob/43926485/zeppelin-web/src/components/resizable/resizable.directive.js
----------------------------------------------------------------------
diff --git a/zeppelin-web/src/components/resizable/resizable.directive.js 
b/zeppelin-web/src/components/resizable/resizable.directive.js
index f0eed76..7bf8477 100644
--- a/zeppelin-web/src/components/resizable/resizable.directive.js
+++ b/zeppelin-web/src/components/resizable/resizable.directive.js
@@ -35,7 +35,7 @@ function resizable () {
           let colStep = window.innerWidth / 12
           elem.off('resizestop')
           let conf = angular.copy(resizableConfig)
-          if (resize.graphType === 'TABLE' || resize.graphType === 'TEXT') {
+          if (resize.graphType === 'TABLE' || resize.graphType === 'NETWORK' 
|| resize.graphType === 'TEXT') {
             conf.grid = [colStep, 10]
             conf.minHeight = 100
           } else {

Reply via email to