How to display Tree diagram at dashboard

#1

Hi is there any package there for display Tree diagram at dashboard
some thing like this
each node get : color, name, parents

tree%20diagram

thanks

#2

Answer in stackoverflow: https://stackoverflow.com/questions/23864115/how-to-recursively-toggle-nodes-in-a-d3-js-tree-diagram

#3

so, I got the tree as node template (will modify it's input to be dynamic later)

tree.txt (6.7 KB)

but it is out of my "map" group
how do I change it?

Alos, how can I modify the layout so I can do map under discovery,
centered and not the auto way it is done?

thakns

#4

Modify the line that appends the SVG element to use the id of your TAB/group. For instance: tab name = D3 and group name = G1, it will be:

var svg = d3.select("#D3_G1").append("svg")

Result:

Looks like in you case it should be:

var svg = d3.select("#Layout_map").append("svg")
1 Like
#5

Love to see this when you get dynamic data added!

#6

Very odd side issue. i put your flow on a tab and it showed up fine on the 'Map' tab. I then added a pie chart on the same tab, but pointing to another UI tab and the Tree shows up on both the 'Map' and the tab (Home in this case) the chart is assigned to. Not only that but it has depth to the lines

@Andrel any thoughts?

#7

Hi @zenofmud, did you modify this line of code from the original code ?

var svg = d3.select("body").append("svg")

How it look like now for you ?

#8

Paul, you need to turn off the css "fill" styling for your <path> elements -- check the Dev Console, and you can see the inherited style that needs to be overridden.

You can see the same effect in the editor links when i turn off the fill: none; style...
image

#9

@andrel

when I change that to:
var svg = d3.select("#Layout_map").append("svg")
nothing shows

@shrickus - it looks fine on the original tab, it just when I went to the tab with the pie chart that I saw that image.

All I've dont is used the flow that @natanel provided with no changes other than putting it in a flow-tab that has a pie chart. Here is the flow(s) showing the issue:

[{"id":"8d9e58f5.85f7c","type":"ui_chart","z":"120f82c.f796cfd","name":"","group":"e22ca977.f549b","order":0,"width":0,"height":0,"label":"chart","chartType":"pie","legend":"false","xformat":"HH:mm:ss","interpolate":"linear","nodata":"","dot":false,"ymin":"","ymax":"","removeOlder":1,"removeOlderPoints":"","removeOlderUnit":"3600","cutout":0,"useOneColor":false,"colors":["#1F77B4","#AEC7E8","#FF7F0E","#2CA02C","#98DF8A","#D62728","#FF9896","#9467BD","#C5B0D5"],"useOldStyle":false,"x":550,"y":260,"wires":[[],[]]},{"id":"fa234288.e1773","type":"inject","z":"120f82c.f796cfd","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":100,"y":260,"wires":[["f6913e72.3b51c","d4bb304a.4f50f8","9aefcaac.f2028"]]},{"id":"f6913e72.3b51c","type":"change","z":"120f82c.f796cfd","name":"Series 1 - 15","rules":[{"t":"set","p":"payload","pt":"msg","to":"15","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 1","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":200,"wires":[["8d9e58f5.85f7c"]]},{"id":"d4bb304a.4f50f8","type":"change","z":"120f82c.f796cfd","name":"Series 2 - 10","rules":[{"t":"set","p":"payload","pt":"msg","to":"10","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 2","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":240,"wires":[["8d9e58f5.85f7c"]]},{"id":"9aefcaac.f2028","type":"change","z":"120f82c.f796cfd","name":"Series 3 - 12","rules":[{"t":"set","p":"payload","pt":"msg","to":"12","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 3","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":280,"wires":[["8d9e58f5.85f7c"]]},{"id":"e9fa2773.b23058","type":"change","z":"120f82c.f796cfd","name":"Series 4 - 13","rules":[{"t":"set","p":"payload","pt":"msg","to":"13","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 4","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":320,"wires":[["8d9e58f5.85f7c"]]},{"id":"434e660f.1c7248","type":"change","z":"120f82c.f796cfd","name":"Series 5 - 8","rules":[{"t":"set","p":"payload","pt":"msg","to":"8","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 4","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":360,"wires":[["8d9e58f5.85f7c"]]},{"id":"2c92a40.d6fb95c","type":"change","z":"120f82c.f796cfd","name":"Series 6 - 17","rules":[{"t":"set","p":"payload","pt":"msg","to":"17","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 8","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":400,"wires":[["8d9e58f5.85f7c"]]},{"id":"b291f4d9.9d0738","type":"change","z":"120f82c.f796cfd","name":"Series 7 - 23","rules":[{"t":"set","p":"payload","pt":"msg","to":"23","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 7","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":440,"wires":[["8d9e58f5.85f7c"]]},{"id":"906fafb9.5c1c78","type":"change","z":"120f82c.f796cfd","name":"Series 8 - 1","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 8","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":480,"wires":[["8d9e58f5.85f7c"]]},{"id":"b71e5fbc.ddacd","type":"change","z":"120f82c.f796cfd","name":"Series9 - 1","rules":[{"t":"set","p":"payload","pt":"msg","to":"1","tot":"num"},{"t":"set","p":"topic","pt":"msg","to":"series 9","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":310,"y":520,"wires":[["8d9e58f5.85f7c"]]},{"id":"5109e224.555dac","type":"ui_template","z":"120f82c.f796cfd","group":"4d52119.21ac4f","name":"map matrix","order":0,"width":"0","height":"0","format":"<style>\t\n\t.node {\n\t\tcursor: pointer;\n\t}\n\n\t.node circle {\n\t  fill: #fff;\n\t  stroke: steelblue;\n\t  stroke-width: 3px;\n\t}\n\n\t.node text {\n\t  font: 12px sans-serif;\n\t  fill: #fff;\n\t}\n\n\t.link {\n\t  fill: none;\n\t  stroke: #ccc;\n\t  stroke-width: 2px;\n\t}\n</style>\n\t\n<script>\nvar treeData = [\n  {\n    \"name\": \"Top Level\",\n    \"parent\": \"null\",\n    \"status\": \"red\",\n    \"children\": [\n      {\n        \"name\": \"Level 2-A\",\n        \"parent\": \"Top Level\",\n        \"status\": \"red\",\n        \"children\": [\n          {\n            \"name\": \"Legolas\",\n            \"status\": \"red\",\n            \"parent\": \"Level 2-A\"\n          },\n          {\n            \"name\": \"Finegal\",\n            \"status\": \"green\",\n            \"parent\": \"Level 2-A\"\n          }\n        ]\n      },\n      {\n        \"name\": \"Level 2-B\",\n        \"status\": \"red\",\n        \"parent\": \"Top Level\",\n        \"children\": [\n          {\n            \"name\": \"Durin\",\n            \"status\": \"red\",\n            \"parent\": \"Level 2-B\",\n\t\t\t\"children\": [\n\t\t\t  {\n\t\t\t\t\"name\": \"Frodo\",\n\t\t\t\t\"status\": \"green\",\n\t\t\t\t\"parent\": \"Durin\"\n\t\t\t  },\n\t\t\t  {\n\t\t\t\t\"name\": \"Bilbo\",\n\t\t\t\t\"status\": \"red\",\n\t\t\t\t\"parent\": \"Durin\"\n\t\t\t  }\n\t\t\t]            \n          },\n          {\n            \"name\": \"Balin\",\n            \"status\": \"green\",\n            \"parent\": \"Level 2-B\",\n\t\t\t\"children\": [\n\t\t\t  {\n\t\t\t\t\"name\": \"Merry\",\n\t\t\t\t\"status\": \"green\",\n\t\t\t\t\"parent\": \"Balin\"\n\t\t\t  },\n\t\t\t  {\n\t\t\t\t\"name\": \"Pipen\",\n\t\t\t\t\"status\": \"green\",\n\t\t\t\t\"parent\": \"Balin\"\n\t\t\t  }\n\t\t\t]            \n          }\n        ]\n      },\n      {\n        \"name\": \"Level 2-C\",\n        \"parent\": \"Top Level\",\n        \"status\": \"green\",\n        \"children\": [\n          {\n            \"name\": \"Gandalf\",\n            \"status\": \"green\",\n            \"parent\": \"Level 2-C\"\n          },\n          {\n            \"name\": \"Sauruman\",\n            \"status\": \"green\",\n            \"parent\": \"Level 2-C\"\n          }\n        ]\n      },\n    ]\n  }\n];\n\n\n// ************** Generate the tree diagram\t *****************\nvar margin = {top: 20, right: 120, bottom: 20, left: 120},\n\twidth = 960 - margin.right - margin.left,\n\theight = 500 - margin.top - margin.bottom;\n\t\nvar i = 0,\n\tduration = 750,\n\troot;\n\nvar tree = d3.layout.tree()\n\t.size([height, width]);\n\nvar diagonal = d3.svg.diagonal()\n\t.projection(function(d) { return [d.y, d.x]; });\n\nvar svg = d3.select(\"body\").append(\"svg\")\n\t.attr(\"width\", width + margin.right + margin.left)\n\t.attr(\"height\", height + margin.top + margin.bottom)\n  .append(\"g\")\n\t.attr(\"transform\", \"translate(\" + margin.left + \",\" \n\t                                + margin.top + \")\");\n\nroot = treeData[0];\nroot.x0 = height / 2;\nroot.y0 = 0;\n\nfunction toggleAll(d) {\n  if (d.children) {\n    if (d.status == \"green\") {\n      d._children = d.children;\n      d._children.forEach(toggleAll);\n      d.children = null;\n    }\n  }\n}\n\nroot.children.forEach(toggleAll);\n  \nupdate(root);\n\nd3.select(self.frameElement).style(\"height\", \"500px\");\n\nfunction update(source) {\n\n  // Compute the new tree layout.\n  var nodes = tree.nodes(root).reverse(),\n\t  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\t  .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\t  .attr(\"class\", \"node\")\n\t  .attr(\"transform\", function(d) { \n\t\t  return \"translate(\" + source.y0 + \",\" + source.x0 + \")\"; })\n\t  .on(\"click\", click);\n\n  nodeEnter.append(\"circle\")\n\t  .attr(\"r\", 1e-6)\n\t  .style(\"fill\", function(d) { return d.status; });\n\n  nodeEnter.append(\"text\")\n\t  .attr(\"x\", function(d) { \n\t\t  return d.children || d._children ? -13 : 13; })\n\t  .attr(\"dy\", \".35em\")\n\t  .attr(\"text-anchor\", function(d) { \n\t\t  return d.children || d._children ? \"end\" : \"start\"; })\n\t  .text(function(d) { return d.name; })\n\t  .style(\"fill-opacity\", 1e-6);\n\n  // Transition nodes to their new position.\n  var nodeUpdate = node.transition()\n\t  .duration(duration)\n\t  .attr(\"transform\", function(d) { \n\t\t  return \"translate(\" + d.y + \",\" + d.x + \")\"; });\n\n  nodeUpdate.select(\"circle\")\n\t  .attr(\"r\", 10)\n\t  .style(\"fill\", function(d) { return d.status; });\n\n  nodeUpdate.select(\"text\")\n\t  .style(\"fill-opacity\", 1);\n\n  // Transition exiting nodes to the parent's new position.\n  var nodeExit = node.exit().transition()\n\t  .duration(duration)\n\t  .attr(\"transform\", function(d) { \n\t\t  return \"translate(\" + source.y + \",\" + source.x + \")\"; })\n\t  .remove();\n\n  nodeExit.select(\"circle\")\n\t  .attr(\"r\", 1e-6);\n\n  nodeExit.select(\"text\")\n\t  .style(\"fill-opacity\", 1e-6);\n\n  // Update the linksĂ–\n  var link = svg.selectAll(\"path.link\")\n\t  .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\t  .attr(\"class\", \"link\")\n\t  .attr(\"d\", function(d) {\n\t\tvar o = {x: source.x0, y: source.y0};\n\t\treturn diagonal({source: o, target: o});\n\t  });\n\n  // Transition links to their new position.\n  link.transition()\n\t  .duration(duration)\n\t  .attr(\"d\", diagonal);\n\n  // Transition exiting nodes to the parent's new position.\n  link.exit().transition()\n\t  .duration(duration)\n\t  .attr(\"d\", function(d) {\n\t\tvar o = {x: source.x, y: source.y};\n\t\treturn diagonal({source: o, target: o});\n\t  })\n\t  .remove();\n\n  // Stash the old positions for transition.\n  nodes.forEach(function(d) {\n\td.x0 = d.x;\n\td.y0 = d.y;\n  });\n}\n\n// Toggle children on click.\nfunction click(d) {\n  console.log(d);\n  if (d.children) {\n\td._children = d.children;\n\td.children = null;\n  } else {\n\td.children = d._children;\n\td._children = null;\n  }\n  update(d);\n}\n</script>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":430,"y":80,"wires":[[]]},{"id":"e22ca977.f549b","type":"ui_group","z":"","name":"Josh plants gauges","tab":"2a9bbfc9.fe1488","order":1,"disp":true,"width":"12","collapse":false},{"id":"4d52119.21ac4f","type":"ui_group","z":"","name":"map","tab":"a95a2ef.0cc6a5","order":2,"disp":true,"width":"6","collapse":false},{"id":"2a9bbfc9.fe1488","type":"ui_tab","z":"","name":"Home","icon":"dashboard","order":1},{"id":"a95a2ef.0cc6a5","type":"ui_tab","z":"","name":"Map","icon":"dashboard","order":2}]

Import it and deploy then go to the UI and look at the MAPS tab - the 'tree' will be there and look fine. Now go to the 'Home' tab and you will see a pie chart and the tree with the expanded curve/lines.

#10

Some more weirdness.My ui has three tabs Home, Maps and Hal. Wen I first of in this is what Home looks like:


Next I go to the Maps tab and it looks like this:

Then I go back to the Home tab and this is what I see:

Me thinks that something is bumping into something else...

#11

Hi zenofmud

I did the send data to map

  • manage to arrange the tree struct
  • did not manage the map template node to read it

as being new , this is probably some syntax error

can you take a look and suggest
tree.txt (9.9 KB)

#12

Hi Paul,

Your code needs to be patched to reflect the tab and group name you have in place.

Instead of

var svg = d3.select("body").append("svg")

Use line below:

var svg = d3.select("#Map_map").append("svg")
1 Like
#13

Ahhh, much better!! I see now, the "body" caused it to be displayed on any tab! I will file this away for future reference!

#14

This is not the issue that is preventing you from displaying the tree in the dashboard but anyway it is better to change your code.

In some places you declare and use a variable named "node". It happens that "node" is already a declared object in Node-RED so better to not use it.

// arrange tree
while( treeData[treeData.length-1].zone !== 0 )
{
    var node = treeData.pop();
    
    // find and add as children to parent
    var i = FindParent(treeData,node);

    if( i != -1)
        treeData[i].children.push(node);
}
#15

Apparently your function node is generating a valid data structure (msg.payload.treeData).

However, I was not able to use it inside the ui_template node. I need to revise how to use the msg object properties inside the ui_template.

When using a variable declared directly inside the ui_template it works perfectly.

Flow:

[{"id":"a9a2ce4f.e328f","type":"tab","label":"Flow 9","disabled":false,"info":""},{"id":"d211e6ca.04c818","type":"function","z":"a9a2ce4f.e328f","name":"prepare map data","func":"// data to read from cordinator eeprom\nvar numBonded = 4;\nvar numDiscovered = 3;\n//\t\t\t\t         0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15\nvar nodesBonded =       [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  0 ];\nvar nodesDiscovered =   [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  0,  0 ];\nvar vrnArray = \t\t    [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  3,  2,  0,  0 ];\nvar nodesZone =         [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  2,  1,  0,  0 ];\nvar nodesParent = \t    [ 0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0 ];\n\nvar treeData = [\n\t{\n\t\t\"name\": \"Cordinator\",\n\t\t\"parent\": \"null\",\n\t\t\"status\": \"orange\",\n\t\t\"zone\": 0,\n\t\t\"children\": []\n\t}\n];\n\nvar index = 0;\nwhile( numBonded )\n{\n    index++;\n\tif( nodesBonded[index] == 1)\n\t{\n\t\tvar parent = \"\";\n\t\tvar status = \"\";\n\t\t\n\t\t// orphan\n\t\tif( nodesDiscovered[index] === 0 )\n\t\t{\n\t\t\tparent = \"null\";\n\t\t\tstatus = \"red\";\n\t\t}\n\t\t// first children\n\t\telse if(  nodesParent[index] === 0 )\n\t\t{\n\t\t\tparent = \"Cordinator\";\n\t\t\tstatus = \"green\";\n\t\t}\n\t\t// other children\n\t\telse\n\t\t{\n\t\t\tparent = \"ID \" + vrnArray.indexOf(nodesParent[index]).toString() + \", VRN \" + nodesParent[index].toString();\n\t\t\tstatus = \"green\";\n\t\t}\n\t\t\n\t\ttreeData.push( \n\t\t{\n\t\t\t\"name\": \"ID \" + index.toString() + \", VRN \" + vrnArray[index].toString(),\n\t\t\t\"parent\": parent,\n\t\t\t\"status\": status,\n\t\t\t\"zone\": nodesZone[index],\n\t\t\t\"children\": []\n\t\t} );\n\t\tnumBonded--;\n\t}\n}\n\n// sort array by zone\ntreeData.sort(function(a,b) {return (a.zone > b.zone) ? 1 : ((b.zone > a.zone) ? -1 : 0);} );\n\n// arrange tree\nwhile( treeData[treeData.length-1].zone !== 0 )\n{\n    var node = treeData.pop();\n    \n    // find and add as children to parent\n    var i = FindParent(treeData,node);\n\n    if( i != -1)\n        treeData[i].children.push(node);\n}\n\nglobal.set(\"treeData\", treeData);\nmsg.payload={\"treeData\": treeData };\n\nreturn msg;\n\nfunction FindParent(treeData, node) {\n    for( var i = treeData.length-1 ; i >= 0 ; i--)\n    {\n        var n = node.parent.localeCompare(treeData[i].name);\n        if((treeData[i].zone === (node.zone)-1) && (n===0))\n            return i;\n    }\n    return -1;\n}","outputs":1,"noerr":0,"x":330,"y":220,"wires":[["2c76fb3c.525134","bdcefd26.67fc7"]]},{"id":"4e9c142a.63189c","type":"inject","z":"a9a2ce4f.e328f","name":"","topic":"","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":140,"y":220,"wires":[["d211e6ca.04c818"]]},{"id":"7b976e82.e2bb7","type":"debug","z":"a9a2ce4f.e328f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":690,"y":220,"wires":[]},{"id":"2c76fb3c.525134","type":"debug","z":"a9a2ce4f.e328f","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":530,"y":160,"wires":[]},{"id":"bdcefd26.67fc7","type":"ui_template","z":"a9a2ce4f.e328f","group":"6a8123c4.16a83c","name":"","order":0,"width":"0","height":"0","format":"<!DOCTYPE html>\n\n<html>\n\n<head>\n<style>\t\n\t.node {\n\t\tcursor: pointer;\n\t}\n\n\t.node circle {\n\t  fill: #fff;\n\t  stroke: steelblue;\n\t  stroke-width: 3px;\n\t}\n\n\t.node text {\n\t  font: 12px sans-serif;\n\t  fill: #fff;\n\t}\n\n\t.link {\n\t  fill: none;\n\t  stroke: #ccc;\n\t  stroke-width: 2px;\n\t}\n</style>\n</head>\n\n<body>\n\n\n<script src=\"//d3js.org/d3.v3.js\"></script>\t\n<script>\nvar treeData = [\n            {\n                \"name\": \"Top Level\",\n                \"parent\": \"null\",\n                \"status\": \"red\",\n                \"children\": [\n                    {\n                        \"name\": \"Level 2-A\",\n                        \"parent\": \"Top Level\",\n                        \"status\": \"red\",\n                        \"children\": [\n                            {\n                                \"name\": \"Legolas\",\n                                \"status\": \"red\",\n                                \"parent\": \"Level 2-A\"\n                            },\n                            {\n                                \"name\": \"Finegal\",\n                                \"status\": \"green\",\n                                \"parent\": \"Level 2-A\"\n                            }\n                        ]\n                    },\n                    {\n                        \"name\": \"Level 2-B\",\n                        \"status\": \"red\",\n                        \"parent\": \"Top Level\",\n                        \"children\": [\n                            {\n                                \"name\": \"Durin\",\n                                \"status\": \"red\",\n                                \"parent\": \"Level 2-B\",\n                                \"children\": [\n                                    {\n                                        \"name\": \"Frodo\",\n                                        \"status\": \"green\",\n                                        \"parent\": \"Durin\"\n                                    },\n                                    {\n                                        \"name\": \"Bilbo\",\n                                        \"status\": \"red\",\n                                        \"parent\": \"Durin\"\n                                    }\n                                ]\n                            },\n                            {\n                                \"name\": \"Balin\",\n                                \"status\": \"green\",\n                                \"parent\": \"Level 2-B\",\n                                \"children\": [\n                                    {\n                                        \"name\": \"Merry\",\n                                        \"status\": \"green\",\n                                        \"parent\": \"Balin\"\n                                    },\n                                    {\n                                        \"name\": \"Pipen\",\n                                        \"status\": \"green\",\n                                        \"parent\": \"Balin\"\n                                    }\n                                ]\n                            }\n                        ]\n                    },\n                    {\n                        \"name\": \"Level 2-C\",\n                        \"parent\": \"Top Level\",\n                        \"status\": \"green\",\n                        \"children\": [\n                            {\n                                \"name\": \"Gandalf\",\n                                \"status\": \"green\",\n                                \"parent\": \"Level 2-C\"\n                            },\n                            {\n                                \"name\": \"Sauruman\",\n                                \"status\": \"green\",\n                                \"parent\": \"Level 2-C\"\n                            }\n                        ]\n                    }\n                ]\n            }\n        ];\n\n\n// ************** Generate the tree diagram\t *****************\nvar margin = {top: 20, right: 120, bottom: 20, left: 120},\n\twidth = 960 - margin.right - margin.left,\n\theight = 500 - margin.top - margin.bottom;\n\t\nvar i = 0,\n\tduration = 750,\n\troot;\n\nvar tree = d3.layout.tree()\n\t.size([height, width]);\n\nvar diagonal = d3.svg.diagonal()\n\t.projection(function(d) { return [d.y, d.x]; });\n\nvar svg = d3.select(\"#Layout_map\").append(\"svg\")\n\t.attr(\"width\", width + margin.right + margin.left)\n\t.attr(\"height\", height + margin.top + margin.bottom)\n  .append(\"g\")\n\t.attr(\"transform\", \"translate(\" + margin.left + \",\" \n\t                                + margin.top + \")\");\n\nroot = treeData[0];\nroot.x0 = height / 2;\nroot.y0 = 0;\n\nfunction toggleAll(d) {\n  if (d.children) {\n    if (d.status == \"green\") {\n      d._children = d.children;\n      d._children.forEach(toggleAll);\n      d.children = null;\n    }\n  }\n}\n\nroot.children.forEach(toggleAll);\n  \nupdate(root);\n\nd3.select(self.frameElement).style(\"height\", \"500px\");\n\nfunction update(source) {\n\n  // Compute the new tree layout.\n  var nodes = tree.nodes(root).reverse(),\n\t  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 node2 = svg.selectAll(\"g.node\")\n\t  .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 = node2.enter().append(\"g\")\n\t  .attr(\"class\", \"node\")\n\t  .attr(\"transform\", function(d) { \n\t\t  return \"translate(\" + source.y0 + \",\" + source.x0 + \")\"; })\n\t  .on(\"click\", click);\n\n  nodeEnter.append(\"circle\")\n\t  .attr(\"r\", 1e-6)\n\t  .style(\"fill\", function(d) { return d.status; });\n\n  nodeEnter.append(\"text\")\n\t  .attr(\"x\", function(d) { \n\t\t  return d.children || d._children ? -13 : 13; })\n\t  .attr(\"dy\", \".35em\")\n\t  .attr(\"text-anchor\", function(d) { \n\t\t  return d.children || d._children ? \"end\" : \"start\"; })\n\t  .text(function(d) { return d.name; })\n\t  .style(\"fill-opacity\", 1e-6);\n\n  // Transition nodes to their new position.\n  var nodeUpdate = node2.transition()\n\t  .duration(duration)\n\t  .attr(\"transform\", function(d) { \n\t\t  return \"translate(\" + d.y + \",\" + d.x + \")\"; });\n\n  nodeUpdate.select(\"circle\")\n\t  .attr(\"r\", 10)\n\t  .style(\"fill\", function(d) { return d.status; });\n\n  nodeUpdate.select(\"text\")\n\t  .style(\"fill-opacity\", 1);\n\n  // Transition exiting nodes to the parent's new position.\n  var nodeExit = node2.exit().transition()\n\t  .duration(duration)\n\t  .attr(\"transform\", function(d) { \n\t\t  return \"translate(\" + source.y + \",\" + source.x + \")\"; })\n\t  .remove();\n\n  nodeExit.select(\"circle\")\n\t  .attr(\"r\", 1e-6);\n\n  nodeExit.select(\"text\")\n\t  .style(\"fill-opacity\", 1e-6);\n\n  // Update the links…\n  var link = svg.selectAll(\"path.link\")\n\t  .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\t  .attr(\"class\", \"link\")\n\t  .attr(\"d\", function(d) {\n\t\tvar o = {x: source.x0, y: source.y0};\n\t\treturn diagonal({source: o, target: o});\n\t  });\n\n  // Transition links to their new position.\n  link.transition()\n\t  .duration(duration)\n\t  .attr(\"d\", diagonal);\n\n  // Transition exiting nodes to the parent's new position.\n  link.exit().transition()\n\t  .duration(duration)\n\t  .attr(\"d\", function(d) {\n\t\tvar o = {x: source.x, y: source.y};\n\t\treturn diagonal({source: o, target: o});\n\t  })\n\t  .remove();\n\n  // Stash the old positions for transition.\n  nodes.forEach(function(d) {\n\td.x0 = d.x;\n\td.y0 = d.y;\n  });\n}\n\n// Toggle children on click.\nfunction click(d) {\n  console.log(d);\n  if (d.children) {\n\td._children = d.children;\n\td.children = null;\n  } else {\n\td.children = d._children;\n\td._children = null;\n  }\n  update(d);\n}\n</script>\n\n</body>\n\n</html>","storeOutMessages":false,"fwdInMessages":true,"templateScope":"local","x":520,"y":220,"wires":[["7b976e82.e2bb7"]]},{"id":"6a8123c4.16a83c","type":"ui_group","z":"","name":"map","tab":"4361b344.61bc2c","order":2,"disp":false,"width":"16","collapse":false},{"id":"4361b344.61bc2c","type":"ui_tab","z":"","name":"Layout","icon":"dashboard","order":2}]
#16

almost there

able to update by msg.payload
but

  • trees are just added , did not find the correct place to erase old tree
    meaning where to put: d3.select("svg").remove();
  • the tree is not fully unfolded, only first level, second level need click on circle

Flow:
tree.txt (8.5 KB)

#17

Hi guys,

Very interesting stuff !

@natanel: will you make this afterwards available on flows.nodered.org, so it could be used as a generic usable component?

Kind regards,
Bart

#18

Hi Bart

first thanks
few points

  1. it is not my code I download it from (Andrei found it)
    https://stackoverflow.com/questions/23864115/how-to-recursively-toggle-nodes-in-a-d3-js-tree-diagram
  2. just modify it to be able to put it in node red template and that it will be able to get input as msg
  3. also built function node to pass information from my mesh network eeprom data to tree requested json
  4. I am so green in this node-red and javascript ( I am real time C++ programmer), so I can be very little help in this time
    (even though I built IOT mesh network to MQTT, node-red, android application which I an quit proud of it)
  5. so fill free to make this available where ever you think, I will use any fix/helps that a community can give

any way my suggestion
this tree is very specific to the example data (found in link)

  1. add ability to auto scale group size as function of zone/ add scrolling abilities
  2. need to clear old tree on new msg, did not worked for me
  3. add multi trees on same group
  4. tree options from up/down/left/right

just send me a link so I can benefit from any improvement

#19

Hi

finally got something
that I can do and I am pleased

tree.txt (13.2 KB)

  1. added computeSvgSizeFromData, which do exactly that "compute Svg Size From Data"
  2. finally found how to erase old data on new data arriving

not sure my clear method is correct, but it's working

// clear
d3.select("#Layout_map_SVG").remove();

still will be nice to have

  1. add scrolling bar abilities
  2. tree options from up/down/left/right
  3. correct way to pass to the SVG element to use the id of the TAB/group,
    getting it from the node properties of the template node

Andrei - as before, your help is gold
zenofmud - you can see the code you asked in the attachment
Bart - fill free to progress this node, I can use the community help

BR
Natanel

1 Like
#20

Hello @natanel,

I am lagging behind as only now I could have a look on this post.

Looks like you already solved both issues but let me provide my thoughts

I would rather find a way to add the SVG element only once, at the startup, so there will be no need to add a statement to remove the SVG element later on. This can be achieved by moving a few lines of code outside the function(scope) block. I did it and, apparently, it is working all right.

In regards to expand all the tree nodes at the startup. As far as I could understand the function toggleAll(d) will check the color of the node and expand or not. In order to always expand at the startup, I modified the color to one not used (black). It worked.

Here is the modified flow in case you want to use some of these ideas:

[{"id":"f889d991.03a9d8","type":"tab","label":"Flow 3","disabled":false,"info":""},{"id":"dd4f4542.e90b08","type":"ui_template","z":"f889d991.03a9d8","group":"4ccda03f.4a457","name":"map matrix","order":0,"width":"6","height":"1","format":"<style>\t\n\t.node {\n\t\tcursor: pointer;\n\t}\n\n\t.node circle {\n\t  fill: #fff;\n\t  stroke: steelblue;\n\t  stroke-width: 3px;\n\t}\n\n\t.node text {\n\t  font: 12px sans-serif;\n\t  fill: #fff;\n\t}\n\n\t.link {\n\t  fill: none;\n\t  stroke: #ccc;\n\t  stroke-width: 2px;\n\t}\n</style>\n\n<script>\n        // ************** Generate the tree diagram\t *****************\n        var margin = { top: 20, right: 120, bottom: 20, left: 120 },\n            width = 960 - margin.right - margin.left,\n            height = 500 - margin.top - margin.bottom;\n            \n            var svg = d3.select(\"#Layout_map\").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 + \",\"\n                + margin.top + \")\");\n                \n</script>\n\n\n<script>\n(function (scope) {\n       //console.dir(scope);\n    scope.$watch('msg', function (msg) {\n    //console.dir(msg);\n\n        var treeData = msg.payload.treeData;\n\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) { return [d.y, d.x]; });\n\n\n\n        root = treeData[0];\n        root.x0 = height / 2;\n        root.y0 = 0;\n\n        function toggleAll(d) {\n            if (d.children) {\n                if (d.status == \"black\") {\n                    d._children = d.children;\n                    d._children.forEach(toggleAll);\n                    d.children = null;\n                }\n            }\n        }\n\n        root.children.forEach(toggleAll);\n\n        update(root);\n\n        //root = treeData[1];\n        //root.x0 = 2* ( height / 3 );\n        //root.y0 = 0;\n        //root.children.forEach(toggleAll);\n        //update(root);\n\n        d3.select(self.frameElement).style(\"height\", \"500px\");\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 node2 = 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 = node2.enter().append(\"g\")\n                .attr(\"class\", \"node\")\n                .attr(\"transform\", function (d) {\n                    return \"translate(\" + source.y0 + \",\" + source.x0 + \")\";\n                })\n                .on(\"click\", click);\n\n            nodeEnter.append(\"circle\")\n                .attr(\"r\", 1e-6)\n                .style(\"fill\", function (d) { return d.status; });\n\n            nodeEnter.append(\"text\")\n                .attr(\"x\", function (d) {\n                    return d.children || d._children ? -13 : 13;\n                })\n                .attr(\"dy\", \".35em\")\n                .attr(\"text-anchor\", function (d) {\n                    return d.children || d._children ? \"end\" : \"start\";\n                })\n                .text(function (d) { return d.name; })\n                .style(\"fill-opacity\", 1e-6);\n\n            // Transition nodes to their new position.\n            var nodeUpdate = node2.transition()\n                .duration(duration)\n                .attr(\"transform\", function (d) {\n                    return \"translate(\" + d.y + \",\" + d.x + \")\";\n                });\n\n            nodeUpdate.select(\"circle\")\n                .attr(\"r\", 10)\n                .style(\"fill\", function (d) { return d.status; });\n\n            nodeUpdate.select(\"text\")\n                .style(\"fill-opacity\", 1);\n\n            // Transition exiting nodes to the parent's new position.\n            var nodeExit = node2.exit().transition()\n                .duration(duration)\n                .attr(\"transform\", function (d) {\n                    return \"translate(\" + source.y + \",\" + source.x + \")\";\n                })\n                .remove();\n\n            nodeExit.select(\"circle\")\n                .attr(\"r\", 1e-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                        // Toggle children on click.\n        function click(d) {\n               //console.log(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})(scope);\n</script>","storeOutMessages":true,"fwdInMessages":true,"templateScope":"local","x":790,"y":240,"wires":[[]]},{"id":"2b1738d4.41a168","type":"function","z":"f889d991.03a9d8","name":"prepare map data","func":"// data to read from cordinator eeprom\nvar numBonded = 4;\nvar numDiscovered = 3;\n//\t\t\t\t       0   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15\nvar nodesBonded =     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0];\nvar nodesDiscovered = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0];\nvar vrnArray =        [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 2, 0, 0];\nvar nodesZone =       [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 1, 0, 0];\nvar nodesParent =     [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0];\n\nvar treeData = [\n    {\n        \"name\": \"Cordinator\",\n        \"parent\": \"null\",\n        \"status\": \"orange\",\n        \"children\": [],\n        \"zone\": 0\n    }\n];\n\nvar index = 0;\nwhile (numBonded) {\n    index++;\n    if (nodesBonded[index] == 1) {\n        var parent = \"\";\n        var status = \"\";\n\n        // orphan\n        if (nodesDiscovered[index] === 0) {\n            parent = \"null\";\n            status = \"red\";\n        }\n        // first children\n        else if (nodesParent[index] === 0) {\n            parent = \"Cordinator\";\n            status = \"green\";\n        }\n        // other children\n        else {\n            parent = \"ID \" + vrnArray.indexOf(nodesParent[index]).toString() + \", VRN \" + nodesParent[index].toString();\n            status = \"green\";\n        }\n\n        treeData.push(\n            {\n                \"name\": \"ID \" + index.toString() + \", VRN \" + vrnArray[index].toString(),\n                \"parent\": parent,\n                \"status\": status,\n                \"children\": [],\n                \"zone\": nodesZone[index],\n            });\n        numBonded--;\n    }\n}\n\n// sort array by zone\ntreeData.sort(function (a, b) { return (a.zone > b.zone) ? 1 : ((b.zone > a.zone) ? -1 : 0); });\n\n// arrange tree\nwhile (treeData[treeData.length - 1].zone !== 0) {\n    var nodeChild = treeData.pop();\n\n    // find and add as children to parent\n    var i = FindParent(treeData, nodeChild);\n\n    if (i != -1)\n        treeData[i].children.push(nodeChild);\n}\n\nglobal.set(\"treeData\", treeData);\nmsg.payload = { \"treeData\": treeData };\n\nreturn msg;\n\nfunction FindParent(treeData, nodeChild) {\n    for (var i = treeData.length - 1; i >= 0; i--) {\n        var n = nodeChild.parent.localeCompare(treeData[i].name);\n        if ((treeData[i].zone === (nodeChild.zone) - 1) && (n === 0))\n            return i;\n    }\n    return -1;\n}","outputs":1,"noerr":0,"x":550,"y":180,"wires":[["527fa260.f0c70c","dd4f4542.e90b08"]]},{"id":"cb1c32ef.a7d4a","type":"inject","z":"f889d991.03a9d8","name":"","topic":"prepare map","payload":"","payloadType":"date","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":320,"y":180,"wires":[["2b1738d4.41a168"]]},{"id":"527fa260.f0c70c","type":"debug","z":"f889d991.03a9d8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":790,"y":180,"wires":[]},{"id":"4ccda03f.4a457","type":"ui_group","z":"","name":"map","tab":"e870aec0.5ba89","order":2,"disp":true,"width":"6","collapse":false},{"id":"e870aec0.5ba89","type":"ui_tab","z":"","name":"Layout","icon":"dashboard","order":2}]