Drawing subscriber and devices
diff --git a/views/ngXosViews/diagnostic/src/js/d3.js b/views/ngXosViews/diagnostic/src/js/d3.js
index 027f08d..d94a22e 100644
--- a/views/ngXosViews/diagnostic/src/js/d3.js
+++ b/views/ngXosViews/diagnostic/src/js/d3.js
@@ -2,260 +2,8 @@
   'use strict';
 
   angular.module('xos.serviceTopology')
-    .factory('d3', function($window){
-      return $window.d3;
-    })
-  .service('TreeLayout', function($window, $log, lodash, ServiceRelation, serviceTopologyConfig){
-
-    const drawLegend = (svg) => {
-      const legendContainer = svg.append('g')
-        .attr({
-          class: 'legend'
-        });
-
-      legendContainer.append('rect')
-      .attr({
-        transform: d => `translate(10, 80)`,
-        width: 100,
-        height: 100
-      });
-
-      // service
-      const service = legendContainer.append('g')
-      .attr({
-        class: 'node service'
-      });
-
-      service.append('circle')
-      .attr({
-        r: serviceTopologyConfig.circle.radius,
-        transform: d => `translate(30, 100)`
-      });
-
-      service.append('text')
-      .attr({
-        transform: d => `translate(45, 100)`,
-        dy: '.35em'
-      })
-      .text('Service')
-        .style('fill-opacity', 1);
-
-      // slice
-      const slice = legendContainer.append('g')
-        .attr({
-          class: 'node slice'
-        });
-
-      slice.append('rect')
-        .attr({
-          width: 20,
-          height: 20,
-          x: -10,
-          y: -10,
-          transform: d => `translate(30, 130)`
-        });
-
-      slice.append('text')
-        .attr({
-          transform: d => `translate(45, 130)`,
-          dy: '.35em'
-        })
-        .text('Slices')
-        .style('fill-opacity', 1);
-
-      // instance
-      const instance = legendContainer.append('g')
-        .attr({
-          class: 'node instance'
-        });
-
-      instance.append('rect')
-        .attr({
-          width: 20,
-          height: 20,
-          x: -10,
-          y: -10,
-          transform: d => `translate(30, 160)`
-        });
-
-      instance.append('text')
-        .attr({
-          transform: d => `translate(45, 160)`,
-          dy: '.35em'
-        })
-        .text('Instances')
-        .style('fill-opacity', 1);
-    };
-
-    var _svg, _layout, _source;
-
-    var i = 0;
-
-    // given a canvas, a layout and a data source, draw a tree layout
-    const updateTree = (svg, layout, source) => {
-
-      //cache data
-      _svg = svg;
-      _layout = layout;
-      _source = source;
-
-      const maxDepth = ServiceRelation.depthOf(source);
-
-      const diagonal = d3.svg.diagonal()
-        .projection(d => [d.y, d.x]);
-
-      // Compute the new tree layout.
-      var nodes = layout.nodes(source).reverse(),
-        links = layout.links(nodes);
-
-      // Normalize for fixed-depth.
-      nodes.forEach(function(d) {
-        // position the child node horizontally
-        const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
-        d.y = d.depth * step;
-      });
-
-      // Update the nodes…
-      var node = svg.selectAll('g.node')
-        .data(nodes, function(d) { return d.id || (d.id = ++i); });
-
-      // Enter any new nodes at the parent's previous position.
-      var nodeEnter = node.enter().append('g')
-        .attr({
-          class: d => `node ${d.type}`,
-          transform: `translate(${source.y0}, ${source.x0})`
-        });
-
-      const subscriberNodes = nodeEnter.filter('.subscriber');
-      const internetNodes = nodeEnter.filter('.internet');
-      const serviceNodes = nodeEnter.filter('.service');
-      const instanceNodes = nodeEnter.filter('.instance');
-      const sliceNodes = nodeEnter.filter('.slice');
-
-      subscriberNodes.append('rect')
-        .attr(serviceTopologyConfig.square);
-
-      internetNodes.append('rect')
-        .attr(serviceTopologyConfig.square);
-
-      serviceNodes.append('circle')
-        .attr('r', 1e-6)
-        .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
-        .on('click', serviceClick);
-
-      sliceNodes.append('rect')
-        .attr({
-          width: 20,
-          height: 20,
-          x: -10,
-          y: -10
-        });
-
-      instanceNodes.append('rect')
-        .attr({
-          width: 20,
-          height: 20,
-          x: -10,
-          y: -10,
-          class: d => d.active ?'' : 'active'
-        });
-
-      nodeEnter.append('text')
-        .attr({
-          x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
-          dy: '.35em',
-          transform: d => {
-            if (d.children && d.parent){
-              if(d.parent.x < d.x){
-                return 'rotate(-30)';
-              }
-              return 'rotate(30)';
-            }
-          },
-          'text-anchor': d => d.children ? 'end' : 'start'
-        })
-        .text(d => d.name)
-        .style('fill-opacity', 1e-6);
-
-      // Transition nodes to their new position.
-      var nodeUpdate = node.transition()
-        .duration(serviceTopologyConfig.duration)
-        .attr({
-          'transform': d => `translate(${d.y},${d.x})`
-        });
-
-      nodeUpdate.select('circle')
-        .attr('r', d => d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius)
-        .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
-
-      nodeUpdate.select('text')
-        .style('fill-opacity', 1);
-
-      // Transition exiting nodes to the parent's new position.
-      var nodeExit = node.exit().transition()
-        .duration(serviceTopologyConfig.duration)
-        .remove();
-
-      nodeExit.select('circle')
-        .attr('r', 1e-6);
-
-      nodeExit.select('text')
-        .style('fill-opacity', 1e-6);
-
-      // Update the links…
-      var link = svg.selectAll('path.link')
-        .data(links, function(d) { return d.target.id; });
-
-      // Enter any new links at the parent's previous position.
-      link.enter().insert('path', 'g')
-        .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
-        .attr('d', function(d) {
-          var o = {x: source.x0, y: source.y0};
-          return diagonal({source: o, target: o});
-        });
-
-      // Transition links to their new position.
-      link.transition()
-        .duration(serviceTopologyConfig.duration)
-        .attr('d', diagonal);
-
-      // Transition exiting nodes to the parent's new position.
-      link.exit().transition()
-        .duration(serviceTopologyConfig.duration)
-        .attr('d', function(d) {
-          var o = {x: source.x, y: source.y};
-          return diagonal({source: o, target: o});
-        })
-        .remove();
-
-      // Stash the old positions for transition.
-      nodes.forEach(function(d) {
-        d.x0 = d.x;
-        d.y0 = d.y;
-      });
-    };
-
-    const serviceClick = function(d) {
-
-      $log.info('TODO emit an event to highlight VMs');
-
-      if(!d.service){
-        return;
-      }
-
-      // toggling selected status
-      d.selected = !d.selected;
-
-      var selectedNode = d3.select(this);
-
-      selectedNode
-        .transition()
-        .duration(serviceTopologyConfig.duration)
-        .attr('r', serviceTopologyConfig.circle.selectedRadius);
-    };
-
-    this.updateTree = updateTree;
-    this.drawLegend = drawLegend;
-  });
+  .factory('d3', function($window){
+    return $window.d3;
+  })
 
 }());
\ No newline at end of file
diff --git a/views/ngXosViews/diagnostic/src/js/diagnostic.js b/views/ngXosViews/diagnostic/src/js/diagnostic.js
index 0a02b04..938b2f6 100644
--- a/views/ngXosViews/diagnostic/src/js/diagnostic.js
+++ b/views/ngXosViews/diagnostic/src/js/diagnostic.js
@@ -10,7 +10,6 @@
       controller: function(Subscribers, ServiceRelation){
         Subscribers.queryWithDevices().$promise
         .then((subscribers) => {
-          console.log(subscribers);
           this.subscribers = subscribers;
           return ServiceRelation.get(subscribers[0]);
         })
diff --git a/views/ngXosViews/diagnostic/src/js/logicTopology.js b/views/ngXosViews/diagnostic/src/js/logicTopology.js
index c00b5b6..0a9215e 100644
--- a/views/ngXosViews/diagnostic/src/js/logicTopology.js
+++ b/views/ngXosViews/diagnostic/src/js/logicTopology.js
@@ -5,15 +5,38 @@
     return {
       restrict: 'E',
       scope: {
-        serviceChain: '='
+        subscribers: '=',
+        selected: '='
       },
       bindToController: true,
       controllerAs: 'vm',
       template: '',
-      controller: function($element, $log){
+      controller: function($element, $log, $scope, d3, LogicTopologyHelper){
         $log.info('Logic Plane');
 
-        
+        var svg;
+
+        $scope.$watch(() => this.subscribers, (subscribers) => {
+          if(subscribers){
+            LogicTopologyHelper.handleSubscribers(svg, subscribers);
+          }
+        });
+
+        $scope.$watch(() => this.selected, (selected) => {
+          if(selected){
+            $log.info(`Update logic layer for subscriber ${selected.humanReadableName}`);
+          }
+        });
+
+        const handleSvg = (el) => {
+
+          svg = d3.select(el)
+          .append('svg')
+          .style('width', `${el.clientWidth}px`)
+          .style('height', `${el.clientHeight}px`);
+        }
+
+        handleSvg($element[0]);
       }
     };
   });
diff --git a/views/ngXosViews/diagnostic/src/js/logicTopologyHelper.js b/views/ngXosViews/diagnostic/src/js/logicTopologyHelper.js
new file mode 100644
index 0000000..8aedda2
--- /dev/null
+++ b/views/ngXosViews/diagnostic/src/js/logicTopologyHelper.js
@@ -0,0 +1,105 @@
+(function () {
+  'use strict';
+
+  angular.module('xos.serviceTopology')
+  .service('LogicTopologyHelper', function($window, $log, lodash, serviceTopologyConfig){
+
+    var hStep, vStep;
+
+    const createDevice = (container, device, xPos, yPos, target) => {
+
+      const deviceGroup = container.append('g')
+      .attr({
+        class: 'device',
+        transform: `translate(${xPos}, ${yPos})`
+      });
+
+      const deviceEl = deviceGroup.append('circle')
+      .attr({
+        r: serviceTopologyConfig.circle.radius
+      });
+
+      deviceGroup.append('text')
+      .attr({
+        x: - serviceTopologyConfig.circle.radius - 3,
+        dy: '.35em',
+        'text-anchor': 'end'
+      })
+      .text(device.name)
+
+      const [deviceX, deviceY] = d3.transform(deviceEl.attr('transform')).translate;
+      const [deviceGroupX, deviceGroupY] = d3.transform(deviceGroup.attr('transform')).translate;
+      let [targetX, targetY] = d3.transform(target.attr('transform')).translate;
+
+      targetX = targetX - deviceGroupX;
+      targetY = targetY - deviceGroupY;
+
+      console.log('Device: ' + deviceX, deviceY);
+      console.log('Subscriber: ' + targetX, targetY);
+
+      var diagonal = d3.svg.diagonal()
+      .source({x: deviceX, y: deviceY})
+      .target({x: targetX, y: targetY})
+      .projection(d => {
+        return [d.x, d.y];
+      });
+
+      deviceGroup
+        .append('path')
+        .attr('class', 'device-link')
+        .attr('d', diagonal);
+    }
+
+    const createSubscriber = (container, subscriber, xPos, yPos) => {
+
+      const subscriberGroup = container.append('g')
+      .attr({
+        class: 'subscriber',
+        transform: `translate(${xPos * 2}, ${yPos})`
+      });
+
+      subscriberGroup.append('circle')
+      .attr({
+        r: serviceTopologyConfig.circle.radius
+      });
+
+      subscriberGroup.append('text')
+      .attr({
+        x: serviceTopologyConfig.circle.radius + 3,
+        dy: '.35em',
+        'text-anchor': 'start'
+      })
+      .text(subscriber.humanReadableName)
+
+      // TODO
+      // starting from the subscriber position, we should center
+      // the device goup based on his own height
+      // const deviceContainer = container.append('g')
+      // .attr({
+      //   class: 'devices-container',
+      //   transform: `translate(${xPos}, ${yPos -(vStep / 2)})`
+      // });
+
+      angular.forEach(subscriber.devices, (device, j) => {
+        createDevice(container, device, xPos, ((vStep / subscriber.devices.length) * j) + (yPos - vStep / 2), subscriberGroup);
+      });
+    }
+
+    this.handleSubscribers = (svg, subscribers) => {
+
+      // HACKY
+      hStep = angular.element(svg[0])[0].clientWidth / 7;
+      vStep = angular.element(svg[0])[0].clientHeight / (subscribers.length + 1);
+
+      const container = svg.append('g')
+      .attr({
+        class: 'subscribers-container'
+      });
+
+      lodash.forEach(subscribers, (subscriber, i) => {
+        createSubscriber(container, subscriber, hStep, vStep * (i + 1));
+      })
+    }
+  });
+
+}());
\ No newline at end of file
diff --git a/views/ngXosViews/diagnostic/src/js/services.js b/views/ngXosViews/diagnostic/src/js/rest_services.js
similarity index 96%
rename from views/ngXosViews/diagnostic/src/js/services.js
rename to views/ngXosViews/diagnostic/src/js/rest_services.js
index 669d028..beeb4fe 100644
--- a/views/ngXosViews/diagnostic/src/js/services.js
+++ b/views/ngXosViews/diagnostic/src/js/rest_services.js
@@ -21,21 +21,28 @@
         isArray: true,
         interceptor: {
           response: function(res){
+
+            /**
+            * For each subscriber retrieve devices and append them
+            */
+
             const deferred = $q.defer();
 
             let requests = [];
 
             angular.forEach(res.data, (subscriber) => {
-              requests.push(SubscriberDevice.query({id: subscriber.id}));
+              requests.push(SubscriberDevice.query({id: subscriber.id}).$promise);
             })
 
             $q.all(requests)
             .then((list) => {
-              console.log(list);
               res.data.map((subscriber, i) => {
                 subscriber.devices = list[i];
                 return subscriber;
               });
+
+              // faking to have 2 subscriber
+              res.data.push(res.data[0]);
               deferred.resolve(res.data);
             })
 
diff --git a/views/ngXosViews/diagnostic/src/js/serviceTopology.js b/views/ngXosViews/diagnostic/src/js/serviceTopology.js
index 163ee34..820b3a9 100644
--- a/views/ngXosViews/diagnostic/src/js/serviceTopology.js
+++ b/views/ngXosViews/diagnostic/src/js/serviceTopology.js
@@ -11,13 +11,12 @@
       bindToController: true,
       controllerAs: 'vm',
       template: '',
-      controller: function($element, $window, $scope, d3, serviceTopologyConfig, ServiceRelation, Slice, Instances, Subscribers, TreeLayout){
+      controller: function($element, $window, $scope, d3, serviceTopologyConfig, ServiceRelation, Slice, Instances, Subscribers, ServiceTopologyHelper){
 
         const el = $element[0];
 
         d3.select(window)
         .on('resize', () => {
-          console.log('resize');
           draw(this.serviceChain);
         });
 
@@ -25,6 +24,8 @@
 
         const draw = (tree) => {
 
+          // TODO update instead clear and redraw
+
           // clean
           d3.select($element[0]).select('svg').remove();
 
@@ -46,8 +47,8 @@
           root.x0 = height / 2;
           root.y0 = width / 2;
 
-          TreeLayout.drawLegend(svg);
-          TreeLayout.updateTree(treeContainer, treeLayout, root);
+          ServiceTopologyHelper.drawLegend(svg);
+          ServiceTopologyHelper.updateTree(treeContainer, treeLayout, root);
         };
 
         this.getInstances = (slice) => {
diff --git a/views/ngXosViews/diagnostic/src/js/serviceTopologyHelper.js b/views/ngXosViews/diagnostic/src/js/serviceTopologyHelper.js
new file mode 100644
index 0000000..f3cb2d2
--- /dev/null
+++ b/views/ngXosViews/diagnostic/src/js/serviceTopologyHelper.js
@@ -0,0 +1,258 @@
+(function () {
+  'use strict';
+
+  angular.module('xos.serviceTopology')
+  .service('ServiceTopologyHelper', function($window, $log, lodash, ServiceRelation, serviceTopologyConfig){
+
+    const drawLegend = (svg) => {
+      const legendContainer = svg.append('g')
+        .attr({
+          class: 'legend'
+        });
+
+      legendContainer.append('rect')
+      .attr({
+        transform: d => `translate(10, 80)`,
+        width: 100,
+        height: 100
+      });
+
+      // service
+      const service = legendContainer.append('g')
+      .attr({
+        class: 'node service'
+      });
+
+      service.append('circle')
+      .attr({
+        r: serviceTopologyConfig.circle.radius,
+        transform: d => `translate(30, 100)`
+      });
+
+      service.append('text')
+      .attr({
+        transform: d => `translate(45, 100)`,
+        dy: '.35em'
+      })
+      .text('Service')
+        .style('fill-opacity', 1);
+
+      // slice
+      const slice = legendContainer.append('g')
+        .attr({
+          class: 'node slice'
+        });
+
+      slice.append('rect')
+        .attr({
+          width: 20,
+          height: 20,
+          x: -10,
+          y: -10,
+          transform: d => `translate(30, 130)`
+        });
+
+      slice.append('text')
+        .attr({
+          transform: d => `translate(45, 130)`,
+          dy: '.35em'
+        })
+        .text('Slices')
+        .style('fill-opacity', 1);
+
+      // instance
+      const instance = legendContainer.append('g')
+        .attr({
+          class: 'node instance'
+        });
+
+      instance.append('rect')
+        .attr({
+          width: 20,
+          height: 20,
+          x: -10,
+          y: -10,
+          transform: d => `translate(30, 160)`
+        });
+
+      instance.append('text')
+        .attr({
+          transform: d => `translate(45, 160)`,
+          dy: '.35em'
+        })
+        .text('Instances')
+        .style('fill-opacity', 1);
+    };
+
+    var _svg, _layout, _source;
+
+    var i = 0;
+
+    // given a canvas, a layout and a data source, draw a tree layout
+    const updateTree = (svg, layout, source) => {
+
+      //cache data
+      _svg = svg;
+      _layout = layout;
+      _source = source;
+
+      const maxDepth = ServiceRelation.depthOf(source);
+
+      const diagonal = d3.svg.diagonal()
+        .projection(d => [d.y, d.x]);
+
+      // Compute the new tree layout.
+      var nodes = layout.nodes(source).reverse(),
+        links = layout.links(nodes);
+
+      // Normalize for fixed-depth.
+      nodes.forEach(function(d) {
+        // position the child node horizontally
+        const step = (($window.innerWidth - (serviceTopologyConfig.widthMargin * 2)) / maxDepth);
+        d.y = d.depth * step;
+      });
+
+      // Update the nodes…
+      var node = svg.selectAll('g.node')
+        .data(nodes, function(d) { return d.id || (d.id = ++i); });
+
+      // Enter any new nodes at the parent's previous position.
+      var nodeEnter = node.enter().append('g')
+        .attr({
+          class: d => `node ${d.type}`,
+          transform: `translate(${source.y0}, ${source.x0})`
+        });
+
+      const subscriberNodes = nodeEnter.filter('.subscriber');
+      const internetNodes = nodeEnter.filter('.internet');
+      const serviceNodes = nodeEnter.filter('.service');
+      const instanceNodes = nodeEnter.filter('.instance');
+      const sliceNodes = nodeEnter.filter('.slice');
+
+      subscriberNodes.append('rect')
+        .attr(serviceTopologyConfig.square);
+
+      internetNodes.append('rect')
+        .attr(serviceTopologyConfig.square);
+
+      serviceNodes.append('circle')
+        .attr('r', 1e-6)
+        .style('fill', d => d._children ? 'lightsteelblue' : '#fff')
+        .on('click', serviceClick);
+
+      sliceNodes.append('rect')
+        .attr({
+          width: 20,
+          height: 20,
+          x: -10,
+          y: -10
+        });
+
+      instanceNodes.append('rect')
+        .attr({
+          width: 20,
+          height: 20,
+          x: -10,
+          y: -10,
+          class: d => d.active ?'' : 'active'
+        });
+
+      nodeEnter.append('text')
+        .attr({
+          x: d => d.children ? -serviceTopologyConfig.circle.selectedRadius -3 : serviceTopologyConfig.circle.selectedRadius + 3,
+          dy: '.35em',
+          transform: d => {
+            if (d.children && d.parent){
+              if(d.parent.x < d.x){
+                return 'rotate(-30)';
+              }
+              return 'rotate(30)';
+            }
+          },
+          'text-anchor': d => d.children ? 'end' : 'start'
+        })
+        .text(d => d.name)
+        .style('fill-opacity', 1e-6);
+
+      // Transition nodes to their new position.
+      var nodeUpdate = node.transition()
+        .duration(serviceTopologyConfig.duration)
+        .attr({
+          'transform': d => `translate(${d.y},${d.x})`
+        });
+
+      nodeUpdate.select('circle')
+        .attr('r', d => d.selected ? serviceTopologyConfig.circle.selectedRadius : serviceTopologyConfig.circle.radius)
+        .style('fill', d => d.selected ? 'lightsteelblue' : '#fff');
+
+      nodeUpdate.select('text')
+        .style('fill-opacity', 1);
+
+      // Transition exiting nodes to the parent's new position.
+      var nodeExit = node.exit().transition()
+        .duration(serviceTopologyConfig.duration)
+        .remove();
+
+      nodeExit.select('circle')
+        .attr('r', 1e-6);
+
+      nodeExit.select('text')
+        .style('fill-opacity', 1e-6);
+
+      // Update the links…
+      var link = svg.selectAll('path.link')
+        .data(links, function(d) { return d.target.id; });
+
+      // Enter any new links at the parent's previous position.
+      link.enter().insert('path', 'g')
+        .attr('class', d => `link ${d.target.type} ${d.target.active ? '' : 'active'}`)
+        .attr('d', function(d) {
+          var o = {x: source.x0, y: source.y0};
+          return diagonal({source: o, target: o});
+        });
+
+      // Transition links to their new position.
+      link.transition()
+        .duration(serviceTopologyConfig.duration)
+        .attr('d', diagonal);
+
+      // Transition exiting nodes to the parent's new position.
+      link.exit().transition()
+        .duration(serviceTopologyConfig.duration)
+        .attr('d', function(d) {
+          var o = {x: source.x, y: source.y};
+          return diagonal({source: o, target: o});
+        })
+        .remove();
+
+      // Stash the old positions for transition.
+      nodes.forEach(function(d) {
+        d.x0 = d.x;
+        d.y0 = d.y;
+      });
+    };
+
+    const serviceClick = function(d) {
+
+      $log.info('TODO emit an event to highlight VMs');
+
+      if(!d.service){
+        return;
+      }
+
+      // toggling selected status
+      d.selected = !d.selected;
+
+      var selectedNode = d3.select(this);
+
+      selectedNode
+        .transition()
+        .duration(serviceTopologyConfig.duration)
+        .attr('r', serviceTopologyConfig.circle.selectedRadius);
+    };
+
+    this.updateTree = updateTree;
+    this.drawLegend = drawLegend;
+  });
+
+}());
\ No newline at end of file