<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<style>
#slice_interaction_chart_placeholder {
    text-align: center;
    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;
}
.sliceinteractions_column {
  display: table-cell;
  padding: 10px;
}
#interactions_function {
  width: 125px;
}

</style>

<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="instance_sites">instance_sites</option>
        <option value="instance_nodes">instance_nodes</option>
    </select>
    </div>
    <div class="sliceinteractions_column">
    <h3 id="sliceEngagementTitle">Slice Interactions</h3>
    </div>
</div>

<div id="slice_interaction_chart_placeholder"></div>

<script>

// 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;

//create number formatting functions
var formatPercent = d3.format("%");
var numberWithCommas = d3.format("0,f");

//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()
    .sortSubgroups(d3.descending)
    .sortChords(d3.ascending);
}
var last_layout; //store layout between updates
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( users, matrix, objectName ) {

    /* 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),
                        " " + objectName + " 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)
                    + " " + objectName + " 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
    ;

    // XXX SMBAKER: The way the text was added with newGroups, it's only
    //   computed when a node is created. This is a problem if we redraw the
    //   graph with a different set of nodes, because the old labels will
    //   stick. So, I added this, which *seems* to cause the labels to be
    //   recomputed.
    groupG.selectAll("text")
        .text(function (d) {
            return users[d.index].name;
        });

    //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);
    });
    */

    // XXX smbaker: there's a bug where if you hilight a slice of the chord
    //   graph, and then update the data, the freshly drawn graph is missing
    //   some of the chords. Flipping the fade bit seems to fix that.
    chordPaths.classed("fade", true);
    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
/*          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) ||
                                 (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 );*/

            //create a zero-width chord object
            var emptyChord = {
                source: { startAngle: d.source.startAngle,
                         endAngle: d.source.startAngle},
                target: { startAngle: d.target.startAngle,
                         endAngle: d.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>
