Name: Erika Siregar
- Assignment : Visualization Implementation (VI7)
- Course: Information Visualization (CS725)
- Semester : Spring 2016
- Node-link diagram: Force-Directed Graph
- Adjacent Matrix : Les Misérables Co-occurrence
- Data : contiguous-usa.dat
*I grouped the data based on their GMT regions. There are 4 regions being displayed: GMT+5 (blue color), GMT+6 (red color), GMT+7 (green color), and GMT+8 (orange color).
*I encode DC with bigger size of node to make it more obvious to users. So, users can easily compare or measure the distance between DC and other states.
*From the node link we can easily see the states that directly adjacent to each other. The adjacent states are connected with a link. The interesting thing to be considered here is since I grouped the nodes by region, we can directly see which states that connect to each other but have different GMT regions. For example: Indiana (IN) is located in GMT+8 region (orange color), but it is neighboring with Illinois (IL) which is located in GMT+7 (green color).
*Another interesting point is users can measure the distance between one state to another by counting the number of links that separate them. For example, there are two links between Oregon and Wyoming through Idaho. It means Oregon is located 2 states away from Wyoming, if we go through Idaho. On the other hand, Oregon and Wyoming are located 3 states away from each other if we go through Nevada and Utah, since there are 3 links that separate them.
*I found the matrix view is more difficult to comprehend compare to node-link diagram. I provide a dropdown list where user can choose how they like to order the states: either by name, number of connections, or time region.
Here are some insights that I could gain from the adjacent matrix:
xxxxxxxxxx
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.node-active{
stroke: red;
stroke-width: 1.5px;
}
.node-inactive{
opacity: .3;
}
.link {
stroke: #555;
stroke-opacity: .7;
}
.link-active {
stroke: red;
stroke-width: 1.5px;
stroke-opacity: 1;
}
.link-inactive{
opacity: .3;
}
.overlay {
fill: none;
pointer-events: all;
}
.tooltip {
position: absolute;
display: none;
pointer-events: none;
background: #fff;
padding: 5px;
text-align: left;
border: solid #ccc 1px;
color: #666;
}
.background {
fill: #eee;
}
line {
stroke: #fff;
}
text.active {
fill: red;
}
</style>
<body>
<div style="overflow-y: scroll; height: 500px">
<div id="vis1"></div>
<div id="vis2">
<p>Order: <select id="order">
<option value="name">State Name</option>
<option value="count">Number of Connection</option>
<option value="group">Timezone</option>
</select>
</div>
</div>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 860,
height = 500;
var color = d3.scale.category10();
var force = d3.layout.force()
.charge(-120)
.linkDistance(30)
.size([width, height]);
var tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
var svg = d3.select("#vis1").append("svg")
.attr("width", width)
.attr("height", height);
d3.json("contiguous-usa.json", function(error, graph) {
if (error) throw error;
var zoom = d3.behavior.zoom()
.scaleExtent([1, 10])
.on("zoom", zoomed);
var drag = d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", dragstarted)
.on("drag", dragged)
.on("dragend", dragended);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var container = svg.append("g");
var link = container.append("g")
.attr("class", "links")
.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function(d) { return Math.sqrt(d.value); });
var node = container.append("g")
.attr("class", "nodes")
.selectAll(".node")
.data(graph.nodes)
.enter().append("circle")
.attr("class", "node")
.attr("r", function(d) {
if(d.id === "DC") {
return d.value * 2 + 20;
} else {
return d.value * 2 + 12;
}
})
.style("fill", function(d) { return color(d.group); })
.style("stroke", function(d) { if(d.id === "DC") return "red" })
.style("stroke-width", function(d) { if(d.id === "DC") return 5 })
.call(drag);
var linkedByIndex = {};
graph.links.forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index];
}
node.on("mouseover", function(d){
node.classed("node-inactive", function(o) {
thisOpacity = isConnected(d, o) ? true : false;
return !thisOpacity;
});
d3.select(this).classed("node-active", true);
d3.select(this).classed("node-inactive", false);
node.classed("node-active", function(o) {
thisOpacity = isConnected(d, o) ? true : false;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.classed("link-active", function(o) {
return o.source === d || o.target === d ? true : false;
});
link.classed("link-inactive", function(o) {
return o.source !== d && o.target !== d ? true : false;
});
// show tooltip
tooltip.text(d.id)
.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px")
.style("display", "block")
.style("opacity", 1);
})
.on("mouseout", function(d){
node.classed("node-active", false);
node.classed("node-inactive", false);
link.classed("link-active", false);
link.classed("link-inactive", false);
// hide tooltip
tooltip.style("opacity", 0)
.style("display", "none");
});
function zoomed() {
container.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")");
}
function dragstarted(d) {
d3.event.sourceEvent.stopPropagation();
force.start();
}
function dragged(d) {
d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}
function dragended(d) {}
force.on("tick", function() {
link.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
node.attr("cx", function(d) { return d.x; })
.attr("cy", function(d) { return d.y; });
});
});
var width = 880,
height = 880;
var margin = {top: 20, right: 0, bottom: 10, left: 20};
var x = d3.scale.ordinal().rangeBands([0, width]),
z = d3.scale.linear().domain([0, 4]).clamp(true),
c = d3.scale.category20().domain(d3.range(10));
var svg2 = d3.select("#vis2").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
d3.json("contiguous-usa.json", function(miserables) {
var matrix = [],
nodes = miserables.nodes,
n = nodes.length;
// Compute index per node.
nodes.forEach(function(node, i) {
node.name = node.id;
node.index = i;
node.count = 0;
matrix[i] = d3.range(n).map(function(j) { return {x: j, y: i, z: 0}; });
});
// Convert links to matrix; count character occurrences.
miserables.links.forEach(function(link) {
matrix[link.source][link.target].z += 1;
matrix[link.target][link.source].z += 1;
matrix[link.source][link.source].z += 1;
matrix[link.target][link.target].z += 1;
nodes[link.source].count += 1;
nodes[link.target].count += 1;
});
// Precompute the orders.
var 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[b].group - nodes[a].group;
})
};
// The default sort order.
x.domain(orders.name);
svg2.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
var row = svg2.selectAll(".row")
.data(matrix).enter()
.append("g")
.attr("id", function(d, i) { return "row_" + i })
.attr("class", "row")
.attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; })
.each(row);
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; });
var column = svg2.selectAll(".column")
.data(matrix).enter()
.append("g")
.attr("id", function(d, i) { return "col_" + i })
.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; });
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 z(d.z); })
.style("fill", function(d) { return nodes[d.x].group == nodes[d.y].group ? c(nodes[d.x].group) : null; })
.on("mouseover", mouseover)
.on("mouseout", mouseout);
}
function mouseover(p) {
svg2.selectAll(".row text").classed("active", function(d, i) { return i == p.y; });
svg2.selectAll(".column text").classed("active", function(d, i) { return i == p.x; });
}
function mouseout() {
svg2.selectAll("text").classed("active", false);
}
function order(value) {
x.domain(orders[value]);
var t = svg2.transition().duration(2500);
t.selectAll(".row")
.delay(function(d, i) { return x(i) * 4; })
.attr("id", function(d, i) { return "row_" + i })
.attr("transform", function(d, i) { return "translate(0," + x(i) + ")"; })
.selectAll(".cell")
.delay(function(d) { return x(d.x) * 4; })
.attr("x", function(d) { return x(d.x); })
.attr('pos-x', function(d) { return d.x; })
.attr('pos-y', function(d) { return d.y; });
t.selectAll(".column")
.delay(function(d, i) { return x(i) * 4; })
.attr("id", function(d, i) { return "col_" + i })
.attr("transform", function(d, i) { return "translate(" + x(i) + ")rotate(-90)"; });
}
var timeout = setTimeout(function() {
order("group");
d3.select("#order").property("selectedIndex", 2).node().focus();
}, 5000);
d3.select("#order").on("change", function() {
clearTimeout(timeout);
order(this.value);
});
})
</script>
https://d3js.org/d3.v3.min.js