Matteo Scandolo | eeb9c08 | 2016-02-09 11:19:22 -0800 | [diff] [blame] | 1 | (function () { |
| 2 | 'use strict'; |
| 3 | |
| 4 | angular.module('xos.serviceTopology') |
| 5 | .service('LogicTopologyHelper', function($window, $log, lodash, serviceTopologyConfig){ |
| 6 | |
| 7 | var hStep, vStep; |
| 8 | |
| 9 | const createDevice = (container, device, xPos, yPos, target) => { |
| 10 | |
| 11 | const deviceGroup = container.append('g') |
| 12 | .attr({ |
| 13 | class: 'device', |
| 14 | transform: `translate(${xPos}, ${yPos})` |
| 15 | }); |
| 16 | |
| 17 | const deviceEl = deviceGroup.append('circle') |
| 18 | .attr({ |
| 19 | r: serviceTopologyConfig.circle.radius |
| 20 | }); |
| 21 | |
| 22 | deviceGroup.append('text') |
| 23 | .attr({ |
| 24 | x: - serviceTopologyConfig.circle.radius - 3, |
| 25 | dy: '.35em', |
| 26 | 'text-anchor': 'end' |
| 27 | }) |
| 28 | .text(device.name) |
| 29 | |
| 30 | const [deviceX, deviceY] = d3.transform(deviceEl.attr('transform')).translate; |
| 31 | const [deviceGroupX, deviceGroupY] = d3.transform(deviceGroup.attr('transform')).translate; |
| 32 | let [targetX, targetY] = d3.transform(target.attr('transform')).translate; |
| 33 | |
| 34 | targetX = targetX - deviceGroupX; |
| 35 | targetY = targetY - deviceGroupY; |
| 36 | |
| 37 | console.log('Device: ' + deviceX, deviceY); |
| 38 | console.log('Subscriber: ' + targetX, targetY); |
| 39 | |
| 40 | var diagonal = d3.svg.diagonal() |
| 41 | .source({x: deviceX, y: deviceY}) |
| 42 | .target({x: targetX, y: targetY}) |
Matteo Scandolo | b373a10 | 2016-02-09 11:37:11 -0800 | [diff] [blame] | 43 | // .projection(d => { |
| 44 | // return [d.y, d.x]; |
| 45 | // }); |
Matteo Scandolo | eeb9c08 | 2016-02-09 11:19:22 -0800 | [diff] [blame] | 46 | |
| 47 | deviceGroup |
| 48 | .append('path') |
| 49 | .attr('class', 'device-link') |
| 50 | .attr('d', diagonal); |
| 51 | } |
| 52 | |
| 53 | const createSubscriber = (container, subscriber, xPos, yPos) => { |
| 54 | |
| 55 | const subscriberGroup = container.append('g') |
| 56 | .attr({ |
| 57 | class: 'subscriber', |
| 58 | transform: `translate(${xPos * 2}, ${yPos})` |
| 59 | }); |
| 60 | |
| 61 | subscriberGroup.append('circle') |
| 62 | .attr({ |
| 63 | r: serviceTopologyConfig.circle.radius |
| 64 | }); |
| 65 | |
| 66 | subscriberGroup.append('text') |
| 67 | .attr({ |
| 68 | x: serviceTopologyConfig.circle.radius + 3, |
| 69 | dy: '.35em', |
| 70 | 'text-anchor': 'start' |
| 71 | }) |
| 72 | .text(subscriber.humanReadableName) |
| 73 | |
| 74 | // TODO |
| 75 | // starting from the subscriber position, we should center |
| 76 | // the device goup based on his own height |
| 77 | // const deviceContainer = container.append('g') |
| 78 | // .attr({ |
| 79 | // class: 'devices-container', |
| 80 | // transform: `translate(${xPos}, ${yPos -(vStep / 2)})` |
| 81 | // }); |
| 82 | |
| 83 | angular.forEach(subscriber.devices, (device, j) => { |
| 84 | createDevice(container, device, xPos, ((vStep / subscriber.devices.length) * j) + (yPos - vStep / 2), subscriberGroup); |
| 85 | }); |
| 86 | } |
| 87 | |
| 88 | this.handleSubscribers = (svg, subscribers) => { |
| 89 | |
| 90 | // HACKY |
| 91 | hStep = angular.element(svg[0])[0].clientWidth / 7; |
| 92 | vStep = angular.element(svg[0])[0].clientHeight / (subscribers.length + 1); |
| 93 | |
| 94 | const container = svg.append('g') |
| 95 | .attr({ |
| 96 | class: 'subscribers-container' |
| 97 | }); |
| 98 | |
| 99 | lodash.forEach(subscribers, (subscriber, i) => { |
| 100 | createSubscriber(container, subscriber, hStep, vStep * (i + 1)); |
| 101 | }) |
| 102 | } |
Matteo Scandolo | 11dc8c4 | 2016-02-09 14:46:14 -0800 | [diff] [blame^] | 103 | |
| 104 | var diagonal, nodes, links, i = 0, svgWidth, svgHeight, layout; |
| 105 | |
| 106 | const baseData = { |
| 107 | name: 'Router', |
| 108 | type: 'router', |
| 109 | children: [ |
| 110 | { |
| 111 | name: 'WAN', |
| 112 | type: 'network', |
| 113 | children: [ |
| 114 | { |
| 115 | name: 'Rack', |
| 116 | type: 'rack', |
| 117 | children: [ |
| 118 | { |
| 119 | name: 'LAN', |
| 120 | type: 'network', |
| 121 | children: [] //subscribers goes here |
| 122 | } |
| 123 | ] |
| 124 | } |
| 125 | ] |
| 126 | } |
| 127 | ] |
| 128 | }; |
| 129 | |
| 130 | const computeLayout = (data) => { |
| 131 | let nodes = layout.nodes(data); |
| 132 | |
| 133 | // Normalize for fixed-depth. |
| 134 | nodes.forEach(function(d) { |
| 135 | // position the child node horizontally |
| 136 | const step = ((svgWidth - (serviceTopologyConfig.widthMargin * 2)) / 7); |
| 137 | d.y = (6 - d.depth) * step; |
| 138 | }); |
| 139 | |
| 140 | let links = layout.links(nodes); |
| 141 | console.log(nodes.length, links.length); |
| 142 | return [nodes, links]; |
| 143 | }; |
| 144 | |
| 145 | const drawNodes = (svg, nodes) => { |
| 146 | // Update the nodes… |
| 147 | var node = svg.selectAll('g.node') |
| 148 | .data(nodes, d => {return d.id || (d.id = `tree-${i++}`)}); |
| 149 | |
| 150 | // Enter any new nodes at the parent's previous position. |
| 151 | var nodeEnter = node.enter().append('g') |
| 152 | .attr({ |
| 153 | class: d => `node ${d.type}`, |
| 154 | transform: `translate(${svgWidth / 2}, ${svgHeight / 2})` |
| 155 | }); |
| 156 | |
| 157 | nodeEnter.append('circle') |
| 158 | .attr('r', 10) |
| 159 | .style('fill', d => d._children ? 'lightsteelblue' : '#fff'); |
| 160 | |
| 161 | nodeEnter.append('text') |
| 162 | .attr({ |
| 163 | x: d => d.children ? serviceTopologyConfig.circle.selectedRadius + 3 : -serviceTopologyConfig.circle.selectedRadius - 3, |
| 164 | dy: '.35em', |
| 165 | transform: d => { |
| 166 | if (d.children && d.parent){ |
| 167 | if(d.parent.x < d.x){ |
| 168 | return 'rotate(-30)'; |
| 169 | } |
| 170 | return 'rotate(30)'; |
| 171 | } |
| 172 | }, |
| 173 | 'text-anchor': d => d.children ? 'start' : 'end' |
| 174 | }) |
| 175 | .text(d => d.name); |
| 176 | |
| 177 | // Transition nodes to their new position. |
| 178 | var nodeUpdate = node.transition() |
| 179 | .duration(serviceTopologyConfig.duration) |
| 180 | .attr({ |
| 181 | 'transform': d => `translate(${d.y},${d.x})` |
| 182 | }); |
| 183 | |
| 184 | // TODO handle node remove |
| 185 | }; |
| 186 | |
| 187 | const drawLinks = (svg, links) => { |
| 188 | |
| 189 | diagonal = d3.svg.diagonal() |
| 190 | .projection(d => [d.y, d.x]); |
| 191 | |
| 192 | // Update the links… |
| 193 | var link = svg.selectAll('path.link') |
| 194 | .data(links, d => { |
| 195 | return d.target.id |
| 196 | }); |
| 197 | |
| 198 | // Enter any new links at the parent's previous position. |
| 199 | link.enter().insert('path', 'g') |
| 200 | .attr('class', d => `link ${d.target.type}`) |
| 201 | .attr('d', function(d) { |
| 202 | var o = {x: svgHeight / 2, y: svgWidth / 2}; |
| 203 | return diagonal({source: o, target: o}); |
| 204 | }); |
| 205 | |
| 206 | // Transition links to their new position. |
| 207 | link.transition() |
| 208 | .duration(serviceTopologyConfig.duration) |
| 209 | .attr('d', diagonal); |
| 210 | }; |
| 211 | |
| 212 | this.drawTree = (svg) => { |
| 213 | |
| 214 | |
| 215 | svgWidth = svg.node().getBoundingClientRect().width; |
| 216 | svgHeight = svg.node().getBoundingClientRect().height; |
| 217 | |
| 218 | const width = svgWidth - (serviceTopologyConfig.widthMargin * 2); |
| 219 | const height = svgHeight - (serviceTopologyConfig.heightMargin * 2); |
| 220 | |
| 221 | layout = d3.layout.tree() |
| 222 | .size([height, width]); |
| 223 | |
| 224 | // Compute the new tree layout. |
| 225 | [nodes, links] = computeLayout(baseData); |
| 226 | |
| 227 | drawNodes(svg, nodes); |
| 228 | drawLinks(svg, links); |
| 229 | |
| 230 | }; |
| 231 | |
| 232 | this.addSubscribers = (svg, subscribers) => { |
| 233 | |
| 234 | console.log(subscribers); |
| 235 | |
| 236 | subscribers.map((subscriber) => { |
| 237 | subscriber.children = subscriber.devices; |
| 238 | }); |
| 239 | |
| 240 | // add subscriber to data tree |
| 241 | baseData.children[0].children[0].children[0].children = subscribers; |
| 242 | |
| 243 | console.log(baseData); |
| 244 | |
| 245 | [nodes, links] = computeLayout(baseData); |
| 246 | |
| 247 | drawNodes(svg, nodes); |
| 248 | drawLinks(svg, links); |
| 249 | } |
Matteo Scandolo | eeb9c08 | 2016-02-09 11:19:22 -0800 | [diff] [blame] | 250 | }); |
| 251 | |
| 252 | }()); |