slice_interactions.html from node33
diff --git a/planetstack/templates/admin/dashboard/slice_interactions.html b/planetstack/templates/admin/dashboard/slice_interactions.html
new file mode 100644
index 0000000..e52bdd7
--- /dev/null
+++ b/planetstack/templates/admin/dashboard/slice_interactions.html
@@ -0,0 +1,482 @@
+<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
+<style>
+#slice_interaction_chart_placeholder {
+    text-align: center;
+    margin: -40px 20px 20px 0px;
+    color:#fff;
+    position: relative;
+    height: 100%;
+    width: 100%;
+}
+.dependencyWheel {
+    font: 10px sans-serif;
+}
+form .btn-primary {
+    margin-top: 25px;
+}
+.labeltext {
+    color: #fff;
+}
+#circle circle {
+    fill: none;
+    pointer-events: all;
+}
+path.chord {
+    stroke: #000;
+    stroke-width: .10px;
+    transition: opacity 0.3s;
+}
+#circle:hover path.fade {
+    opacity: 0;
+}
+a {
+    text-decoration: none;
+    border-bottom: 1px dotted #666;
+    color: #999;
+}
+.more a {
+    color: #666;
+}
+.by a {
+    color: #fff;
+}
+a:hover {
+    color: #45b8e2;
+}
+a:not(:hover) {
+    text-decoration: none;
+}
+text {
+    fill: black;
+}
+svg {
+    font-size: 12px;
+    font-weight: bold;
+    color: #999;
+    font-family:'Arial', sans-serif;
+    min-height: 100%;
+    min-width: 100%;
+}
+button:disabled {
+    color:red;
+    background-color: lightyellow;
+}
+#sliceEngagementTitle {
+    margin-top: -50px;
+    margin-left: -40px;
+
+}
+
+</style>
+<h3 id="sliceEngagementTitle"> Involvement between Slices by User Engagement</h3>
+<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
+//
+
+var width = 600,
+    height = 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
+
+
+/* Create OR update a chord layout from a data matrix */
+function updateChords( datasetURL ) {
+    
+  /*  d3.json(datasetURL, function(error, matrix) {
+
+    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);
+ 
+    /* Create/update "group" elements */
+    var groupG = g.selectAll("g.group")
+        .data(layout.groups(), function (d) {
+            return d.index; 
+            //use a key function in case the 
+            //groups are sorted differently between updates
+        });
+    
+    groupG.exit()
+        .transition()
+            .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
+    //enter the <path>, <text>, and <title> elements as well
+
+    
+    //Create the title tooltip for the new groups
+    newGroups.append("title");
+    
+    //Update the (tooltip) title text based on the data
+    groupG.select("title")
+        .text(function(d, i) {
+            return "Slice (" + users[i].name + 
+                ") "
+                ;
+        });
+
+    //create the arc paths and set the constant attributes
+    //(those based on the group index, not on the value)
+    newGroups.append("path")
+        .attr("id", function (d) {
+            return "group" + d.index;
+            //using d.index and not i to maintain consistency
+            //even if groups are sorted
+        })
+        .style("fill", function (d) {
+            return users[d.index].color;
+        });
+    
+    //update the paths to match the layout
+    groupG.select("path") 
+        .transition()
+            .duration(1500)
+            .attr("opacity", 0.5) //optional, just to observe the transition
+        .attrTween("d", arcTween( last_layout ))
+       //     .transition().duration(100).attr("opacity", 1) //reset opacity
+        ;
+    
+    //create the group labels
+    newGroups.append("svg:text")
+        .attr("xlink:href", function (d) {
+            return "#group" + d.index;
+        })
+        .attr("dy", ".35em")
+        .attr("color", "#fff")
+        .text(function (d) {
+            return users[d.index].name;
+        });
+
+    //position group labels to match layout
+    groupG.select("text")
+        .transition()
+            .duration(1500)
+            .attr("transform", function(d) {
+                d.angle = (d.startAngle + d.endAngle) / 2;
+                //store the midpoint angle in the data object
+                
+                return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")" +
+                    " translate(" + (innerRadius + 26) + ")" + 
+                    (d.angle > Math.PI ? " rotate(180)" : " rotate(0)"); 
+                //include the rotate zero so that transforms can be interpolated
+            })
+            .attr("text-anchor", function (d) {
+                return d.angle > Math.PI ? "end" : "begin";
+            });
+    
+    
+    /* Create/update the chord paths */
+    var chordPaths = g.selectAll("path.chord")
+        .data(layout.chords(), chordKey );
+            //specify a key function to match chords
+            //between updates
+        
+    
+    //create the new chord paths
+    var newChords = chordPaths.enter()
+        .append("path")
+        .attr("class", "chord");
+    
+    // Add title tooltip for each new chord.
+    newChords.append("title");
+    
+    // Update all chord title texts
+    chordPaths.select("title")
+        .text(function(d) {
+            if (users[d.target.index].name !== users[d.source.index].name) {
+                return [numberWithCommas(d.source.value),
+                        " users in common between \n",
+                        users[d.source.index].name,
+                        " and ",
+                        users[d.target.index].name,
+                        "\n"
+                        ].join(""); 
+                    //joining an array of many strings is faster than
+                    //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 (" 
+                    + users[d.source.index].name + ")";
+            }
+        });
+
+    //handle exiting paths:
+    chordPaths.exit().transition()
+        .duration(1500)
+        .attr("opacity", 0)
+        .remove();
+
+    //update the path shape
+    chordPaths.transition()
+        .duration(1500)
+        //.attr("opacity", 0.5) //optional, just to observe the transition
+        .style("fill", function (d) {
+            return users[d.source.index].color;
+        })
+        .attrTween("d", chordTween(last_layout))
+        //.transition().duration(100).attr("opacity", 1) //reset opacity
+    ;
+
+    //add the mouseover/fade out behaviour to the groups
+    //this is reset on every update, so it will use the latest
+    //chordPaths selection
+    groupG.on("mouseover", function(d) {
+        chordPaths.classed("fade", function (p) {
+            //returns true if *neither* the source or target of the chord
+            //matches the group that has been moused-over
+            return ((p.source.index != d.index) && (p.target.index != d.index));
+        });
+    });
+    //the "unfade" is handled with CSS :hover class on g#circle
+    //you could also do it using a mouseout event:
+    /*
+    g.on("mouseout", function() {
+        if (this == g.node() )
+            //only respond to mouseout of the entire circle
+            //not mouseout events for sub-components
+            chordPaths.classed("fade", false);
+    });
+    */
+    
+    last_layout = layout; //save for next update
+    
+//  }); //end of d3.json
+}
+
+function arcTween(oldLayout) {
+    //this function will be called once per update cycle
+    
+    //Create a key:value version of the old layout's groups array
+    //so we can easily find the matching group 
+    //even if the group index values don't match the array index
+    //(because of sorting)
+    var oldGroups = {};
+    if (oldLayout) {
+        oldLayout.groups().forEach( function(groupData) {
+            oldGroups[ groupData.index ] = groupData;
+        });
+    }
+    
+    return function (d, i) {
+        var tween;
+        var old = oldGroups[d.index];
+        if (old) { //there's a matching old group
+            tween = d3.interpolate(old, d);
+        }
+        else {
+            //create a zero-width arc object
+            var emptyArc = {startAngle:d.startAngle,
+                            endAngle:d.startAngle};
+            tween = d3.interpolate(emptyArc, d);
+        }
+        
+        return function (t) {
+            return arc( tween(t) );
+        };
+    };
+}
+
+function chordKey(data) {
+    return (data.source.index < data.target.index) ?
+        data.source.index  + "-" + data.target.index:
+        data.target.index  + "-" + data.source.index;
+    
+    //create a key that will represent the relationship
+    //between these two groups *regardless*
+    //of which group is called 'source' and which 'target'
+}
+function chordTween(oldLayout) {
+    //this function will be called once per update cycle
+    
+    //Create a key:value version of the old layout's chords array
+    //so we can easily find the matching chord 
+    //(which may not have a matching index)
+    
+    var oldChords = {};
+    
+    if (oldLayout) {
+        oldLayout.chords().forEach( function(chordData) {
+            oldChords[ chordKey(chordData) ] = chordData;
+        });
+    }
+    
+    return function (d, i) {
+        //this function will be called for each active chord
+        
+        var tween;
+        var old = oldChords[ chordKey(d) ];
+        if (old) {
+            //old is not undefined, i.e.
+            //there is a matching old chord value
+            
+            //check whether source and target have been switched:
+            if (d.source.index != old.source.index ){
+                //swap source and target to match the new data
+                old = {
+                    source: old.target,
+                    target: old.source
+                };
+            }
+            
+            tween = d3.interpolate(old, d);
+        }
+        else {
+            //create a zero-width chord object
+            if (oldLayout) {
+                var oldGroups = oldLayout.groups().filter(function(group) {
+                        return ( (group.index == d.source.index) ||
+                                 (group.index == d.target.index) )
+                    });
+                old = {source:oldGroups[0],
+                           target:oldGroups[1] || oldGroups[0] };
+                    //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 = {
+                        source: old.target,
+                        target: old.source
+                    };
+                }
+            }
+            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 );
+        }
+
+        return function (t) {
+            //this function calculates the intermediary shapes
+            return path(tween(t));
+        };
+    };
+}
+
+
+/* Activate the buttons and link to data sets */
+d3.select("#ReadersButton").on("click", function () {
+    updateChords( "#readinfo" );
+    //replace this with a file url as appropriate
+    
+    //enable other buttons, disable this one
+    disableButton(this);
+});
+
+d3.select("#ContributorsButton").on("click", function() {
+    updateChords( "#contributorinfo" );
+    disableButton(this);
+});
+
+d3.select("#AllUsersButton").on("click", function() {
+    updateChords( "#allinfo" );
+    disableButton(this);
+});
+function disableButton(buttonNode) {
+    d3.selectAll("button")
+        .attr("disabled", function(d) {
+            return this === buttonNode? "true": null;
+        });
+}
+
+</script>