slice_interactions dynamic update
authorScott Baker <smbaker@gmail.com>
Tue, 27 May 2014 23:55:00 +0000 (16:55 -0700)
committerScott Baker <smbaker@gmail.com>
Tue, 27 May 2014 23:55:00 +0000 (16:55 -0700)
planetstack/core/plus/sites.py
planetstack/core/plus/views.py
planetstack/templates/admin/dashboard/slice_interactions.html

index 66c5d00..5d670d0 100644 (file)
@@ -12,7 +12,10 @@ class AdminMixin(object):
     def get_urls(self):
         """Add our dashboard view to the admin urlconf. Deleted the default index."""
         from django.conf.urls import patterns, url
-        from views import DashboardCustomize, DashboardDynamicView, DashboardWelcomeView, DashboardAjaxView, SimulatorView, DashboardSummaryAjaxView, DashboardAddOrRemoveSliverView, DashboardUserSiteView, DashboardAnalyticsAjaxView, TenantViewData,TenantCreateSlice, TenantAddOrRemoveSliverView, TenantPickSitesView, TenantDeleteSliceView,TenantUpdateSlice
+        from views import DashboardCustomize, DashboardDynamicView, DashboardWelcomeView, DashboardAjaxView, SimulatorView, \
+                          DashboardSummaryAjaxView, DashboardAddOrRemoveSliverView, DashboardUserSiteView, DashboardAnalyticsAjaxView, \
+                          TenantViewData,TenantCreateSlice, TenantAddOrRemoveSliverView, TenantPickSitesView, TenantDeleteSliceView, \
+                          TenantUpdateSlice, DashboardSliceInteractions
 
         urls = super(AdminMixin, self).get_urls()
         del urls[0]
@@ -21,6 +24,8 @@ class AdminMixin(object):
                     name="index"),
                url(r'^test/', self.admin_view(DashboardUserSiteView.as_view()),
                     name="test"),
+               url(r'^sliceinteractions/(?P<name>\w+)/$', self.admin_view(DashboardSliceInteractions.as_view()),
+                    name="interactions"),
                url(r'^dashboard/(?P<name>\w+)/$', self.admin_view(DashboardDynamicView.as_view()),
                     name="dashboard"),
               url(r'^customize/$', self.admin_view(DashboardCustomize.as_view()),
index f57f587..886db20 100644 (file)
@@ -16,6 +16,7 @@ from django.views.decorators.csrf import csrf_exempt
 from django.http import HttpResponse, HttpResponseServerError, HttpResponseForbidden
 from django.core import urlresolvers
 from django.contrib.gis.geoip import GeoIP
+from django.db.models import Q
 from ipware.ip import get_ip
 from operator import itemgetter, attrgetter
 import traceback
@@ -61,10 +62,6 @@ class DashboardDynamicView(TemplateView):
             if (fn=="tenant"):
                 # fix for tenant view - it writes html to a div called tabs-5
                 template = '<div id="tabs-5"></div>' + template
-            if (fn=="slice_interactions"):
-                # fix for slice_interactions - it gives its container div a 40px
-                # margin, and then positions it's header at -40px
-                template = '<div id="tabs-4">' + template + '</div>'
             return template
         except:
             return "failed to open %s" % fn
@@ -114,7 +111,7 @@ class DashboardDynamicView(TemplateView):
         head_template = self.head_template
         tail_template = self.tail_template
 
-        t = template.Template(head_template + self.readDashboard(fn) + self.tail_template)
+        t = template.Template(head_template + self.readDashboard(name) + self.tail_template)
 
         response_kwargs = {}
         response_kwargs.setdefault('content_type', self.content_type)
@@ -341,7 +338,7 @@ def get_free_port():
         inuse[vs.peer_portnum]=True
         inuse[vs.replicate_portnum]=True
     for network in Network.objects.all():
-        if not network_ports:
+        if not network.ports:
             continue
         network_ports = [x.strip() for x in network.ports.split(",")]
         for network_port in network_ports:
@@ -797,3 +794,114 @@ class DashboardCustomize(View):
 \r
         return HttpResponse(json.dumps("Success"), mimetype='application/javascript')\r
 
+class DashboardSliceInteractions(View):
+    def get(self, request, name="users", **kwargs):
+        colors = ["#005586", "#6ebe49", "orange", "#707170", "#00c4b3", "#077767", "dodgerblue", "#a79b94", "#c4e76a", "red"]
+
+        groups = []
+        matrix = []
+        slices = list(Slice.objects.all())
+
+        slices = [x for x in slices if not self.isEmpty(x,name)]
+
+        for i,slice in enumerate(slices):
+            groups.append({"name": slice.name, "color": colors[i%len(colors)]})
+            matrix.append(self.buildMatrix(slice, slices, name))
+
+        result = {"groups": groups, "matrix": matrix}
+
+        if name=="users":
+            result["title"] = "Slice interactions by user privilege"
+            result["objectName"] = "users"
+        elif name=="networks":
+            result["title"] = "Slice interactions by network membership"
+            result["objectName"] = "networks"
+        elif name=="sites":
+            result["title"] = "Slice interactions by site ownership"
+            result["objectName"] = "sites"
+        elif name=="sliver_sites":
+            result["title"] = "Slice interactions by sliver sites"
+            result["objectName"] = "sites"
+        elif name=="sliver_nodes":
+            result["title"] = "Slice interactions by sliver nodes"
+            result["objectName"] = "nodes"
+
+        return HttpResponse(json.dumps(result), mimetype='application/javascript')
+
+    def buildMatrix(self, slice, slices, name):
+        row = []
+        for otherSlice in slices:
+            if (otherSlice == slice):
+                row.append(self.getCount(slice, name))
+            else:
+                row.append(self.getInCommon(slice, otherSlice, name))
+        return row
+
+    def other_slice_sites(self, slice):
+        ids=[]
+        for sliver in Sliver.objects.all():
+            if sliver.slice!=slice:
+                if sliver.node.site.id not in ids:
+                    ids.append(sliver.node.site.id)
+        return ids
+
+    def other_slice_nodes(self, slice):
+        ids=[]
+        for sliver in Sliver.objects.all():
+            if sliver.slice!=slice:
+                if sliver.node.id not in ids:
+                    ids.append(sliver.node.id)
+        return ids
+
+    def getIds(self, slice, name, onlySelf=False):
+        ids=[]
+        if name=="users":
+            for sp in slice.slice_privileges.all():
+                if (not onlySelf) or (len(sp.user.slice_privileges.all())==1):
+                    if sp.user.id not in ids:
+                        ids.append(sp.user.id)
+        elif name=="networks":
+            for sp in slice.networkslice_set.all():
+                if (not onlySelf) or (len(sp.network.networkslice_set.all())==1):
+                    if sp.network.id not in ids:
+                        ids.append(sp.network.id)
+        elif name=="sites":
+            ids = [slice.site.id]
+        elif name=="sliver_sites":
+            for sp in slice.slivers.all():
+                 if sp.node.site.id not in ids:
+                     ids.append(sp.node.site.id)
+            if onlySelf:
+                other_slice_sites = self.other_slice_sites(slice)
+                ids = [x for x in ids if x not in other_slice_sites]
+        elif name=="sliver_nodes":
+            for sp in slice.slivers.all():
+                 if sp.node.id not in ids:
+                     ids.append(sp.node.id)
+            if onlySelf:
+                other_slice_nodes = self.other_slice_nodes(slice)
+                ids = [x for x in ids if x not in other_slice_nodes]
+
+        return ids
+
+    def inCommonIds(self, ids1, ids2):
+        count = 0
+        for id in ids1:
+            if id in ids2:
+                count+=1
+        return count
+
+    def getCount(self, slice, name):
+        if (name in ["users", "networks", "sites", "sliver_nodes", "sliver_sites"]):
+            return len(self.getIds(slice,name,onlySelf=True))
+
+    def getInCommon(self, slice, otherSlice, name):
+        if (name in ["users", "networks", "sites", "sliver_nodes", "sliver_sites"]):
+            slice_ids = self.getIds(slice,name)
+            otherSlice_ids = self.getIds(otherSlice,name)
+            return self.inCommonIds(slice_ids, otherSlice_ids)
+
+    def isEmpty(self, slice, name):
+        if (name in ["users", "networks", "sites", "sliver_nodes", "sliver_sites"]):
+            return (len(self.getIds(slice,name)) == 0)
+
index e52bdd7..6fafc5c 100644 (file)
@@ -2,7 +2,6 @@
 <style>
 #slice_interaction_chart_placeholder {
     text-align: center;
-    margin: -40px 20px 20px 0px;
     color:#fff;
     position: relative;
     height: 100%;
@@ -61,33 +60,34 @@ button:disabled {
     color:red;
     background-color: lightyellow;
 }
-#sliceEngagementTitle {
-    margin-top: -50px;
-    margin-left: -40px;
-
+.sliceinteractions_column {
+  display: table-cell;\r
+  padding: 10px;\r
+}
+#interactions_function {
+  width: 125px;
 }
 
 </style>
-<h3 id="sliceEngagementTitle"> Involvement between Slices by User Engagement</h3>
+
+<div class="row">
+    <div class="sliceinteractions_column">
+    <select id="interactions_function">
+        <option value="networks">networks</option>
+        <option value="users">users</option>
+        <option value="owner sites">sites</option>
+        <option value="sliver_sites">sliver_sites</option>
+        <option value="sliver_nodes">sliver_nodes</option>
+    </select>
+    </div>
+    <div class="sliceinteractions_column">
+    <h3 id="sliceEngagementTitle">Slice Interactions</h3>
+    </div>
+</div>
+
 <div id="slice_interaction_chart_placeholder"></div>
 
 <script>
-    
-  /*  d3.json(datasetURL, function(error, matrix) {
-
-    if (error) {alert("Error reading file: ", error.statusText); return; }
-    
-    */
-    actualData =  [[2, 2, 2, 2, 2, 2, 2, 1, 2, 1],
- [2, 7, 3, 7, 7, 3, 2, 2, 7, 2],
- [2, 3, 4, 3, 4, 2, 2, 2, 3, 2],
- [2, 7, 3, 7, 7, 3, 2, 2, 7, 2],
- [2, 7, 4, 7, 15, 3, 2, 6, 7, 6],
- [2, 3, 2, 3, 3, 3, 2, 1, 3, 1],
- [2, 2, 2, 2, 2, 2, 2, 1, 2, 1],
- [1, 2, 2, 2, 6, 1, 1, 6, 2, 6],
- [2, 7, 3, 7, 7, 3, 2, 2, 7, 2],
- [1, 2, 2, 2, 6, 1, 1, 6, 2, 6]];
 
 // Chord Diagram for showing Collaboration between users found in an anchor query
 // Collaboration View
@@ -98,80 +98,74 @@ var width = 600,
     outerRadius = Math.min(width, height) / 2 - 100,
     innerRadius = outerRadius - 18;
 
-var dataset = "#allinfo";
-//string url for the initial data set
-//would usually be a file path url, here it is the id
-//selector for the <pre> element storing the data
-
 //create number formatting functions
 var formatPercent = d3.format("%");
 var numberWithCommas = d3.format("0,f");
 
-//create the arc path data generator for the groups
-var arc = d3.svg.arc()
-    .innerRadius(innerRadius)
-    .outerRadius(outerRadius);
-
-//create the chord path data generator for the chords
-var path = d3.svg.chord()
-    .radius(innerRadius);
-
 //define the default chord layout parameters
 //within a function that returns a new layout object;
 //that way, you can create multiple chord layouts
 //that are the same except for the data.
 function getDefaultLayout() {
     return d3.layout.chord()
-//    .padding(0.03)
     .sortSubgroups(d3.descending)
     .sortChords(d3.ascending);
-}  
+}
 var last_layout; //store layout between updates
-var users = [{"color": "#005586", "name": "Owl"}, {"color": "#6ebe49", "name": "DnsDemux"}, {"color": "orange", "name": "Infrastructure"}, {"color": "#707170", "name": "HyperCache"}, {"color": "#00c4b3", "name": "Syndicate"}, {"color": "#077767", "name": "Hadoop"}, {"color": "dodgerblue", "name": "Stork"}, {"color": "#a79b94", "name": "test2"}, {"color": "#c4e76a", "name": "DnsRedir"}, {"color": "red", "name": "test"}];
-
-/*** Initialize the visualization ***/
-var g = d3.select("#slice_interaction_chart_placeholder").append("svg")
-        .attr("width", width)
-        .attr("height", height)
-    .append("g")
-        .attr("id", "circle")
-        .attr("transform", 
-              "translate(" + width / 2 + "," + height / 2 + ")");
-//the entire graphic will be drawn within this <g> element,
-//so all coordinates will be relative to the center of the circle
-
-g.append("circle")
-    .attr("r", outerRadius);
-//this circle is set in CSS to be transparent but to respond to mouse events
-//It will ensure that the <g> responds to all mouse events within
-//the area, even after chords are faded out.
-
-/*** Read in the neighbourhoods data and update with initial data matrix ***/
-//normally this would be done with file-reading functions
-//d3.csv and d3.json and callbacks, 
-//instead we're using the string-parsing functions
-//d3.csv.parse and JSON.parse, both of which return the data,
-//no callbacks required.
-
-
-    updateChords(dataset); 
-    //call the update method with the default dataset
-    
-//} ); //end of d3.csv function
+var g;
+var arc;
+var path;
+
+function init_visualization() {
+    arc = d3.svg.arc()
+        .innerRadius(innerRadius)
+        .outerRadius(outerRadius);
+
+    path = d3.svg.chord()
+        .radius(innerRadius);
+
+
+    /*** Initialize the visualization ***/
+    g = d3.select("#slice_interaction_chart_placeholder").append("svg")
+            .attr("width", width)
+            .attr("height", height)
+        .append("g")
+            .attr("id", "circle")
+            .attr("transform",
+                  "translate(" + width / 2 + "," + height / 2 + ")");
+    //the entire graphic will be drawn within this <g> element,
+    //so all coordinates will be relative to the center of the circle
+
+    g.append("circle")
+        .attr("r", outerRadius);
+}
+
+$( document ).ready(function() {
+    init_visualization();
+    $('#interactions_function').change(function() {
+         updateInteractions();
+     });
+    updateInteractions();
+});
+
+function updateInteractions() {
+ $( "#sliceEngagementTitle" ).html("<h3>Loading...</h3>");
+ $.ajax({
+    url : "/admin/sliceinteractions/" + $("#interactions_function :selected").text() + "/",
+    dataType : 'json',
+    type : 'GET',
+    success: function(newData)
+    {
+        $( "#sliceEngagementTitle" ).html("<h3>" + newData["title"] + "</h3>");
+        updateChords(newData["groups"], newData["matrix"], newData["objectName"])
+    }
+   });
+}
 
 
 /* Create OR update a chord layout from a data matrix */
-function updateChords( datasetURL ) {
-    
-  /*  d3.json(datasetURL, function(error, matrix) {
+function updateChords( users, matrix, objectName ) {
 
-    if (error) {alert("Error reading file: ", error.statusText); return; }
-    
-    */
-    //var matrix = JSON.parse( d3.select(datasetURL).text() );
-    var matrix = actualData;
-        // instead of d3.json
-    
     /* Compute chord layout. */
     layout = getDefaultLayout(); //create a new layout object
     layout.matrix(matrix);
@@ -189,7 +183,7 @@ function updateChords( datasetURL ) {
             .duration(1500)
             .attr("opacity", 0)
             .remove(); //remove after transitions are complete
-    
+
     var newGroups = groupG.enter().append("g")
         .attr("class", "group");
     //the enter selection is stored in a variable so we can
@@ -202,7 +196,7 @@ function updateChords( datasetURL ) {
     //Update the (tooltip) title text based on the data
     groupG.select("title")
         .text(function(d, i) {
-            return "Slice (" + users[i].name + 
+            return "Slice (" + users[i].name +
                 ") "
                 ;
         });
@@ -277,19 +271,19 @@ function updateChords( datasetURL ) {
         .text(function(d) {
             if (users[d.target.index].name !== users[d.source.index].name) {
                 return [numberWithCommas(d.source.value),
-                        " users in common between \n",
+                        " " + objectName + " in common between \n",
                         users[d.source.index].name,
                         " and ",
                         users[d.target.index].name,
                         "\n"
-                        ].join(""); 
+                        ].join("");
                     //joining an array of many strings is faster than
-                    //repeated calls to the '+' operator, 
+                    //repeated calls to the '+' operator,
                     //and makes for neater code!
-            } 
+            }
             else { //source and target are the same
-                return numberWithCommas(d.source.value) 
-                    + " users are only in Slice (" 
+                return numberWithCommas(d.source.value)
+                    + " " + objectName + " are only in Slice ("
                     + users[d.source.index].name + ")";
             }
         });
@@ -331,7 +325,7 @@ function updateChords( datasetURL ) {
             chordPaths.classed("fade", false);
     });
     */
-    
+
     last_layout = layout; //save for next update
     
 //  }); //end of d3.json
@@ -416,6 +410,8 @@ function chordTween(oldLayout) {
         }
         else {
             //create a zero-width chord object
+/*          XXX SMBAKER: the code commented out below was causing an error,
+                  so I replaced it with the following code from stacktrace
             if (oldLayout) {
                 var oldGroups = oldLayout.groups().filter(function(group) {
                         return ( (group.index == d.source.index) ||
@@ -426,7 +422,7 @@ function chordTween(oldLayout) {
                     //the OR in target is in case source and target are equal
                     //in the data, in which case only one group will pass the
                     //filter function
-                
+
                 if (d.source.index != old.source.index ){
                     //swap source and target to match the new data
                     old = {
@@ -436,13 +432,22 @@ function chordTween(oldLayout) {
                 }
             }
             else old = d;
-                
+
             var emptyChord = {
                 source: { startAngle: old.source.startAngle,
                          endAngle: old.source.startAngle},
                 target: { startAngle: old.target.startAngle,
                          endAngle: old.target.startAngle}
             };
+            tween = d3.interpolate( emptyChord, d );*/
+
+            //create a zero-width chord object
+            var emptyChord = {\r
+                source: { startAngle: d.source.startAngle,\r
+                         endAngle: d.source.startAngle},\r
+                target: { startAngle: d.target.startAngle,\r
+                         endAngle: d.target.startAngle}\r
+            };\r
             tween = d3.interpolate( emptyChord, d );
         }