Browse Source

HPCC-12805 JavaScript Graph Control

Include HPCC Visualization framework.
Add JavaScript Graph Control.
Fallback to JavaScript Graph Control when no ActiveX/NPAPI plugin exists.

Fixes HPCC-12805

Signed-off-by: Gordon Smith <gordon.smith@lexisnexis.com>
Gordon Smith 10 years ago
parent
commit
cb0d2f7a57

+ 3 - 0
.gitmodules

@@ -31,3 +31,6 @@
 [submodule "plugins/cassandra/cpp-driver"]
 	path = plugins/cassandra/cpp-driver
 	url = https://github.com/hpcc-systems/cpp-driver.git
+[submodule "esp/src/Visualization"]
+	path = esp/src/Visualization
+	url = https://github.com/hpcc-systems/Visualization.git

+ 8 - 4
esp/build.sh

@@ -39,14 +39,18 @@ perl -pe "
   s/<\!--.*?-->//g;                          # Strip comments
   s/\s+/ /g;                                 # Collapse white-space" > "$DISTDIR/stub.htm"
 
+echo "Building: $SRCDIR/Visualization"
+cd "$SRCDIR/Visualization/"
+./build.sh
+mkdir -p "$DISTDIR/Visualization"
+cp -r "$SRCDIR/Visualization/build" "$DISTDIR/Visualization/widgets"
+
 cd "$TOOLSDIR"
 
 if which node >/dev/null; then
-    node ../../dojo/dojo.js load=build --require "$PROFILE" --releaseDir "$DISTDIR" ${*:2}
-elif which java >/dev/null; then
-    java -Xms256m -Xmx256m  -cp ../shrinksafe/js.jar:../closureCompiler/compiler.jar:../shrinksafe/shrinksafe.jar org.mozilla.javascript.tools.shell.Main  ../../dojo/dojo.js baseUrl=../../dojo load=build --profile "$PROFILE" --releaseDir "$DISTDIR" ${*:2}
+    node ../../dojo/dojo.js baseUrl=../../dojo load=build --profile "$PROFILE" --releaseDir "$DISTDIR" ${*:2}
 else
-    echo "Need node.js or Java to build!"
+    echo "node.js is required to build ECL Watch - see https://github.com/joyent/node/wiki/Installing-Node.js-via-package-manager"
     exit 1
 fi
 

+ 0 - 14
esp/profiles/eclwatch.profile.js

@@ -59,20 +59,6 @@ var profile = {
         'put-selector',
         'xstyle',
         {
-            name: "d3",
-            location: "./d3",
-            trees: [
-                [".", ".", /(\/\.)|(~$)|(^((?!d3\.js).)*$)|(test|src|lib|bin)/]
-            ]
-        },
-        {
-            name: "topojson",
-            location: "./topojson",
-            trees: [
-                [".", ".", /(\/\.)|(~$)|(^((?!topojson\.js).)*$)|(test|examples|lib|bin)/]
-            ]
-        },
-        {
             name: "hpcc",
             location: "./eclwatch"
         },

+ 1 - 0
esp/src/Visualization

@@ -0,0 +1 @@
+Subproject commit f4193ed0c3960452b808a00f6f17e61ca0a51056

+ 3 - 1
esp/src/eclwatch/ECLPlaygroundWidget.js

@@ -31,6 +31,7 @@ define([
     "hpcc/ECLSourceWidget",
     "hpcc/TargetSelectWidget",
     "hpcc/GraphWidget",
+    "hpcc/JSGraphWidget",
     "hpcc/ECLPlaygroundResultsWidget",
     "hpcc/ESPWorkunit",
     "hpcc/ESPQuery",
@@ -41,13 +42,14 @@ define([
 
 ], function (declare, lang, i18n, nlsHPCC, xhr, dom, query,
                 BorderContainer, TabContainer, ContentPane, registry,
-                _Widget, EclSourceWidget, TargetSelectWidget, GraphWidget, ResultsWidget, ESPWorkunit, ESPQuery,
+                _Widget, EclSourceWidget, TargetSelectWidget, GraphWidget, JSGraphWidget, ResultsWidget, ESPWorkunit, ESPQuery,
                 template) {
     return declare("ECLPlaygroundWidget", [_Widget], {
         templateString: template,
         baseClass: "ECLPlaygroundWidget",
         i18n: nlsHPCC,
 
+        graphType: dojoConfig.isPluginInstalled() ? "GraphWidget" : "JSGraphWidget",
         wu: null,
         editorControl: null,
         graphControl: null,

+ 534 - 0
esp/src/eclwatch/ESPGraph.js

@@ -0,0 +1,534 @@
+/*##############################################################################
+#    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+#
+#    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.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/array",
+    "dojo/i18n",
+    "dojo/i18n!./nls/hpcc",
+
+    "dojox/xml/parser"
+
+], function (declare, arrayUtil, i18n, nlsHPCC,
+    parser) {
+
+    var i18n = nlsHPCC;
+
+    var GRAPH_TYPE = {
+        UNKNOWN: 0,
+        GRAPH: 1,
+        SUBGRAPH: 2,
+        VERTEX: 3,
+        EDGE: 4,
+        LAST: 5
+    };
+    var GRAPH_TYPE_STRING = {
+        UNKNOWN: "Unknown",
+        GRAPH: "Graph",
+        SUBGRAPH: "Cluster",
+        VERTEX: "Vertex",
+        EDGE: "Edge",
+        LAST: "Last"
+    };
+
+    var LocalisedXGMMLWriter = declare([], {
+        constructor: function (graph) {
+            this.graph = graph;
+
+            this.m_xgmml = "";
+            this.m_visibleSubgraphs = {};
+            this.m_visibleVertices = {};
+            this.m_semiVisibleVertices = {};
+            this.m_visibleEdges = {};
+        },
+
+        calcVisibility: function (items, localisationDepth, localisationDistance) {
+            arrayUtil.forEach(items, function (item) {
+                switch (this.graph.getGlobalType(item)) {
+                    case GRAPH_TYPE.VERTEX:
+                        this.calcInVertexVisibility(item, localisationDistance);
+                        this.calcOutVertexVisibility(item, localisationDistance);
+                        break;
+                    case GRAPH_TYPE.EDGE:
+                        this.calcInVertexVisibility(item.getSource(), localisationDistance - 1);
+                        this.calcOutVertexVisibility(item.getTarget(), localisationDistance - 1);
+                        break;
+                    case GRAPH_TYPE.SUBGRAPH:
+                        this.m_visibleSubgraphs[item.__hpcc_id] = item;
+                        this.calcSubgraphVisibility(item, localisationDepth - 1);
+                        break;
+                }
+            }, this);
+            this.calcVisibility2();
+        },
+
+        calcInVertexVisibility: function (vertex, localisationDistance) {
+            this.m_visibleVertices[vertex.__hpcc_id] = vertex;
+            if (localisationDistance > 0) {
+                arrayUtil.forEach(vertex.getInEdges(), function (edge, idx) {
+                    this.calcInVertexVisibility(edge.getSource(), localisationDistance - 1);
+                }, this);
+            }
+        },
+
+        calcOutVertexVisibility: function (vertex, localisationDistance) {
+            this.m_visibleVertices[vertex.__hpcc_id] = vertex;
+            if (localisationDistance > 0) {
+                arrayUtil.forEach(vertex.getOutEdges(), function (edge, idx) {
+                    this.calcOutVertexVisibility(edge.getTarget(), localisationDistance - 1);
+                }, this);
+            }
+        },
+
+        calcSubgraphVisibility: function (subgraph, localisationDepth) {
+            if (localisationDepth < 0) {
+                return;
+            }
+
+            if (localisationDepth > 0) {
+                arrayUtil.forEach(subgraph.__hpcc_subgraphs, function (subgraph, idx) {
+                    this.calcSubgraphVisibility(subgraph, localisationDepth - 1);
+                }, this);
+            }
+
+            arrayUtil.forEach(subgraph.__hpcc_subgraphs, function (subgraph, idx) {
+                this.m_visibleSubgraphs[subgraph.__hpcc_id] = subgraph;
+            }, this);
+            arrayUtil.forEach(subgraph.__hpcc_vertices, function (vertex, idx) {
+                this.m_visibleVertices[vertex.__hpcc_id] = vertex;
+            }, this);
+
+            //  Calculate edges that pass through the subgraph  ---
+            var dedupEdges = {};
+            arrayUtil.forEach(this.graph.edges, function (edge, idx) {
+                if (edge.getSource().__hpcc_parent !== edge.getTarget().__hpcc_parent && subgraph === this.getCommonAncestor(edge)) {
+                    //  Only include one unique edge between subgraphs  ---
+                    if (!dedupEdges[edge.getSource().__hpcc_parent.__hpcc_id + "::" + edge.getTarget().__hpcc_parent.__hpcc_id]) {
+                        dedupEdges[edge.getSource().__hpcc_parent.__hpcc_id + "::" + edge.getTarget().__hpcc_parent.__hpcc_id] = true;
+                        this.m_visibleEdges[edge.__hpcc_id] = edge;
+                    }
+                }
+            }, this);
+        },
+
+        xmlEncode: function (str) {
+            str = "" + str;
+            return str.replace(/&/g, '&amp;')
+                      .replace(/"/g, '&quot;')
+                      .replace(/'/g, '&apos;')
+                      .replace(/</g, '&lt;')
+                      .replace(/>/g, '&gt;')
+                      .replace(/\n/g, '&#10;')
+                      .replace(/\r/g, '&#13;')
+            ;
+        },
+
+        buildVertexString: function (vertex, isPoint) {
+            var attrStr = "";
+            var propsStr = "";
+            var props = vertex.getProperties();
+            for (var key in props) {
+                if (isPoint && key.indexOf("_kind") >= 0) {
+                    propsStr += "<att name=\"_kind\" value=\"point\"/>";
+                } else if (key === "id" || key === "label") {
+                    attrStr += " " + key + "=\"" + this.xmlEncode(props[key]) + "\"";
+                } else {
+                    propsStr += "<att name=\"" + key + "\" value=\"" + this.xmlEncode(props[key]) + "\"/>";
+                }
+            }
+            return "<node" + attrStr + ">" + propsStr + "</node>";
+        },
+
+        buildEdgeString: function (edge) {
+            var attrStr = "";
+            var propsStr = "";
+            var props = edge.getProperties();
+            for (var key in props) {
+                if (key.toLowerCase() === "id" ||
+                    key.toLowerCase() === "label" ||
+                    key.toLowerCase() === "source" ||
+                    key.toLowerCase() === "target") {
+                    attrStr += " " + key + "=\"" + this.xmlEncode(props[key]) + "\"";
+                } else {
+                    propsStr += "<att name=\"" + key + "\" value=\"" + this.xmlEncode(props[key]) + "\"/>";
+                }
+            }
+            return "<edge" + attrStr + ">" + propsStr + "</edge>";
+        },
+
+        getAncestors: function (v, ancestors) {
+            var parent = v.__hpcc_parent;
+            while (parent) {
+                ancestors.push(parent);
+                parent = parent.__hpcc_parent;
+            }
+        },
+
+        getCommonAncestorV: function (v1, v2) {
+            var v1_ancestors = [];
+            var v2_ancestors = [];
+            this.getAncestors(v1, v1_ancestors);
+            this.getAncestors(v2, v2_ancestors);
+            var finger1 = v1_ancestors.length - 1;
+            var finger2 = v2_ancestors.length - 1;
+            var retVal = null;
+            while (finger1 >= 0 && finger2 >= 0 && v1_ancestors[finger1] === v2_ancestors[finger2]) {
+                retVal = v1_ancestors[finger1];
+                --finger1;
+                --finger2;
+            }
+            return retVal;
+        },
+
+        getCommonAncestor: function (e) {
+            return this.getCommonAncestorV(e.getSource(), e.getTarget());
+        },
+
+        calcAncestorVisibility: function (vertex) {
+            var ancestors = [];
+            this.getAncestors(vertex, ancestors);
+            arrayUtil.forEach(ancestors, function (item, idx) {
+                this.m_visibleSubgraphs[item.__hpcc_id] = item;
+            }, this);
+        },
+
+        calcVisibility2: function () {
+            for (var key in this.m_visibleVertices) {
+                var vertex = this.m_visibleVertices[key];
+                arrayUtil.forEach(vertex.getInEdges(), function (edge, idx) {
+                    this.m_visibleEdges[edge.__hpcc_id] = edge;
+                }, this);
+                arrayUtil.forEach(vertex.getOutEdges(), function (edge, idx) {
+                    this.m_visibleEdges[edge.__hpcc_id] = edge;
+                }, this);
+                this.calcAncestorVisibility(vertex);
+            }
+            this.calcSemiVisibleVertices();
+        },
+
+        calcSemiVisibleVertices: function () {
+            for (var key in this.m_visibleEdges) {
+                var edge = this.m_visibleEdges[key];
+                if (!this.m_visibleVertices[edge.getSource().__hpcc_id]) {
+                    this.m_semiVisibleVertices[edge.getSource().__hpcc_id] = edge.getSource();
+                    this.calcAncestorVisibility(edge.getSource());
+                }
+                if (!this.m_visibleVertices[edge.getTarget().__hpcc_id]) {
+                    this.m_semiVisibleVertices[edge.getTarget().__hpcc_id] = edge.getSource();
+                    this.calcAncestorVisibility(edge.getTarget());
+                }
+            }
+        },
+
+        writeXgmml: function () {
+            this.subgraphVisited(this.graph.subgraphs[0], true);
+            arrayUtil.forEach(this.graph.edges, function (edge, idx) {
+                this.edgeVisited(edge);
+            }, this);
+        },
+
+        subgraphVisited: function (subgraph, root) {
+            if (this.m_visibleSubgraphs[subgraph.__hpcc_id]) {
+                var propsStr = "";
+                this.m_xgmml += root ? "" : "<node id=\"" + subgraph.__hpcc_id + "\"><att><graph>";
+                var xgmmlLen = this.m_xgmml.length;
+                subgraph.walkSubgraphs(this);
+                subgraph.walkVertices(this);
+                if (xgmmlLen === this.m_xgmml.length) {
+                    //  Add at least one child otherwise subgraphs will render as a vertex  ---
+                    var vertex = subgraph.__hpcc_vertices[0];
+                    if (vertex) {
+                        this.m_xgmml += this.buildVertexString(vertex, true);
+                    }
+                }
+
+                var props = subgraph.getProperties();
+                for (var key in props) {
+                    propsStr += "<att name=\"" + key + "\" value=\"" + this.xmlEncode(props[key]) + "\"/>";
+                }
+                this.m_xgmml += root ? "" : "</graph></att>" + propsStr + "</node>";
+            }
+            return false;
+        },
+
+        vertexVisited: function (vertex) {
+            if (this.m_visibleVertices[vertex.__hpcc_id]) {
+                this.m_xgmml += this.buildVertexString(vertex, false);
+            }
+            else if (this.m_semiVisibleVertices[vertex.__hpcc_id]) {
+                this.m_xgmml += this.buildVertexString(vertex, true);
+            }
+        },
+
+        edgeVisited: function (edge) {
+            if (this.m_visibleEdges[edge.__hpcc_id]) {
+                this.m_xgmml += this.buildEdgeString(edge);
+            }
+        }
+    });
+
+    var GraphItem = declare([], {
+        constructor: function (graph, id) {
+            this.__hpcc_graph = graph;
+            this.__hpcc_id = id;
+            this._globalID = id;
+        },
+        getProperties: function () {
+            var retVal = {};
+            for (var key in this) {
+                if (key.indexOf("__") !== 0 && this.hasOwnProperty(key)) {
+                    retVal[key] = this[key];
+                }
+            }
+            return retVal;
+        }
+    });
+
+    var Subgraph = declare([GraphItem], {
+        constructor: function (graph, id) {
+            this._globalType = "Cluster";
+            this.__hpcc_subgraphs = [];
+            this.__hpcc_vertices = [];
+            this.__hpcc_edges = [];
+            this.id = id;
+        },
+
+        addSubgraph: function (subgraph) {
+            subgraph.__hpcc_parent = this;
+            this.__hpcc_subgraphs.push(subgraph);
+        },
+
+        addVertex: function (vertex) {
+            vertex.__hpcc_parent = this;
+            this.__hpcc_vertices.push(vertex);
+        },
+
+        addEdge: function (edge) {
+            edge.__hpcc_parent = this;
+            this.__hpcc_edges.push(edge);
+            edge.getSource().__hpcc_outEdges[edge.__hpcc_id] = edge;
+            edge.getTarget().__hpcc_inEdges[edge.__hpcc_id] = edge;
+        },
+
+        walkSubgraphs: function (visitor) {
+            arrayUtil.forEach(this.__hpcc_subgraphs, function (subgraph, idx) {
+                if (visitor.subgraphVisited(subgraph)) {
+                    subgraph.walkSubgraphs(visitor);
+                }
+            }, this);
+        },
+
+        walkVertices: function (visitor) {
+            arrayUtil.forEach(this.__hpcc_vertices, function (vertex, idx) {
+                visitor.vertexVisited(vertex);
+            }, this);
+        }
+    });
+
+    var Vertex = declare([GraphItem], {
+        constructor: function () {
+            this._globalType = "Vertex";
+            this.__hpcc_inEdges = {};
+            this.__hpcc_outEdges = {};
+        },
+
+        getInEdges: function () {
+            var retVal = [];
+            for (var key in this.__hpcc_inEdges) {
+                retVal.push(this.__hpcc_inEdges[key]);
+            }
+            return retVal;
+        },
+
+        getOutEdges: function () {
+            var retVal = [];
+            for (var key in this.__hpcc_outEdges) {
+                retVal.push(this.__hpcc_outEdges[key]);
+            }
+            return retVal;
+        }
+    });
+
+    var Edge = declare([GraphItem], {
+        constructor: function (graph, id) {
+            this._globalType = "Edge";
+        },
+
+        getSource: function () {
+            return this.__hpcc_graph.idx[this._sourceActivity || this.source];
+        },
+
+        getTarget: function () {
+            return this.__hpcc_graph.idx[this._targetActivity || this.target];
+        }
+    });
+
+    var Graph = declare([], {
+        constructor: function () {
+            this.clear();
+        },
+
+        clear: function () {
+            this.xgmml = "";
+
+            this.idx = {};
+            this.subgraphs = [];
+            this.vertices = [];
+            this.edges = [];
+        },
+
+        load: function (xgmml) {
+            this.clear();
+            this.merge(xgmml);
+        },
+
+        merge: function (xgmml) {
+            this.xgmml = xgmml;
+            var dom = parser.parse(xgmml);
+            this.walkDocument(dom.documentElement, "0");
+        },
+
+        getGlobalType: function (item) {
+            if (item instanceof Vertex) {
+                return GRAPH_TYPE.VERTEX;
+            } else if (item instanceof Edge) {
+                return GRAPH_TYPE.EDGE;
+            } else if (item instanceof Subgraph) {
+                return GRAPH_TYPE.SUBGRAPH;
+            } else if (item instanceof Graph) {
+                return GRAPH_TYPE.GRAPH;
+            }
+            return GRAPH_TYPE.UNKNOWN;
+        },
+
+        getGlobalTypeString: function (item) {
+            if (item instanceof Vertex) {
+                return GRAPH_TYPE_STRING.VERTEX;
+            } else if (item instanceof Edge) {
+                return GRAPH_TYPE_STRING.EDGE;
+            } else if (item instanceof Subgraph) {
+                return GRAPH_TYPE_STRING.SUBGRAPH;
+            } else if (item instanceof Graph) {
+                return GRAPH_TYPE_STRING.GRAPH;
+            }
+            return GRAPH_TYPE_STRING.UNKNOWN;
+        },
+
+        getItem: function (docNode, id) {
+            if (!this.idx[id]) {
+                switch (docNode.tagName) {
+                    case "graph":
+                        var subgraph = new Subgraph(this, id);
+                        this.subgraphs.push(subgraph);
+                        this.idx[id] = subgraph;
+                        break;
+                    case "node":
+                        var vertex = new Vertex(this, id);
+                        this.vertices.push(vertex);
+                        this.idx[id] = vertex;
+                        break;
+                    case "edge":
+                        var edge = new Edge(this, id);
+                        this.edges.push(edge);
+                        this.idx[id] = edge;
+                        break;
+                    default:
+                        console.log("Graph.getItem - Unknown Node Type!");
+                        break;
+                }
+            }
+            var retVal = this.idx[id];
+            arrayUtil.forEach(docNode.attributes, function (attr, idx) {
+                retVal[attr.name] = attr.value;
+            }, this);
+            return retVal;
+        },
+
+        getChildByTagName: function (docNode, tagName) {
+            var retVal = null;
+            arrayUtil.some(docNode.childNodes, function (childNode, idx) {
+                if (childNode.tagName === tagName) {
+                    retVal = childNode;
+                    return true;
+                }
+            }, this);
+            return retVal;
+        },
+
+        walkDocument: function (docNode, id) {
+            var retVal = this.getItem(docNode, id);
+            arrayUtil.forEach(docNode.childNodes, function (childNode, idx) {
+                switch (childNode.nodeType) {
+                    case 1:     //	ELEMENT_NODE
+                        switch (childNode.tagName) {
+                            case "graph":
+                                break;
+                            case "node":
+                                var isSubgraph = false;
+                                var attNode = this.getChildByTagName(childNode, "att");
+                                if (attNode) {
+                                    var graphNode = this.getChildByTagName(attNode, "graph");
+                                    if (graphNode) {
+                                        isSubgraph = true;
+                                        var subgraph = this.walkDocument(graphNode, childNode.getAttribute("id"));
+                                        retVal.addSubgraph(subgraph);
+                                    }
+                                }
+                                if (!isSubgraph) {
+                                    var vertex = this.walkDocument(childNode, childNode.getAttribute("id"));
+                                    retVal.addVertex(vertex);
+                                }
+                                break;
+                            case "att":
+                                var name = childNode.getAttribute("name");
+                                var value = childNode.getAttribute("value");
+                                retVal[name] = value;
+                                break;
+                            case "edge":
+                                var edge = this.walkDocument(childNode, childNode.getAttribute("id"));
+                                retVal.addEdge(edge);
+                                break;
+                            default:
+                                break;
+                        }
+                        break;
+                    case 2:     //	ATTRIBUTE_NODE
+                    case 3:     //	TEXT_NODE
+                    case 4:     //	CDATA_SECTION_NODE
+                    case 5:     //	ENTITY_REFERENCE_NODE
+                    case 6:     //	ENTITY_NODE
+                    case 7:     //	PROCESSING_INSTRUCTION_NODE
+                    case 8:     //	COMMENT_NODE
+                    case 9:     //	DOCUMENT_NODE
+                    case 10:    //	DOCUMENT_TYPE_NODE
+                    case 11:    //	DOCUMENT_FRAGMENT_NODE
+                    case 12:    //	NOTATION_NODE
+                    default:
+                        break;
+                }
+            }, this);
+            return retVal;
+        },
+
+        getLocalisedXGMML: function (items, localisationDepth, localisationDistance) {
+            var xgmmlWriter = new LocalisedXGMMLWriter(this);
+            xgmmlWriter.calcVisibility(items, localisationDepth, localisationDistance);
+            xgmmlWriter.writeXgmml();
+            return "<graph>" + xgmmlWriter.m_xgmml + "</graph>";
+        }
+    });
+
+    return Graph;
+});

+ 19 - 8
esp/src/eclwatch/GraphPageWidget.js

@@ -32,6 +32,7 @@ define([
 
     "hpcc/_Widget",
     "hpcc/GraphWidget",
+    "hpcc/JSGraphWidget",
     "hpcc/ESPUtil",
     "hpcc/ESPWorkunit",
     "hpcc/TimingTreeMapWidget",
@@ -54,13 +55,14 @@ define([
 ], function (declare, lang, i18n, nlsHPCC, arrayUtil, Deferred, dom, domConstruct, on, html,
             registry, Dialog,
             entities,
-            _Widget, GraphWidget, ESPUtil, ESPWorkunit, TimingTreeMapWidget, WsWorkunits,
+            _Widget, GraphWidget, JSGraphWidget, ESPUtil, ESPWorkunit, TimingTreeMapWidget, WsWorkunits,
             template) {
     return declare("GraphPageWidget", [_Widget], {
         templateString: template,
         baseClass: "GraphPageWidget",
         i18n: nlsHPCC,
 
+        graphType: dojoConfig.isPluginInstalled() ? "GraphWidget" : "JSGraphWidget",
         borderContainer: null,
         rightBorderContainer: null,
         graphName: "",
@@ -359,6 +361,20 @@ define([
             if (this.inherited(arguments))
                 return;
 
+            if (this.global._plugin) {
+                this.doInit(params);
+            } else {
+                this.global.on("ready", lang.hitch(this, function (evt) {
+                    this.doInit(params);
+                }));
+            }
+        },
+
+        doInit: function(params) {
+            if (this.global.version.major < 5) {
+                dom.byId(this.id + "Warning").innerHTML = this.i18n.WarnOldGraphControl + " (" + this.global.version.version + ")";
+            }
+
             if (params.SafeMode && params.SafeMode != "false") {
                 this.overviewTabContainer.selectChild(this.widget.SubgraphsGridCP);
                 this.localTabContainer.selectChild(this.properties);
@@ -414,12 +430,6 @@ define([
                 },
                 hideHelp: true
             }, params));
-
-            this.global.on("ready", lang.hitch(this, function(evt) {
-                if (this.global.version.major < 5) {
-                    dom.byId(this.id + "Warning").innerHTML = this.i18n.WarnOldGraphControl + " (" + this.global.version.version + ")";
-                }
-            }));
         },
 
         refreshData: function () {
@@ -437,7 +447,8 @@ define([
                 if (this.overview.depth.get("value") === -1) {
                     var newDepth = 0;
                     for (; newDepth < 5; ++newDepth) {
-                        if (this.global.getLocalisedXGMML([0], newDepth, this.overview.distance.get("value")) !== "") {
+                        var xgmml = this.global.getLocalisedXGMML([this.global.getItem(0)], newDepth, this.overview.distance.get("value"));
+                        if (xgmml !== "" && xgmml !== "<graph></graph>") {
                             break;
                         }
                     }

+ 17 - 7
esp/src/eclwatch/GraphTreeWidget.js

@@ -38,6 +38,7 @@ define([
 
     "hpcc/_Widget",
     "hpcc/GraphWidget",
+    "hpcc/JSGraphWidget",
     "hpcc/ESPUtil",
     "hpcc/ESPWorkunit",
     "hpcc/TimingTreeMapWidget",
@@ -59,7 +60,7 @@ define([
             registry, Dialog, Menu, MenuItem, MenuSeparator, CheckedMenuItem,
             entities,
             tree,
-            _Widget, GraphWidget, ESPUtil, ESPWorkunit, TimingTreeMapWidget, WsWorkunits,
+            _Widget, GraphWidget, JSGraphWidget, ESPUtil, ESPWorkunit, TimingTreeMapWidget, WsWorkunits,
             template) {
 
     return declare("GraphTreeWidget", [_Widget], {
@@ -67,6 +68,7 @@ define([
         baseClass: "GraphTreeWidget",
         i18n: nlsHPCC,
 
+        graphType: dojoConfig.isPluginInstalled() ? "GraphWidget" : "JSGraphWidget",
         graphName: "",
         wu: null,
         global: null,
@@ -370,6 +372,20 @@ define([
             if (this.inherited(arguments))
                 return;
 
+            if (this.global._plugin) {
+                this.doInit(params);
+            } else {
+                this.global.on("ready", lang.hitch(this, function (evt) {
+                    this.doInit(params);
+                }));
+            }
+        },
+
+        doInit: function (params) {
+            if (this.global.version.major < 5) {
+                dom.byId(this.id + "Warning").innerHTML = this.i18n.WarnOldGraphControl + " (" + this.global.version.version + ")";
+            }
+
             if (params.SafeMode && params.SafeMode != "false") {
                 this.main.depth.set("value", 1);
                 var dotAttrs = this.global.getDotMetaAttributes();
@@ -419,12 +435,6 @@ define([
                 },
                 hideHelp: true
             }, params));
-
-            this.global.on("ready", lang.hitch(this, function(evt) {
-                if (this.global.version.major < 5) {
-                    dom.byId(this.id + "Warning").innerHTML = this.i18n.WarnOldGraphControl + " (" + this.global.version.version + ")";
-                }
-            }));
         },
 
         refreshData: function () {

+ 9 - 24
esp/src/eclwatch/GraphWidget.js

@@ -29,6 +29,7 @@ define([
     "dojo/store/Memory",
     "dojo/store/Observable",
     "dojo/store/util/QueryResults",
+    "dojo/Evented",
 
     "dijit/registry",
     "dijit/layout/BorderContainer",
@@ -43,7 +44,7 @@ define([
     "dijit/form/Button",
     "dijit/form/NumberSpinner"
     
-], function (declare, lang, i18n, nlsHPCC, arrayUtil, Deferred, aspect, has, dom, domConstruct, domClass, domStyle, Memory, Observable, QueryResults,
+], function (declare, lang, i18n, nlsHPCC, arrayUtil, Deferred, aspect, has, dom, domConstruct, domClass, domStyle, Memory, Observable, QueryResults, Evented,
             registry, BorderContainer, ContentPane,
             _Widget,
             template) {
@@ -263,7 +264,7 @@ define([
                                         this.graphViewHistory.getLatest().navigateTo(this);
                                     }
                                     deferred.resolve("Layout Complete.");
-                                    this.refreshRootState(context.selectedGlobalIDs);
+                                    this.refreshRootState(context.rootGlobalIDs);
                                 };
                                 if (this.svg) {
                                     targetGraphWidget.startCachedLayout(this.svg);
@@ -278,7 +279,7 @@ define([
                             targetGraphWidget.setSelectedAsGlobalID(context.selectedGlobalIDs);
                             targetGraphWidget.setMessage("");
                             deferred.resolve("XGMML Did Not Change.");
-                            targetGraphWidget.refreshRootState(context.selectedGlobalIDs);
+                            targetGraphWidget.refreshRootState(context.rootGlobalIDs);
                         }
                     }));
                 }));
@@ -296,6 +297,7 @@ define([
 
         constructor: function (sourceGraphWidget) {
             this.sourceGraphWidget = sourceGraphWidget;
+            this.historicPos = 0;
             this.history = [];
             this.index = {};
         },
@@ -488,7 +490,7 @@ define([
 
             startup: function (args) {
                 this.inherited(arguments);
-                this._isPluginInstalled = this.isPluginInstalled();
+                this._isPluginInstalled = dojoConfig.isPluginInstalled();
                 this.createPlugin();
                 this.watchStyleChange();
             },
@@ -751,24 +753,6 @@ define([
                 }
             },
 
-            isPluginInstalled: function () {
-                if (this.isIE || this.isIE11) {
-                    try {
-                        var o = new ActiveXObject("HPCCSystems.HPCCSystemsGraphViewControl.1");
-                        o = null;
-                        return true;
-                    } catch (e) { }
-                    return false;
-                } else {
-                    for (var i = 0, p = navigator.plugins, l = p.length; i < l; i++) {
-                        if (p[i].name.indexOf("HPCCSystemsGraphViewControl") > -1) {
-                            return true;
-                        }
-                    }
-                    return false;
-                }
-            },
-
             createPlugin: function () {
                 if (!this.hasPlugin()) {
                     if (this._isPluginInstalled) {
@@ -1174,7 +1158,9 @@ define([
 
             registerEvent: function (evt, func) {
                 if (this.hasPlugin()) {
-                    if (this.isIE11) {
+                    if (this._plugin instanceof Evented) {
+                        this._plugin.on(evt, func);
+                    } else if (this.isIE11) {
                         this._plugin["on" + evt] = func;
                     } else if (this._plugin.attachEvent !== undefined) {
                         return this._plugin.attachEvent("on" + evt, func);
@@ -1188,7 +1174,6 @@ define([
             refreshActionState: function () {
                 this.setDisabled(this.id + "Previous", !this.graphViewHistory.hasPrevious(), "iconLeft", "iconLeftDisabled");
                 this.setDisabled(this.id + "Next", !this.graphViewHistory.hasNext(), "iconRight", "iconRightDisabled");
-                var selection = this.getSelection();
                 this.setDisabled(this.id + "SyncSelection", !this.getSelection().length, "iconSync", "iconSyncDisabled");
             },
 

+ 330 - 0
esp/src/eclwatch/JSGraphWidget.js

@@ -0,0 +1,330 @@
+/*##############################################################################
+#    HPCC SYSTEMS software Copyright (C) 2012 HPCC Systems.
+#
+#    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.
+############################################################################## */
+define([
+    "dojo/_base/declare",
+    "dojo/_base/lang",
+    "dojo/i18n",
+    "dojo/i18n!./nls/hpcc",
+    "dojo/_base/array",
+    "dojo/Evented",
+
+    "hpcc/GraphWidget",
+    "hpcc/ESPGraph"
+], function (declare, lang, i18n, nlsHPCC, arrayUtil, Evented,
+            GraphWidget, ESPGraph) {
+    var loadJSPlugin = function (callback) {
+        require(["src/common/Shape", "src/common/TextBox", "src/graph/Graph", "src/graph/Vertex", "src/graph/Edge"], function (Shape, TextBox, Graph, Vertex, Edge) {
+            callback(declare([Evented], {
+                KeyState_None: 0,
+                KeyState_Shift: 1,
+                KeyState_Control: 2,
+                KeyState_Menu: 4,
+
+                constructor: function (domNode) {
+                    this.graphData = new ESPGraph();
+                    this.graphWidget = new Graph()
+                        .target(domNode.id)
+                        .allowDragging(false)
+                        .render()
+                    ;
+                    var context = this;
+                    this.graphWidget.vertex_click = function (item, event) {
+                        context.emit("SelectionChanged", [item]);
+                    }
+                    this.graphWidget.vertex_dblclick = function (item, event) {
+                        context.emit("MouseDoubleClick", item, (event.shiftKey ? context.KeyState_Shift : 0) + (event.ctrlKey ? context.KeyState_Control : 0) + (event.altKey ? context.KeyState_Menu : 0));
+                    }
+                },
+
+                setMessage: function (msg) {
+                },
+
+                setScale: function (scale) {
+                    this.graphWidget.zoom.scale(scale / 100);
+                    this.graphWidget.applyZoom(this.graphWidget._transitionDuration);
+                },
+
+                centerOnItem: function (item, scaleToFit, widthOnly) {
+                    if (item === 0) {
+                        item = this.graphData.subgraphs[0];
+                    }
+                    var bounds = this.graphWidget.getBounds([item.__widget]);
+                    if (scaleToFit) {
+                        if (widthOnly) {
+                            bounds[0][1] = 0;
+                            bounds[1][1] = 0;
+                        }
+                        this.graphWidget.shrinkToFit(bounds);
+                    } else {
+                        this.graphWidget.centerOn(bounds);
+                    }
+                },
+
+                getSelectionAsGlobalID: function () {
+                    var selection = this.graphWidget.selection();
+                    return selection.map(function (item) {
+                        return item.__hpcc_globalID;
+                    });
+                },
+
+                setSelectedAsGlobalID: function (globalIDs) {
+                    var selection = [];
+                    globalIDs.forEach(function (globalID, idx) {
+                        var item = this.getItem(globalID);
+                        if (item && item.__widget) {
+                            selection.push(item.__widget);
+                        }
+                    }, this);
+                    this.graphWidget.selection(selection);
+                },
+
+                getGlobalType: function (item) {
+                    return this.graphData.getGlobalTypeString(item);
+                },
+
+                getGlobalID: function (item) {
+                    return item.__hpcc_id;
+                },
+
+                getItem: function (globalID) {
+                    return this.graphData.idx[globalID];
+                },
+
+                setSelected: function (items) {
+                    this.graphWidget.selection(items);
+                },
+
+                getSelection: function () {
+                    return this.graphWidget.selection();
+                },
+
+                getSVG: function () {
+                    return "";  //TODO - Should be Serialized Layout to prevent re-calculation on prev/next  ---
+                },
+
+                getDOT: function () {
+                    return "";
+                },
+
+                getVertices: function () {
+                    return this.graphData.vertices;
+                },
+
+                find: function (findText) {
+                    return this.graphData.vertices.filter(function (item) {
+                        return (item.label.toLowerCase().indexOf(findText.toLowerCase()) >= 0);
+                    });
+                },
+
+                gatherTreeWithProperties: function (subgraph) {
+                    subgraph = subgraph || this.graphData.subgraphs[0];
+                    var retVal = subgraph.getProperties();
+                    retVal._children = [];
+                    arrayUtil.forEach(subgraph.__hpcc_subgraphs, function (subgraph, idx) {
+                        retVal._children.push(this.gatherTreeWithProperties(subgraph));
+                    }, this);
+                    arrayUtil.forEach(subgraph.__hpcc_vertices, function (vertex, idx) {
+                        retVal._children.push(vertex.getProperties());
+                    }, this);
+                    return retVal;
+                },
+
+                getProperties: function (item) {
+                    return item.getProperties();
+                },
+
+                getTreeWithProperties: function () {
+                    return [this.gatherTreeWithProperties()];
+                },
+
+                getSubgraphsWithProperties: function () {
+                    return this.graphData.subgraphs;
+                },
+
+                getVerticesWithProperties: function () {
+                    return this.graphData.vertices;
+                },
+
+                getEdgesWithProperties: function () {
+                    return this.graphData.edges;
+                },
+
+                getLocalisedXGMML: function (selectedItems, depth, distance) {
+                    return this.graphData.getLocalisedXGMML(selectedItems, depth, distance);
+                },
+
+                startLayout: function (layout) {
+                    var context = this;
+                    setTimeout(function (layout) {
+                        context.graphWidget
+                            .layout("Hierarchy")
+                            .render()
+                        ;
+                        context.emit("LayoutFinished", {});
+                    }, 100);
+                },
+
+                clear: function () {
+                    this.graphData.clear();
+                    this.graphWidget.clear();
+                },
+
+                mergeXGMML: function (xgmml, timers) {
+                    return this.loadXGMML(xgmml, true, timers)
+                },
+
+                loadXGMML: function (xgmml, merge, timers) {
+                    var retVal = this.inherited(arguments);
+                    if (merge) {
+                        this.graphData.merge(xgmml);
+                    } else {
+                        this.graphData.load(xgmml);
+                    }
+                    var vertices = [];
+                    var edges = [];
+                    var hierarchy = [];
+
+                    arrayUtil.forEach(this.graphData.subgraphs, function (subgraph, idx) {
+                        if (!subgraph.__widget) {
+                            subgraph.__widget = new Shape()
+                                .shape("rect")
+                            ;
+                            subgraph.__widget.__hpcc_globalID = subgraph.__hpcc_id;
+                        }
+                        vertices.push(subgraph.__widget);
+                    }, this);
+                    arrayUtil.forEach(this.graphData.vertices, function (item, idx) {
+                        if (!item.__widget) {
+                            switch (item._kind) {
+                                case "point":
+                                    item.__widget = new Shape()
+                                        .radius(3)
+                                    ;
+                                    break;
+                                default:
+                                    item.__widget = new TextBox()
+                                        .text(item.label)
+                                    ;
+                                    break;
+                            }
+                            item.__widget.__hpcc_globalID = item.__hpcc_id;
+                        }
+                        vertices.push(item.__widget);
+                    }, this);
+                    arrayUtil.forEach(this.graphData.edges, function (item, idx) {
+                        if (!item.__widget) {
+                            var strokeDasharray = null;
+                            var weight = 100;
+                            if (item._dependsOn) {
+                                weight = 10;
+                                strokeDasharray = "1,5";
+                            } else if (item._childGraph) {
+                                strokeDasharray = "5,5";
+                            } else if (item._sourceActivity || item._targetActivity) {
+                                weight = 25;
+                                strokeDasharray = "5,5,10,5";
+                            }
+
+                            var label = item.label ? item.label : "";
+                            if (item.count) {
+                                if (label) {
+                                    label += "\n";
+                                }
+                                label += item.count.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
+                            }
+                            if (item.inputProgress) {
+                                if (label) {
+                                    label += "\n";
+                                }
+                                label += "[" + item.inputProgress.replace(/\B(?=(\d{3})+(?!\d))/g, ",") + "]";
+                            }
+                            if (item.maxskew && item.minskew) {
+                                if (label) {
+                                    label += "\n";
+                                }
+                                label += "+" + item.maxskew + "%, -" + item.minskew + "%";
+                            }
+                            item.__widget = new Edge()
+                                .sourceVertex(item.getSource().__widget)
+                                .targetVertex(item.getTarget().__widget)
+                                .targetMarker("arrowHead")
+                                .weight(weight)
+                                .strokeDasharray(strokeDasharray)
+                                .text(label)
+                            ;
+                            item.__widget.__hpcc_globalID = item.__hpcc_id;
+                        }
+                        edges.push(item.__widget);
+                    }, this);
+                    arrayUtil.forEach(this.graphData.subgraphs, function (subgraph, idx) {
+                        arrayUtil.forEach(subgraph.__hpcc_subgraphs, function (item, idx) {
+                            if (!subgraph.__widget || !item.__widget) {
+                                var d = 0;
+                            }
+                            hierarchy.push({ parent: subgraph.__widget, child: item.__widget });
+                        }, this);
+                        arrayUtil.forEach(subgraph.__hpcc_vertices, function (item, idx) {
+                            if (!subgraph.__widget || !item.__widget) {
+                                var d = 0;
+                            }
+                            hierarchy.push({ parent: subgraph.__widget, child: item.__widget });
+                        }, this);
+                    }, this);
+                    this.graphWidget.data({ vertices: vertices, edges: edges, hierarchy: hierarchy, merge: merge });
+                    return retVal;
+                }
+            }));
+        });
+    };
+
+    return declare("JSGraphWidget", [GraphWidget], {
+        baseClass: "JSGraphWidget",
+        constructor: function () {
+            this.graphData = new ESPGraph();
+        },
+
+        resize: function (size) {
+            this.inherited(arguments);
+            if (this.hasPlugin()) {
+                this._plugin.graphWidget
+                    .resize()
+                    .render()
+                ;
+            }
+        },
+
+        createPlugin: function () {
+            if (!this.hasPlugin()) {
+                var context = this;
+                loadJSPlugin(function (JSPlugin) {
+                    context._plugin = new JSPlugin(context.graphContentPane.domNode);
+                    context.version = {
+                        major: 6,
+                        minor: 0
+                    };
+                    context.registerEvents();
+                    context.emit("ready");
+                });
+            }
+        },
+
+        watchSplitter: function (splitter) {
+        },
+
+        watchSelect: function (select) {
+        }
+    });
+});

+ 26 - 0
esp/src/eclwatch/css/hpcc.css

@@ -1017,3 +1017,29 @@ margin-left:-20px;
     height: auto;
 }
 
+.claro .graph .zoom {
+    fill: white;
+}
+
+.claro .graph .edge .shape {
+    fill: white;
+}
+
+.claro .graph .graphVertex > .shape {
+    stroke: black;
+    fill: none;
+}
+
+.claro .graph .graphVertex > .shape.selected {
+    stroke: #1f77b4;
+}
+
+.claro .graph .graphVertex .textbox .shape {
+    stroke: black;
+    fill: white;
+}
+
+.claro .graph .graphVertex .textbox.selected .shape {
+    fill: #dcf1ff;
+    stroke: #1f77b4;
+}

+ 36 - 7
esp/src/eclwatch/dojoConfig.js

@@ -38,14 +38,30 @@ var dojoConfig = (function () {
         getImageHTML: function (name, tooltip) {
             return "<img src='" + this.getImageURL(name) + "'" + (tooltip ? " title='" + tooltip + "'" : "") + " class='iconAlign'/>";
         },
+        isPluginInstalled: function () {
+            try {
+                var o = new ActiveXObject("HPCCSystems.HPCCSystemsGraphViewControl.1");
+                o = null;
+                return true;
+            } catch (e) { 
+            }
+            if (navigator.plugins) {
+                for (var i = 0, p = navigator.plugins, l = p.length; i < l; i++) {
+                    if (p[i].name.indexOf("HPCCSystemsGraphViewControl") > -1) {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        },
+        paths: {
+            //  Visualization Paths  ---
+            "async": urlInfo.basePath + "/Visualization/widgets/lib/requirejs/plugins/async",
+            "css": urlInfo.basePath + "/Visualization/widgets/lib/requirejs/plugins/css",
+            "goog": urlInfo.basePath + "/Visualization/widgets/lib/requirejs/plugins/goog",
+            "propertyParser": urlInfo.basePath + "/Visualization/widgets/lib/requirejs/plugins/propertyParser"
+        },
         packages: [{
-            name: "d3",
-            location: urlInfo.basePath + "/d3",
-            main:"d3"
-        }, {
-            name: "topojson",
-            location: urlInfo.basePath + "/topojson"
-        }, {
             name: "hpcc",
             location: urlInfo.scriptsPath
         }, {
@@ -58,6 +74,19 @@ var dojoConfig = (function () {
             name: "plugins",
             location: urlInfo.pluginsPath
         }, {
+            name: "src",
+            location: urlInfo.basePath + "/Visualization/widgets/src"
+        }, {
+            name: "lib",
+            location: urlInfo.basePath + "/Visualization/widgets/lib"
+        }, {
+            name: "d3",
+            location: urlInfo.basePath + "/Visualization/widgets/lib/d3",
+            main:"d3"
+        }, {
+            name: "topojson",
+            location: urlInfo.basePath + "/Visualization/widgets/lib/topojson"
+        }, {
             name: "this",
             location: urlInfo.thisPath
         }],

+ 12 - 1
esp/src/eclwatch/package.js

@@ -7,7 +7,18 @@ var profile = (function(){
             "hpcc/viz/DojoD3WordCloud": true,
             "hpcc/viz/d3-cloud/d3.layout.cloud": true,
             "hpcc/viz/map/us.json": true,
-            "hpcc/viz/map/us_counties.json": true
+            "hpcc/viz/map/us_counties.json": true,
+            "hpcc/viz/DojoD3": true,
+            "hpcc/viz/DojoD3BarChart": true,
+            "hpcc/viz/DojoD3Choropleth": true,
+            "hpcc/viz/DojoD3Choropleth": true,
+            "hpcc/viz/DojoD3CooccurrenceGraph": true,
+            "hpcc/viz/DojoD3Histogram": true,
+            "hpcc/viz/DojoD3PieChart": true,
+            "hpcc/viz/DojoD3ScatterChart": true,
+            "hpcc/viz/DojoD3DonutChart": true,
+            "hpcc/viz/DojoD3ForceDirectedGraph": true,
+            "hpcc/viz/DojoSlider": true
         };
         return (mid in list) ||
             (/^hpcc\/resources\//.test(mid) && !/\.css$/.test(filename)) ||

+ 1 - 1
esp/src/eclwatch/templates/ECLPlaygroundWidget.html

@@ -12,7 +12,7 @@
         </div>
         <div id="${id}Source" class="centerPanel" data-dojo-props="region: 'center'" data-dojo-type="ECLSourceWidget">
         </div>
-        <div id="${id}GraphControl" style="width: 240px;" data-dojo-props="minSize:120, region: 'right', splitter:true" data-dojo-type="GraphWidget">
+        <div id="${id}GraphControl" style="width: 240px;" data-dojo-props="minSize:120, region: 'right', splitter:true" data-dojo-type="${graphType}">
         </div>
         <div id="${id}StackContainer" style="height: 240px;" data-dojo-props="minSize:120, region: 'bottom', splitter:true" data-dojo-type="dijit.layout.StackContainer">
             <div id="${id}_ErrWarn" data-dojo-props="iconClass:'iconErrWarn', showTitle: false, disabled: true, title: '${i18n.ErrorWarnings}'" data-dojo-type="InfoGridWidget">

+ 4 - 4
esp/src/eclwatch/templates/GraphPageWidget.html

@@ -25,11 +25,11 @@
             <label id="${id}Warning"></label>
             <div id="${id}NewPage" class="right" data-dojo-attach-event="onClick:_onNewPage" data-dojo-props="iconClass:'iconNewPage', showLabel:false" data-dojo-type="dijit.form.Button">${i18n.OpenInNewPage}</div>
         </div>
-        <div id="${id}MainGraphWidget" data-dojo-props="region: 'center'" data-dojo-type="GraphWidget">
+        <div id="${id}MainGraphWidget" data-dojo-props="region: 'center'" data-dojo-type="${graphType}">
         </div>
         <div id="${id}RightBorderContainer" style="width: 33%; padding: 0px; overflow: hidden" data-dojo-props="region: 'right', splitter:true, minSize: 120" data-dojo-type="dijit.layout.BorderContainer">
             <div id="${id}OverviewTabContainer" data-dojo-props="region: 'center', tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
-                <div id="${id}MiniGraphWidget" title="${i18n.Overview}" data-dojo-type="GraphWidget">
+                <div id="${id}MiniGraphWidget" title="${i18n.Overview}" data-dojo-type="${graphType}">
                 </div>
                 <div id="${id}SubgraphsGridCP" title="${i18n.Subgraphs}" style="padding: 0px; overflow: hidden" data-dojo-type="dijit.layout.ContentPane">
                     <div id="${id}SubgraphsGrid">
@@ -47,14 +47,14 @@
                 </div>
             </div>
             <div id="${id}LocalTabContainer" style="height: 66%" data-dojo-props="region: 'bottom', splitter:true, minSize: 120, tabPosition: 'bottom'" data-dojo-type="dijit.layout.TabContainer">
-                <div id="${id}LocalGraphWidget" title="${i18n.Local}" data-dojo-type="GraphWidget">
+                <div id="${id}LocalGraphWidget" title="${i18n.Local}" data-dojo-type="${graphType}">
                 </div>
                 <div id="${id}Properties" title="${i18n.Properties}" data-dojo-type="dijit.layout.ContentPane">
                 </div>
             </div>
         </div>
     </div>
-    <div id="${id}GlobalGraphWidget" data-dojo-type="GraphWidget">
+    <div id="${id}GlobalGraphWidget" data-dojo-type="${graphType}">
     </div>
     <div id="${id}XGMMLDialog" title="${i18n.XGMML}" data-dojo-type="dijit.Dialog">
         <div class="dijitDialogPaneContentArea">

+ 2 - 2
esp/src/eclwatch/templates/GraphTreeWidget.html

@@ -25,7 +25,7 @@
             <label id="${id}Warning"></label>
             <div id="${id}NewPage" class="right" data-dojo-attach-event="onClick:_onNewPage" data-dojo-props="iconClass:'iconNewPage', showLabel:false" data-dojo-type="dijit.form.Button">${i18n.OpenInNewPage}</div>
         </div>
-        <div id="${id}MainGraphWidget" data-dojo-props="region: 'center'" data-dojo-type="GraphWidget">
+        <div id="${id}MainGraphWidget" data-dojo-props="region: 'center'" data-dojo-type="${graphType}">
         </div>
         <div id="${id}SideBorderContainer" style="width: 33%" data-dojo-props="region: 'left', splitter:true, minSize: 120" data-dojo-type="dijit.layout.BorderContainer">
             <div id="${id}TreeToolbar" class="topPanel" data-dojo-props="region: 'top'" data-dojo-type="dijit.Toolbar">
@@ -60,7 +60,7 @@
             </div>
         </div>
     </div>
-    <div id="${id}GlobalGraphWidget" data-dojo-type="GraphWidget">
+    <div id="${id}GlobalGraphWidget" data-dojo-type="${graphType}">
     </div>
     <div id="${id}XGMMLDialog" title="${i18n.XGMML}" data-dojo-type="dijit.Dialog">
         <div class="dijitDialogPaneContentArea">