2018 - Built with blockbuilder.org
================
forked from willzjc's block: Matrix - Undergraduate Major Disciplines that Co-Occur
(which was originally forked from Forked from saraquigley's block: Undergraduate Major Disciplines that Co-Occur in Fall 2014)
xxxxxxxxxx
<meta charset="utf-8">
<title>Rating Co-occurrence</title>
<link href='https://fonts.googleapis.com/css?family=Lato:400,900,700,300' rel='stylesheet' type='text/css'>
<style>
#container {
min-height: 800px;
margin: 0px;
padding: 0px;
display: -webkit-flex;
display: flex;
-webkit-flex-flow: row;
flex-flow: row;
}
#container > graphic {
margin: 4px;
padding: 2px;
min-width: 300px;
background: #fff;
-webkit-flex: 1 6 66%;
flex: 1 6 66%;
-webkit-order: 1;
order: 1;
}
#container > aside {
margin: 4px;
padding: 5px;
background: #fff;
-webkit-flex: 1 6 34%;
flex: 1 6 34%;
-webkit-order: 2;
order: 2;
}
header, footer {
display: block;
margin: 4px;
/*padding: 5px;*/
min-height: 50px;
/* background: #ffeebb;*/
font-family: 'Lato', sans-serif;
font-size: 32px;
font-weight: 300;
/*letter-spacing: -2px;*/
padding: 0em 1em 0 0;
text-rendering: optimizeLegibility;
text-align: center;
}
/* Too narrow to support three columns */
@media all and (max-width: 640px) {
#container, #page {
-webkit-flex-flow: column;
flex-direction: column;
}
#container > article, #container > nav, #container > aside {
/* Return them to document order */
-webkit-order: 0;
order: 0;
}
#container > nav, #container > aside, header, footer {
min-height: 50px;
max-height: 50px;
}
}
.aside {
color: #525252;
font: 300 10pt "Lato", sans-serif;
}
#explain {
font: 400 14pt "Lato", sans-serif;
height: 110px;
padding-top: 50px;
}
svg {
font: 300 10pt 'Lato', sans-serif;
}
.background {
fill: #eee;
}
line {
stroke: #fff;
}
text.active {
text-shadow: 2px 2px 2px yellow;
}
</style>
<script src="https://d3js.org/d3.v3.js"></script>
<!-- <script type="text/JavaScript" src="d3.js"></script> -->
<script src="jsLouvain.js" type="text/JavaScript"></script>
<header>Matrix Transitions</header>
<div id="container">
<graphic id="matrix"></graphic>
<aside id="explain-aside">
<div id="explain"></div> <!-- explain -->
<div class="aside">
<p>Year & Term: <select id="termyear">
</select>
<p>Order By: <select id="order">
<option value="name">Currency Shortcode (Alphabetically)</option>
<option value="count">Currency Relation with highest trend ratings</option>
<option value="group">Clusters of Co-Occurring CCY Pairs</option>
</select>
<p>This adjacency matrix diagram shows major currencies that co-occur as part of relations.
<p>Each colored cell represents two pair relations were each declared by at least four students. Mouseover a cell and the Google's metric of trend ratings will display in the upper right.
<p>The darker the cell, the higher the number of rating for that combination of disciplines.
<!-- <p>Colors represent clusters<span id="colors"></span></p> -->
<p/>Don't forget to use the drop-down menu above to reorder the matrix and explore the data!
</div> <!-- aside -->
</aside> <!-- explain-aside -->
</div> <!-- container -->
<footer>...</footer><script>
// set the dimensions of the graphic
var margin = {top: 240, right: 0, bottom: 10, left: 200},
width = (window.innerWidth*0.66) - 240,
height = width,
orders ,
matrix = [],
nodeHash = {},
nodes = [],
edges = [],
n; // nodes.length
// define the scales
var x = d3.scale.ordinal().rangeBands([0, width]),
z = d3.scale.linear().domain([0, 3300]).clamp(true),
c = d3.scale.ordinal().range(["#5254a3","#e80c89","#7848ac","#aa9408","#49bc38","#016437","#8a8309","#5062a4","#da01a0"]);
// add the svg container
var svg = d3.select("#matrix").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + (margin.left - 20) + "," + (margin.top - 50) + ")");
// add the explainer text which displays when you mouseover a colored cell
var explainer = d3.select("#explain").append("foreignObject")
.attr("width", screen.width * 0.2)
.attr("height", 200)
.append("xhtml:text")
.attr("id", "linklabel")
.html("<strong>hint:</strong> mouseover a colored cell<br> to view multiple-major parings");
d3.csv("data.csv",function(error,data) {
// populate the term dropdown based on the data
// var termyears = d3.nest()
// .key(function(d) { return d.termyear}).sortKeys(d3.ascending)
// .rollup(function(leaves) { return leaves.length; })
// .entries(data);
// var termyears = ["2005 Spring", "2005 Fall", "2006 Spring", "2006 Fall", "2007 Spring", "2007 Fall", "2008 Spring", "2008 Fall", "2009 Spring", "2009 Fall", "2010 Spring", "2010 Fall", "2011 Spring", "2011 Fall", "2012 Spring", "2012 Fall", "2013 Spring", "2013 Fall", "2014 Spring", "2014 Fall", "2015 Spring"]
var termyears= ['2017 Fall']
var list = d3.select("#termyear");
list.selectAll("option")
.data(termyears)
.enter()
.append("option")
.attr("value", function(d) {return d;})
.text(function(d) {return d; });
// set the default to the latest term /y ear
d3.select("#termyear").property("selectedIndex", (d3.select("#termyear").property("length")-1));
// filter the data by term/year; spring 2015 is the current default
var filtered_data = data.filter(function(d) {return d.termyear === d3.select("#termyear").property('value');});
// The matrix is a grey background
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
createNetwork(filtered_data);
// Precompute the orders.
orders = {
name: d3.range(n).sort(function(a, b) { return d3.ascending(nodes[a].name, nodes[b].name); }),
count: d3.range(n).sort(function(a, b) { return nodes[b].count - nodes[a].count; }),
group: d3.range(n).sort(function(a, b) { return nodes[a].group - nodes[b].group; })
};
// The default sort order.
x.domain(orders.name);
// Each row is a group of things, such as a white horizontal line, a colored square, and a text label
var row = svg.selectAll(".row")
.data(matrix)
.enter().append("g")
.attr("class", "row")
.attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; })
.each(row); // call a row function to deal with the task of creating a square cell
row.append("line")
.attr("x2", width);
row.append("text")
.attr("x", -6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "end")
.text(function(d, i) { return nodes[i].name.replace(/&/g, '&'); })
.style("fill", function(d,i) { return c(nodes[i].group); })
.on("mouseover", mouseover_row)
.on("mouseout", mouseout_row);
// each column is also a group containing a line and some text, both rotated -90 degrees
var column = svg.selectAll(".column")
.data(matrix)
.enter().append("g")
.attr("class", "column")
.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
column.append("line")
.attr("x1", -width);
column.append("text")
.attr("x", 6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "start")
.text(function(d, i) { return nodes[i].name.replace(/&/g, '&'); })
.style("fill", function(d,i) { return c(nodes[i].group); })
.on("mouseover", mouseover_col)
.on("mouseout", mouseout_col);
// create the actual colored square cells and color them according to their group.
function row(row) {
var cell = d3.select(this).selectAll(".cell")
.data(row.filter(function(d) { return d.z; }))
.enter().append("rect")
.attr("class", "cell")
.attr("x", function(d) { return x(d.x); })
.attr("width", x.rangeBand())
.attr("height", x.rangeBand())
.style("fill-opacity", function(d) { return d.x == d.y ? 1 : z(d.z); })
.style("fill", function(d) { return d.x == d.y ? '#fff' : (nodes[d.x].group == nodes[d.y].group ? c(nodes[d.x].group) : '#03a423');})
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.append("title")
.text(function(d) {return d.x == d.y ? "" : d.z + " of Google Trend Rating";});
}
// when the Order By dropdown changes,
d3.select("#order").on("change", function() {
// clearTimeout(timeout); // This clears the delay set by WindowTimers.setTimeout ().
order(this.value);
});
// when the Term / Year dropdown changes,
d3.select("#termyear").on("change", function() {
var filtered_data = data.filter(function(d) {return d.termyear === d3.select("#termyear").property('value');});
createNetwork(filtered_data);
updateMatrix(matrix);
});
function order(value) {
//re-order the x domain according to the predefined sort orders
x.domain(orders[value]);
// set up the transition to last a total of 3 seconds
var t = svg.transition().duration(3000);
// have each row and column move after a delay that is a function of the index of its location
t.selectAll(".row")
.delay(function(d, i) { return x(i) * 5; })
.attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; })
.selectAll(".cell")
.delay(function(d) { return x(d.x) * 5; })
.attr("x", function(d) { return x(d.x); });
t.selectAll(".column")
.delay(function(d, i) { return x(i) * 5; })
.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
}
// other options for the layout above that don't make the diagonal white:
// .style("fill-opacity", function(d) { return d.x == d.y ? 0 : z(d.z); }) // make the diagonal grey
// .style("fill", function(d) { return d.x == d.y ? '#696969' : (nodes[d.x].group == nodes[d.y].group ? c(nodes[d.x].group) : '#696969');})
// the row & column labels of the matrix and the square cells all have associated mouseover events
function mouseover_row(p) {
var activeCells = d3.selectAll("rect.cell").filter(function(d) {
return d.y == p[0].y && d.x != d.y ;
}).style('stroke-width',3).style('stroke','yellow');
}
function mouseout_row(p) {
d3.selectAll("rect.cell").filter(function(d) {
return d.y == p[0].y && d.x != d.y ;
}).style('stroke-width',0);
}
function mouseover_col(p) {
var activeCells = d3.selectAll("rect.cell").filter(function(d) {
return d.x == p[0].y && d.x != d.y ;
}).style('stroke-width',3).style('stroke','yellow');
}
function mouseout_col(p) {
d3.selectAll("rect.cell").filter(function(d) {
return d.x == p[0].y && d.x != d.y ;
}).style('stroke-width',0);
}
function mouseover(p) {
var rowtext = d3.selectAll(".row text").filter(function(d, i) { return i == p.y; }),
coltext = d3.selectAll(".column text").filter(function(d, i) { return i == p.x; });
d3.selectAll(".row text").classed("active", function(d, i) { return i == p.y; });
d3.selectAll(".column text").classed("active", function(d, i) { return i == p.x; });
if(rowtext.text() === coltext.text()) {
d3.select("#linklabel").html("");
}
else {
d3.select("#linklabel").html(rowtext.text() + ' | ' + coltext.text() + '<br><br><span style="font-size: 18pt;"> ' + p.z + "</span> Google Trend Rating");
}
}
function mouseout() {
d3.selectAll("text").classed("active", false);
d3.select("#linklabel").html("");
}
// take the data output and turn it into a network.
function createNetwork(edgelist) {
edges = [], nodeHash = {};
var nestedEdges, edgelist;
// account for triple majors,such as cases where a student is triple major in say, econ-math-statistics : the Cal Answers query returns two rows for econ-stat, one for 1st/2nd major, other for 1st/3rd major so combine the two duplicative rows and sum their values.
nestedEdges = d3.nest().key(function(d) {return d.source + '-' + d.target;}).rollup(function(v) { return d3.sum(v, function(d) { return +d.weight; }); }).entries(edgelist);
// manipulate the combined data into a network data structure of edges and nodes
nestedEdges.forEach(function(d) {
d.source = d.key.slice(0,d.key.indexOf('-'));
d.target = d.key.slice(d.key.indexOf('-') + 1, d.key.length);
d.weight = d.values;
});
// clean up the data structure and call it edgelist
edgelist = nestedEdges.map(function(d) {return {'source':d.source, 'target':d.target, 'weight':d.weight};});
// for each edge whose value is greater than 4
edgelist.forEach(function (edge) {
if (edge.weight >= 4) {
// use nodeHash to keep track of things as the nodes and edges are created
if (!nodeHash[edge.source]) {
nodeHash[edge.source] = {id: edge.source, name: edge.source};
if(nodes.filter(function(d) {return d.name === edge.source;}).length == 0) {nodes.push(nodeHash[edge.source]);}
}
if (!nodeHash[edge.target]) {
nodeHash[edge.target] = {id: edge.target, name: edge.target};
if(nodes.filter(function(d) {return d.name === edge.target;}).length == 0) {nodes.push(nodeHash[edge.target]); }
}
edges.push({source: nodeHash[edge.source], target: nodeHash[edge.target], weight: edge.weight});
}
});
var newedges = edges.map(function(d) {
return {
'sourceName' : d.source.name,
'targetName' : d.target.name,
'source' : "",
'target' : "",
'weight' : +d.weight
}
});
// remove nodes that don't belong in the array
// nodes.filter(return function(d) {return d.name === edges;});
newnodes = [];
nodes.forEach(function (node,i){
var b = newedges.filter(function(d) {return d.sourceName === node.name || d.targetName === node.name; });
if(b.length > 0){
newnodes.push(node);
}
});
nodes = newnodes;
nodes.sort(function (a, b) {
if (a.name > b.name) {
return 1;
}
if (a.name < b.name) {
return -1;
}
// a must be equal to b
return 0;
});
// convert the index to numeric for each node
nodes = nodes.map(function(d,i) {return {name: d.name, index: i};});
// add the node id to the nodeHash to create numeric source/target values that the matrix expects
nodes.forEach(function(node) { nodeHash[node.name] = {id: node.index, name: node.name};});
//update the edges with numeric source/target values that the matrix expects
newedges = edges.map(function(d) {
return {
'sourceName' : d.source.name,
'targetName' : d.target.name,
'source' : nodeHash[d.source.name].id,
'target' : nodeHash[d.target.name].id,
'weight' : +d.weight
}
});
// create the matrix
createMatrix(nodes, newedges);
}
function modularityCensus(nodes, edges) {
nodes.forEach(function (node) {
var theseEdges = edges.filter(function(d) {return d.sourceName === node.name || d.targetName === node.name});
node.majors = d3.sum(theseEdges.map(function(d) {return +d.weight;}));
});
}
function createMatrix(nodes, edges) {
// reset the matrix
matrix = [];
// set up the data for the Louvain method of detecting communities
var node_data = nodes.map(function (d) {return d.name});
var edge_data = edges.map(function (d) {return {source: d.sourceName, target: d.targetName, weight: d.weight}; });
// pass the data to the Louvain algorithm. The logic in an external jsLouvain.js file is what returns the result:
var community = jLouvain().nodes(node_data).edges(edge_data);
var result = community();
// update each node with its group number
nodes.forEach(function (node) {
node.group = result[node.name]
});
// sum up the total number of edges for each node
modularityCensus(nodes, edges);
n = nodes.length;
// Compute index per node.
nodes.forEach(function(node, i) {
node.count = 0;
matrix[i] = d3.range(n).map(function(j) { return {x: j, y: i, z: 0}; });
});
// Convert edges to a matrix; count major occurrences.
edges.forEach(function(edge) {
matrix[edge.source][edge.target].z += edge.weight;
matrix[edge.target][edge.source].z += edge.weight;
matrix[edge.source][edge.source].z += edge.weight;
matrix[edge.target][edge.target].z += edge.weight;
nodes[edge.source].count += edge.weight;
nodes[edge.target].count += edge.weight;
});
// setTimeout is a web browser function that can be used to execute a code snippet after a specified delay.
// var timeout = setTimeout(function() {
// }, 5000);
} // end ?
function updateMatrix(matrix) {
orders = {};
// Precompute the orders.
orders = {
name: d3.range(n).sort(function(a, b) { return d3.ascending(nodes[a].name, nodes[b].name); }),
count: d3.range(n).sort(function(a, b) { return nodes[b].count - nodes[a].count; }),
group: d3.range(n).sort(function(a, b) { return nodes[a].group - nodes[b].group; })
};
// use the current sort selection
x.domain(orders[d3.select("#order").property("value")]);
var rows = svg.selectAll("g.row")
.data(matrix)
.each(row);
rows.select("text")
.attr("x", -6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "end")
.style("fill", function(d,i) { return c(nodes[i].group); })
.text(function(d, i) {
return nodes[i].name.replace(/&/g, '&');
});
rows.select("line")
.attr("x2", width);
var newrows = rows.enter()
.append("g")
.attr("class", "row")
.each(row);
newrows.append("line")
.attr("x2", width);
newrows.append("text")
.attr("x", -6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "end")
.style("fill", function(d,i) { return c(nodes[i].group); })
.text(function(d, i) {
return nodes[i].name.replace(/&/g, '&');
});
rows.exit().remove();
svg.selectAll("g.row text")
.on("mouseover", mouseover_row)
.on("mouseout", mouseout_row);
// each column is also a group containing a line and some text, both rotated -90 degrees
var columns = svg.selectAll("g.column")
.data(matrix)
columns.enter().append("g")
.attr("class", "column")
.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; })
.append("text")
.attr("x", 6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "start")
.text(function(d, i) { return nodes[i].name.replace(/&/g, '&'); })
.style("fill", function(d,i) { return c(nodes[i].group); })
.on("mouseover", mouseover_col)
.on("mouseout", mouseout_col);
columns.append("line")
.attr("x1", -width);
columns.exit().remove();
columns.select("line")
.attr("x1", -width);
columns.select("text")
.attr("x", 6)
.attr("y", x.rangeBand() / 2)
.attr("dy", ".32em")
.attr("text-anchor", "start")
.text(function(d, i) { return nodes[i].name.replace(/&/g, '&'); })
.style("fill", function(d,i) { return c(nodes[i].group); })
.on("mouseover", mouseover_col)
.on("mouseout", mouseout_col);
// create the actual colored square cells and color them according to their group.
function row(row) {
var cell = d3.select(this).selectAll(".cell")
.data(row.filter(function(d) {
return d.z;
}))
// .attr("x", function(d) { return x(d.x); })
.attr("width", x.rangeBand())
.attr("height", x.rangeBand())
.style("fill-opacity", function(d) { return d.x == d.y ? 1 : z(d.z); })
.style("fill", function(d) {
return d.x == d.y ? '#fff' : (nodes[d.x].group == nodes[d.y].group ? c(nodes[d.x].group) : '#565656');
})
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.text(function(d) {return d.x == d.y ? "" : d.z + " value pair rating";});
cell.enter().append("rect")
.attr("class", "cell")
// .attr("x", function(d) { return x(d.x); })
.attr("width", x.rangeBand())
.attr("height", x.rangeBand())
.style("fill-opacity", function(d) { return d.x == d.y ? 1 : z(d.z); })
.style("fill", function(d) {
return d.x == d.y ? '#fff' : (nodes[d.x].group == nodes[d.y].group ? c(nodes[d.x].group) : '#ff0586');
})
.on("mouseover", mouseover)
.on("mouseout", mouseout)
.append("title")
.text(function(d) {return d.x == d.y ? "" : d.z + " value pair relations";});
cell.exit().remove();
}
// set up the transition to last a total of 3 seconds
var t = svg.transition().duration(3000);
// have each row and column move after a delay that is a function of the index of its location
t.selectAll(".row")
.delay(function(d, i) { return x(i) * 5; })
.attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; })
.selectAll(".cell")
.delay(function(d) { return x(d.x) * 5; })
.attr("x", function(d) { return x(d.x); });
t.selectAll(".column")
.delay(function(d, i) { return x(i) * 5; })
.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
function key(d) {return d.x;}
// order(d3.select("#order").property("value"));
} //updateMatrix
});
</script>
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-53090028-12', 'auto');
ga('send', 'pageview');
</script>
</html>
Modified http://d3js.org/d3.v3.js to a secure url
https://d3js.org/d3.v3.js