IBM Connections Sentiment D3 Tree

Based on Abiwax's Twitter Sentiments D3 Display but converted to use IBM Connections and delete existing data before running each time.

This sample application demonstrates the use of sentiment analysis on IBM Connections status update data and displays it on a d3 collapsible tree. To use: Connect a CloudantDB service to your node-red application, double click on the cloudant node and select the created service, set your database name. Connect your IBM Connections account to your Connections node.

In your browser, run /connectionssentiments in your browser for results.

[{"id":"a0bd560d.2675a8","type":"http in","z":"7548b0c.9cdead","name":"","url":"/connectionssentiments","method":"get","swaggerDoc":"","x":1253.3334503173828,"y":1134.999873161316,"wires":[["f73045aa.17c578"]]},{"id":"6ca858de.86cfe8","type":"http response","z":"7548b0c.9cdead","name":"","x":1878.8334884643555,"y":1137.9999237060547,"wires":[]},{"id":"14ef817.ce858ff","type":"cloudant out","z":"7548b0c.9cdead","name":"","cloudant":"","database":"analysis","service":"_ext_","payonly":false,"operation":"insert","x":1700.1667251586914,"y":914.3332967758179,"wires":[]},{"id":"1a33112.d876f6f","type":"sentiment","z":"7548b0c.9cdead","name":"","x":1463.833381652832,"y":914.999963760376,"wires":[["14ef817.ce858ff"]]},{"id":"769e071.e3a1af8","type":"template","z":"7548b0c.9cdead","name":"HTML","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Title</title>\n    <style>\n        {{{payload.style}}}\n    </style>\n    <script src=\"https://d3js.org/d3.v3.min.js\"></script>\n\n    <script src=\"https://ajax.googleapis.com/ajax/libs/jquery/3.2.0/jquery.min.js\"></script>\n    <!-- Latest compiled and minified CSS -->\n    <!-- <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\" integrity=\"sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u\" crossorigin=\"anonymous\"> -->\n    <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css\">\n    \n    <!-- Optional theme -->\n    <!-- <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css\" integrity=\"sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp\" crossorigin=\"anonymous\"> -->\n    <link rel=\"stylesheet\" href=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css\">\n    \n    <!-- Latest compiled and minified JavaScript -->\n    <!-- <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\" integrity=\"sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa\" crossorigin=\"anonymous\"></script> -->\n    <script src=\"https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js\"></script>\n</head>\n<body>\n    <header class=\"text-center\">\n        <h1>IBM Connections Sentiments</h1>\n    </header>\n<div class=\"row\">\n<div class=\"col-md-12\">\n    <p id=\"test\" style=\"display: none;\">{{{payload.final_result}}}</p>\n<script>\nvar url  = window.location.host\n{{{ payload.script }}}\nd3Function()\n</script>\n</div>\n</div>\n</body>\n</html>","x":1739.8334884643555,"y":1136.9999237060547,"wires":[["6ca858de.86cfe8"]]},{"id":"c7531a6f.10fa8","type":"template","z":"7548b0c.9cdead","name":"CSS","field":"payload.style","fieldType":"msg","format":"css","syntax":"mustache","template":".node {\n    cursor: pointer;\n}\n\n.node circle {\n    fill: #fff;\n    stroke: steelblue;\n    stroke-width: 1.5px;\n}\n\n.node text {\n    font: 10px sans-serif;\n}\n\n.link {\n    fill: none;\n    stroke: #ccc;\n    stroke-width: 1.5px;\n}\n\nheader{\n    background-color: darkgreen;\n    color: white;\n    min-height: 100px;\n    padding-top: 20px;\n}\n","x":1603.6906051635742,"y":1134.2856369018555,"wires":[["769e071.e3a1af8"]]},{"id":"f73045aa.17c578","type":"template","z":"7548b0c.9cdead","name":"JS","field":"payload.script","fieldType":"msg","format":"javascript","syntax":"mustache","template":"var d3Function = function d3Function() {\n\n        var margin = {top: 20, right: 120, bottom: 20, left: 120},\n            width = 960 - margin.right - margin.left,\n            height = 800 - margin.top - margin.bottom;\n\n        var i = 0,\n            duration = 750,\n            root;\n\n        var tree = d3.layout.tree()\n            .size([height, width]);\n\n        var diagonal = d3.svg.diagonal()\n            .projection(function (d) {\n                return [d.y, d.x];\n            });\n\n        var svg = d3.select(\"body\").append(\"svg\")\n            .attr(\"width\", width + margin.right + margin.left)\n            .attr(\"height\", height + margin.top + margin.bottom)\n            .append(\"g\")\n            .attr(\"transform\", \"translate(\" + margin.left + \",\" + margin.top + \")\");\n        \n        d3.json(\"http://\"+url+\"/sentiments\", function(error, flare) {\n            if (error) throw error;\n\n            root = flare;\n            root.x0 = height / 2;\n            root.y0 = 0;\n\n            function collapse(d) {\n                if (d.children) {\n                    d._children = d.children;\n                    d._children.forEach(collapse);\n                    d.children = null;\n                }\n            }\n\n            root.children.forEach(collapse);\n            update(root);\n        });\n        \n        d3.select(self.frameElement).style(\"height\", \"800px\");\n\n        function update(source) {\n\n            // Compute the new tree layout.\n            var nodes = tree.nodes(root).reverse(),\n                links = tree.links(nodes);\n\n            // Normalize for fixed-depth.\n            nodes.forEach(function(d) { d.y = d.depth * 180; });\n\n            // Update the nodes…\n            var node = svg.selectAll(\"g.node\")\n                .data(nodes, function(d) { return d.id || (d.id = ++i); });\n\n            // Enter any new nodes at the parent's previous position.\n            var nodeEnter = node.enter().append(\"g\")\n                .attr(\"class\", \"node\")\n                .attr(\"transform\", function(d) { return \"translate(\" + source.y0 + \",\" + source.x0 + \")\"; })\n                .on(\"click\", click);\n\n            nodeEnter.append(\"circle\")\n                .attr(\"r\", 6)\n                .style(\"fill\", function(d) { return d._children ? \"lightsteelblue\" : \"#fff\"; });\n\n            nodeEnter.append(\"text\")\n                .attr(\"x\", function(d) { return d.children || d._children ? -14 : 14; })\n                .attr(\"dy\", \".35em\")\n                .attr(\"text-anchor\", function(d) { return d.children || d._children ? \"end\" : \"start\"; })\n                .text(function(d) { return d.name; })\n                .style(\"fill-opacity\", 1e-6);\n\n            // Transition nodes to their new position.\n            var nodeUpdate = node.transition()\n                .duration(duration)\n                .attr(\"transform\", function(d) { return \"translate(\" + d.y + \",\" + d.x + \")\"; });\n\n            nodeUpdate.select(\"circle\")\n                .attr(\"r\", 10)\n                .style(\"fill\", function(d) { return d._children ? \"lightsteelblue\" : \"#fff\"; });\n\n            nodeUpdate.select(\"text\")\n                .style(\"fill-opacity\", 1);\n\n            // Transition exiting nodes to the parent's new position.\n            var nodeExit = node.exit().transition()\n                .duration(duration)\n                .attr(\"transform\", function(d) { return \"translate(\" + source.y + \",\" + source.x + \")\"; })\n                .remove();\n\n            nodeExit.select(\"circle\")\n                .attr(\"r\", 6);\n\n            nodeExit.select(\"text\")\n                .style(\"fill-opacity\", 1e-6);\n\n            // Update the links…\n            var link = svg.selectAll(\"path.link\")\n                .data(links, function(d) { return d.target.id; });\n\n            // Enter any new links at the parent's previous position.\n            link.enter().insert(\"path\", \"g\")\n                .attr(\"class\", \"link\")\n                .attr(\"d\", function(d) {\n                    var o = {x: source.x0, y: source.y0};\n                    return diagonal({source: o, target: o});\n                });\n\n            // Transition links to their new position.\n            link.transition()\n                .duration(duration)\n                .attr(\"d\", diagonal);\n\n            // Transition exiting nodes to the parent's new position.\n            link.exit().transition()\n                .duration(duration)\n                .attr(\"d\", function(d) {\n                    var o = {x: source.x, y: source.y};\n                    return diagonal({source: o, target: o});\n                })\n                .remove();\n\n            // Stash the old positions for transition.\n            nodes.forEach(function(d) {\n                d.x0 = d.x;\n                d.y0 = d.y;\n            });\n        }\n\n\n        // Toggle children on click.\n        function click(d) {\n            if (d.children) {\n                d._children = d.children;\n                d.children = null;\n            } else {\n                d.children = d._children;\n                d._children = null;\n            }\n            update(d);\n        }\n};\n","x":1450.5477828979492,"y":1134.5713500976562,"wires":[["c7531a6f.10fa8"]]},{"id":"bddd8121.be70b8","type":"http in","z":"7548b0c.9cdead","name":"","url":"/sentiments","method":"get","swaggerDoc":"","x":1216.0951843261719,"y":1195.7618465423584,"wires":[["b4ca792a.9b4e3"]]},{"id":"b4ca792a.9b4e3","type":"cloudant in","z":"7548b0c.9cdead","name":"","cloudant":"","database":"analysis","service":"_ext_","search":"_all_","design":"","index":"","x":1488.5000228881836,"y":1195.5713653564453,"wires":[["c083f71.55a7388"]]},{"id":"c083f71.55a7388","type":"function","z":"7548b0c.9cdead","name":"Convert","func":"var new_result = msg.payload;\nvar final_result = {};\nvar positive_sentiment = {};\nvar negative_sentiment = {};\npositive_sentiment[\"name\"] = \"Positive\";\nnegative_sentiment[\"name\"] = \"Negative\";\npositive_sentiment[\"children\"] = [];\nnegative_sentiment[\"children\"] = [];\nfor (var i in new_result) {\n    p_sentiment = new_result[i][\"sentiment\"][\"positive\"];\n    n_sentiment = new_result[i][\"sentiment\"][\"negative\"];\n\n    if (p_sentiment.length > 0){\n        user_sent_list = [];\n        user_sent = {};\n        user_sent[\"name\"] = new_result[i][\"originator\"];\n        user_sent[\"children\"] = [];\n        for (var value in p_sentiment) {\n            children = {};\n            children[\"name\"] = p_sentiment[value];\n            user_sent[\"children\"].push(children)\n        }\n        positive_sentiment[\"children\"].push(user_sent)\n    }\n\n\n    if (n_sentiment.length > 0) {\n        user_sent_list = [];\n        user_sent = {};\n        user_sent[\"name\"] = new_result[i][\"originator\"];\n        user_sent[\"children\"] = [];\n        for (value in n_sentiment) {\n            children = {};\n            children[\"name\"] = n_sentiment[value];\n            user_sent[\"children\"].push(children)\n        }\n        negative_sentiment[\"children\"].push(user_sent)\n    }\n}\n\nfinal_result[\"name\"] = \"Sentiments\";\nfinal_result[\"children\"] = [];\nfinal_result[\"children\"].push(positive_sentiment);\nfinal_result[\"children\"].push(negative_sentiment);\n\nmsg.payload = final_result;\n\nreturn msg;","outputs":1,"noerr":0,"x":1680.3572311401367,"y":1195.8571014404297,"wires":[["c1a3f8f5.74ab"]]},{"id":"c1a3f8f5.74ab","type":"http response","z":"7548b0c.9cdead","name":"","x":1878.7382316589355,"y":1194.9045886993408,"wires":[]},{"id":"82817342.68b3e","type":"debug","z":"7548b0c.9cdead","name":"","active":true,"console":"true","complete":"true","x":1285.8332920074463,"y":961.6667966842651,"wires":[]},{"id":"3db974b5.67cf24","type":"inject","z":"7548b0c.9cdead","name":"","topic":"","payload":"\"tim minter\"","payloadType":"str","repeat":"","crontab":"","once":false,"x":221.66669464111328,"y":693.3334426879883,"wires":[["212a36c9.e54832","2bdf652d.55db52"]]},{"id":"1a1bf151.56d7bf","type":"function","z":"7548b0c.9cdead","name":"Translate msg.payload to msg.query","func":"msg.query = msg.payload;\nreturn msg;","outputs":1,"noerr":0,"x":545.1666488647461,"y":785.3333892822266,"wires":[["7663f502.0008ec"]]},{"id":"7663f502.0008ec","type":"SimpleSearch","z":"7548b0c.9cdead","name":"Search All Status Updates","server":"","query":"","theScope":"status_updates","sortKey":"date","sortOrder":"desc","maxResults":"50","limitCB":true,"sinceDate":"07/08/2016","sinceCB":false,"untilDate":"06/04/2017","untilCB":false,"mytags":"","x":890.1666450500488,"y":792.0000581741333,"wires":[["1ad6e059.0c13b8"]]},{"id":"1ad6e059.0c13b8","type":"splitter","z":"7548b0c.9cdead","name":"","property":"payload","x":1100.9166679382324,"y":792.0834093093872,"wires":[["25b89295.d7e32e"]]},{"id":"25b89295.d7e32e","type":"function","z":"7548b0c.9cdead","name":"Get Title","func":"msg.originator = msg.payload.name;\nmsg.payload = msg.payload.title;\nreturn msg;","outputs":1,"noerr":0,"x":1107.3333473205566,"y":916.5835428237915,"wires":[["1a33112.d876f6f","82817342.68b3e"]]},{"id":"2bdf652d.55db52","type":"cloudant in","z":"7548b0c.9cdead","name":"Retrieve documents","cloudant":"","database":"analysis","service":"_ext_","search":"_all_","design":"","index":"","x":563.2166519165039,"y":556.3276081085205,"wires":[["af3d58a1.88e7f8"]]},{"id":"c160e7e7.cff9c8","type":"cloudant out","z":"7548b0c.9cdead","name":"Remove all documents ","cloudant":"","database":"analysis","service":"_ext_","payonly":false,"operation":"delete","x":1347.6666641235352,"y":557.8665385246277,"wires":[]},{"id":"af3d58a1.88e7f8","type":"split","z":"7548b0c.9cdead","name":"split out individual documents","splt":"\\n","x":836.6665420532227,"y":556.8832483291626,"wires":[["549d6fbe.9c8fd"]]},{"id":"1cc6214.d7ba7df","type":"debug","z":"7548b0c.9cdead","name":"id and rev to delete","active":true,"console":"false","complete":"payload","x":1341.6666412353516,"y":616.5665950775146,"wires":[]},{"id":"549d6fbe.9c8fd","type":"function","z":"7548b0c.9cdead","name":"Extract id and rev","func":"\nvar document = {_id: msg.payload._id, _rev: msg.payload._rev};\n\nmsg.payload = document;\n\nreturn msg;","outputs":1,"noerr":0,"x":1091.3332748413086,"y":556.7832469940186,"wires":[["c160e7e7.cff9c8","1cc6214.d7ba7df"]]},{"id":"212a36c9.e54832","type":"delay","z":"7548b0c.9cdead","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"x":415.8333206176758,"y":693.3333568572998,"wires":[["1a1bf151.56d7bf"]]},{"id":"ac77ca90.2a9098","type":"comment","z":"7548b0c.9cdead","name":"Remove this part if you DO NOT want to remove all results before getting new results","info":"","x":755.8333129882812,"y":509.9999842643738,"wires":[]},{"id":"afe9f324.ab0ea","type":"comment","z":"7548b0c.9cdead","name":"Note: we use a simple delay to get existing results deleted before getting new results","info":"Really we need to wait until the delete completes to avoid issues.\n completes","x":685.8333435058594,"y":651.6666717529297,"wires":[]},{"id":"3c9ba728.5cf3e","type":"comment","z":"7548b0c.9cdead","name":"Our web interface","info":"","x":1219.1666793823242,"y":1093.333293914795,"wires":[]}]
tim-minter

Flow Info

created 6 months, 3 weeks ago

Node Types

Core
  • comment (x3)
  • debug (x2)
  • delay (x1)
  • function (x4)
  • http in (x2)
  • http response (x2)
  • inject (x1)
  • sentiment (x1)
  • template (x3)
Other

Tags

  • IBMConnections
  • Connections
  • IBM
  • sentiment
  • D3
Copy this flow JSON to your clipboard and then import into Node-RED using the Import From > Clipboard (Ctrl-I) menu option