From f342d90ec762dff0baa8e55b65b41b1147a3294b Mon Sep 17 00:00:00 2001 From: Mark Ostrander Date: Mon, 11 Nov 2019 07:12:16 -0500 Subject: [PATCH 1/2] Add support for subgraph and clusters --- .assets/clusters.svg | 169 ++++++++++++++++++++++++++++ README.md | 98 +++++++++++++++- {samples => examples}/attributes.js | 0 {samples => examples}/basic.js | 0 examples/clusters.js | 49 ++++++++ {samples => examples}/html.js | 0 index.js | 2 +- lib/attributes.js | 3 +- lib/graph.js | 60 ++++++++-- package.json | 5 +- 10 files changed, 369 insertions(+), 17 deletions(-) create mode 100644 .assets/clusters.svg rename {samples => examples}/attributes.js (100%) rename {samples => examples}/basic.js (100%) create mode 100644 examples/clusters.js rename {samples => examples}/html.js (100%) diff --git a/.assets/clusters.svg b/.assets/clusters.svg new file mode 100644 index 0000000..c845e3f --- /dev/null +++ b/.assets/clusters.svg @@ -0,0 +1,169 @@ + + + + + + +Subgraph Example + + +cluster0 + +process #1 + + +cluster1 + +process #2 + + + +a0 + +a0 + + + +a1 + +a1 + + + +a0->a1 + + + + + +a2 + +a2 + + + +a1->a2 + + + + + +b3 + +b3 + + + +a1->b3 + + + + + +a3 + +a3 + + + +a2->a3 + + + + + +a3->a0 + + + + + +end + + + + + +end + + + +a3->end + + + + + +b0 + +b0 + + + +b1 + +b1 + + + +b0->b1 + + + + + +b2 + +b2 + + + +b1->b2 + + + + + +b2->a3 + + + + + +b2->b3 + + + + + +b3->end + + + + + +start + + + + + +start + + + +start->a0 + + + + + +start->b0 + + + + + diff --git a/README.md b/README.md index fd3ddb1..2f8483b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A simple node wrapper for [Graphviz](http://www.graphviz.org/). -> **NOTE:** This is a stripped down node implementation of [Graphviz](http://www.graphviz.org/). It does not support support undirected graph objects, clusters or subgraphs. For more bugs/issues or additional features please submit an issue. +> **NOTE:** This is a stripped down node implementation of [Graphviz](http://www.graphviz.org/). It does not support support undirected graph objects, clusters or subgraphs. For more bugs/issues or additional features please submit an issue. ## Documentation @@ -14,7 +14,7 @@ npm run docs Js docs will automatically open in your browser. -You can also view node examples in the `samples/` directory. +You can also view node examples in the `examples/` directory. ## Prerequisite @@ -57,7 +57,7 @@ g.render("example"); ## Basic Usage -The graphviz module provides one `Graph` class. It creates graph descriptions in the DOT language for **directed** graphs. +The graphviz module provides one `Graph` class. It creates graph descriptions in the DOT language for **directed** graphs. Create a directed graph by instantiating a new Graph object: @@ -257,6 +257,98 @@ digraph "html" { ![HTML Node Table 2](./.assets/html_2.svg) +## Subgraphs & Clusters + +Graph and Digraph objects have an `addSubgraph() method for adding a subgraph to a graph.This is done by creating a ready-made graph object of the same kind as the only argument (whose content is added as a subgraph). + +> **Note:** If the name of a subgraph begins with 'cluster' (all lowercase) the layout engine will treat it as a special cluster subgraph (example). Also see the Subgraphs and Clusters section of [the DOT](https://www.graphviz.org/doc/info/lang.html) language documentation. + +```js +// Create new graph +let g = new Graph("Subgraph Example"); + +// Create parent nodes +let start = g.addNode("start", {"shape": "Mdiamond"}); +let end = g.addNode("end", {"shape": "Msquare"}); +let a0 = g.addNode("a0"); +let a1 = g.addNode("a1"); +let a2 = g.addNode("a2"); +let a3 = g.addNode("a3"); +let b0 = g.addNode("b0"); +let b1 = g.addNode("b1"); +let b2 = g.addNode("b2"); +let b3 = g.addNode("b3"); + +// Create edges +g.addEdge(start, a0); +g.addEdge(start, b0); +g.addEdge(a1, b3); +g.addEdge(b2, a3); +g.addEdge(a3, a0); +g.addEdge(a3, end); +g.addEdge(b3, end); + +// Create graph and set nodes and edges +let c0 = new Graph("cluster0"); +c0.set({"style":"filled", "color":"lightgrey", "label": "process #1"}); +c0.setNodesAttributes({"style":"filled", "color":"white"}); +c0.addEdge(a0, a1); +c0.addEdge(a1, a2); +c0.addEdge(a2, a3); + +// Create graph and set nodes and edges +let c1 = new Graph("cluster1"); +c1.set({"color":"blue", "label":"process #2"}); +c1.setNodesAttributes({"style":"filled"}); +c1.addEdge(b0, b1); +c1.addEdge(b1, b2); +c1.addEdge(b2, b3); + +// Associate subgraphs +g.addSubgraph(c0); +g.addSubgraph(c1); + +// Print dot file +console.log(g.toDot()); + +// Dot sytanx that gets generated... +digraph "Subgraph Example" { + subgraph "cluster0" { + graph [style="filled", color="lightgrey", label="process #1"]; + node [style="filled", color="white"]; + "a0" -> "a1"; + "a1" -> "a2"; + "a2" -> "a3"; + } + subgraph "cluster1" { + graph [color="blue", label="process #2"]; + node [style="filled"]; + "b0" -> "b1"; + "b1" -> "b2"; + "b2" -> "b3"; + } + "start" [shape=Mdiamond]; + "end" [shape=Msquare]; + "a0"; + "a1"; + "a2"; + "a3"; + "b0"; + "b1"; + "b2"; + "b3"; + "start" -> "a0"; + "start" -> "b0"; + "a1" -> "b3"; + "b2" -> "a3"; + "a3" -> "a0"; + "a3" -> "end"; + "b3" -> "end"; +} +``` + +![Clusters](./.assets/clusters.svg) + ## Attribution This library was heavily inspired by https://github.com/glejeune/node-graphviz. Code was rewritten to support a stripped down version for specific use-cases. For a more complete set of features and support of graphviz, please consider using glejeune's implementation (Undirected graphs, clusters, subgraphs, etc.). diff --git a/samples/attributes.js b/examples/attributes.js similarity index 100% rename from samples/attributes.js rename to examples/attributes.js diff --git a/samples/basic.js b/examples/basic.js similarity index 100% rename from samples/basic.js rename to examples/basic.js diff --git a/examples/clusters.js b/examples/clusters.js new file mode 100644 index 0000000..52a75bc --- /dev/null +++ b/examples/clusters.js @@ -0,0 +1,49 @@ +let Graph = require('../index'); + +// Create new graph +let g = new Graph("Subgraph Example"); + +// Create parent nodes +let start = g.addNode("start", {"shape": "Mdiamond"}); +let end = g.addNode("end", {"shape": "Msquare"}); +let a0 = g.addNode("a0"); +let a1 = g.addNode("a1"); +let a2 = g.addNode("a2"); +let a3 = g.addNode("a3"); +let b0 = g.addNode("b0"); +let b1 = g.addNode("b1"); +let b2 = g.addNode("b2"); +let b3 = g.addNode("b3"); + +// Create edges +g.addEdge(start, a0); +g.addEdge(start, b0); +g.addEdge(a1, b3); +g.addEdge(b2, a3); +g.addEdge(a3, a0); +g.addEdge(a3, end); +g.addEdge(b3, end); + +// Create graph and set nodes and edges +let c0 = new Graph("cluster0"); +c0.set({"style":"filled", "color":"lightgrey", "label": "process #1"}); +c0.setNodesAttributes({"style":"filled", "color":"white"}); +c0.addEdge(a0, a1); +c0.addEdge(a1, a2); +c0.addEdge(a2, a3); + +// Create graph and set nodes and edges +let c1 = new Graph("cluster1"); +c1.set({"color":"blue", "label":"process #2"}); +c1.setNodesAttributes({"style":"filled"}); +c1.addEdge(b0, b1); +c1.addEdge(b1, b2); +c1.addEdge(b2, b3); + +// Associate subgraphs +g.addSubgraph(c0); +g.addSubgraph(c1); + +// Print dot file +let dotScript = g.toDot(); +console.log(dotScript); \ No newline at end of file diff --git a/samples/html.js b/examples/html.js similarity index 100% rename from samples/html.js rename to examples/html.js diff --git a/index.js b/index.js index d53e398..5b723cc 100644 --- a/index.js +++ b/index.js @@ -1,2 +1,2 @@ // Ref: https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf -module.exports = require("./lib/graph"); +module.exports = require("./lib/graph"); \ No newline at end of file diff --git a/lib/attributes.js b/lib/attributes.js index afa174a..9096733 100644 --- a/lib/attributes.js +++ b/lib/attributes.js @@ -182,7 +182,8 @@ let quotedTypes = [ "splineType", "style", "viewPort", - "fillcolor" + "fillcolor", + "label" ]; /** diff --git a/lib/graph.js b/lib/graph.js index 06293c7..b74d874 100644 --- a/lib/graph.js +++ b/lib/graph.js @@ -16,8 +16,10 @@ let fs = require("fs"); */ class Graph { constructor(id) { + this._relativeGraph = null; this._id = id || ""; this._type = "digraph"; + this._clusters = {}; this._nodes = {}; this._htmlNodes = {}; this._edges = []; @@ -26,9 +28,31 @@ class Graph { this._edgesAttributes = new Attributes("E"); } + /** + * Add a subgraph to current graph + * @param {Object} graph The graph to add as subgraph/cluster + * @return {Graph} + * + * @example + * let g = new Graph("parent"); + * let c = new Graph("cluster0"); + * + * g.setSubgraph(c); + */ + addSubgraph(graph) { + if (!graph._type === this._type) { + console.error("Cannot add subgraph of different type..."); + throw "Cannot add subgraph of different type..."; + } + + this._clusters[graph._id] = graph; + graph._relativeGraph = this; + + return this._clusters[graph._id]; + } + /** * Create a new node - * * @param {String} id The node ID * @param {Object} [attributes] Node attributes * @return {Node} @@ -266,48 +290,62 @@ class Graph { */ toDot() { let dotScript = ""; + let spacer = ""; - if (this._id) { + if (!this._relativeGraph) { + spacer = " "; dotScript = this._type + ' "' + this._id + '" {\n'; } else { - dotScript = this._type + " {\n"; + spacer = " "; + dotScript = ' subgraph' + ' "' + this._id + '" {\n'; } // Graph attributes if (this._graphAttributes.length() > 0) { - dotScript = dotScript + " graph" + this._graphAttributes.toDot() + ";\n"; + dotScript = dotScript + spacer + "graph" + this._graphAttributes.toDot() + ";\n"; } // Node attributes if (this._nodesAttributes.length() > 0) { - dotScript = dotScript + " node" + this._nodesAttributes.toDot() + ";\n"; + dotScript = dotScript + spacer + "node" + this._nodesAttributes.toDot() + ";\n"; } // Edge attributes if (this._edgesAttributes.length() > 0) { - dotScript = dotScript + " edge" + this._edgesAttributes.toDot() + ";\n"; + dotScript = dotScript + spacer + "edge" + this._edgesAttributes.toDot() + ";\n"; + } + + // Each cluster + for (let [key] of Object.entries(this._clusters)) { + if (this._clusters.hasOwnProperty(key)) { + dotScript = dotScript + this._clusters[key].toDot() + "\n"; + } } // Each node for (let [key] of Object.entries(this._nodes)) { if (this._nodes.hasOwnProperty(key)) { - dotScript = dotScript + " " + this._nodes[key].toDot() + ";\n"; + dotScript = dotScript + spacer + this._nodes[key].toDot() + ";\n"; } } // Each HTML node for (let [key] of Object.entries(this._htmlNodes)) { if (this._htmlNodes.hasOwnProperty(key)) { - dotScript = dotScript + " " + this._htmlNodes[key].toDot() + ";\n"; + dotScript = dotScript + spacer + this._htmlNodes[key].toDot() + ";\n"; } } // Each edge for (let i = 0; i < this._edges.length; i++) { - dotScript = dotScript + " " + this._edges[i].toDot() + ";\n"; + dotScript = dotScript + spacer + this._edges[i].toDot() + ";\n"; } - dotScript = dotScript + "}\n"; + if (this._relativeGraph) { + dotScript += " }"; + } else { + dotScript += "}\n"; + } return dotScript; } @@ -338,4 +376,4 @@ class Graph { } } -module.exports = Graph; +module.exports = Graph; \ No newline at end of file diff --git a/package.json b/package.json index a97ddfc..45b5a9c 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,10 @@ "name": "graphviz-node", "version": "0.6.0", "description": "Node.js wrapper for Graphviz.", - "keywords": ["node", "graphviz"], + "keywords": [ + "node", + "graphviz" + ], "main": "index.js", "homepage": "https://github.com/540co/graphviz-node", "repository": { From e26b3af160357d2a897cf2b63469eb1f997ce8ef Mon Sep 17 00:00:00 2001 From: Mark Ostrander Date: Mon, 11 Nov 2019 07:20:11 -0500 Subject: [PATCH 2/2] Update README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 2f8483b..9e97c2a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ A simple node wrapper for [Graphviz](http://www.graphviz.org/). -> **NOTE:** This is a stripped down node implementation of [Graphviz](http://www.graphviz.org/). It does not support support undirected graph objects, clusters or subgraphs. For more bugs/issues or additional features please submit an issue. +> **NOTE:** This is a stripped down node implementation of [Graphviz](http://www.graphviz.org/). It does not support support undirected graph objects. For more bugs/issues or additional features please submit an issue. ## Documentation @@ -259,7 +259,7 @@ digraph "html" { ## Subgraphs & Clusters -Graph and Digraph objects have an `addSubgraph() method for adding a subgraph to a graph.This is done by creating a ready-made graph object of the same kind as the only argument (whose content is added as a subgraph). +Graph and Digraph objects have an `addSubgraph()` method for adding a subgraph to a graph.This is done by creating a ready-made graph object of the same kind as the only argument (whose content is added as a subgraph). > **Note:** If the name of a subgraph begins with 'cluster' (all lowercase) the layout engine will treat it as a special cluster subgraph (example). Also see the Subgraphs and Clusters section of [the DOT](https://www.graphviz.org/doc/info/lang.html) language documentation. @@ -351,4 +351,4 @@ digraph "Subgraph Example" { ## Attribution -This library was heavily inspired by https://github.com/glejeune/node-graphviz. Code was rewritten to support a stripped down version for specific use-cases. For a more complete set of features and support of graphviz, please consider using glejeune's implementation (Undirected graphs, clusters, subgraphs, etc.). +This library was heavily inspired by https://github.com/glejeune/node-graphviz. Code was rewritten to support a stripped a specific use-case. For a more complete set of features and support of graphviz, please consider using glejeune's implementation.