Skip to content

Instantly share code, notes, and snippets.

@erikaris
Last active February 24, 2016 13:06
Show Gist options
  • Save erikaris/ddcc7c4cb91c67a6dcdb to your computer and use it in GitHub Desktop.
Save erikaris/ddcc7c4cb91c67a6dcdb to your computer and use it in GitHub Desktop.
VI5 (Overview, Zoom and Filter, Details on Demand)

Name: Erika Siregar

  • Assignment : Visualization Implementation (VI5)
  • Course: Information Visualization (CS725)
  • Semester : Spring 2016

Explanation:

  1. The one that break the rule
    In this visualization, we display the globe and show all of the countries' names. It could overwhelm the users since the globe look very densed and some of the labels are overlap with each other. We can see that it is hard for a user to find certain country. They have to comb the map very carefully and slowly until finally they find the country that they are looking for.
  2. The one that follow the rule
    Based on the "wrong" graph above, we know that we need to implement the "overview, zoom and filter, details on demand" rule to make it easier for users to find a particular country. So, I add a dropdown list to choose the name of the country that we want to find. When user select a certain country, for example: Indonesia, then the globe will automatically rotate to show the location of Indonesia. The area of Indonesia will also be highlighted on the globe. Therefore, the users will directly see Indonesia on the globe. When user hover the mouse above the area of Indonesia, a tooltip contains a detail will show up.
  3. Additional Note
    Eventhough this is not the rule that I choose for my assignment, I would like to point out the perspective distortion that we can see on the globe. From the globe, we can see that the area that is far from user's eyes will look smaller than the area that is right in front of the user's eyes. This distortion perspective could help the user to estimate the distance between countries. Also, the distortion makes the vis looks more real rather than just make all the lines and areas have the same length and wide.
<html>
<head>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//d3js.org/d3.geo.projection.v0.min.js"></script>
<script src="//d3js.org/topojson.v1.min.js"></script>
<script src="http://d3js.org/queue.v1.min.js"></script>
<style>
body {
font-family: 'Open sans',verdana,arial,sans-serif;
font-size: 10pt
}
.outer-container {
overflow-x : hidden;
overflow-y : scroll;
}
.pan-canvas .background {
stroke: #333333;
fill: #FAFAFF;
}
.filter-canvas .background {
stroke: #333333;
fill: #FAFAFF;
}
.water {
fill: #B0D6FD;
stroke: #000;
}
.country {
fill: #A98B6F;
stroke: #000;
}
.country:hover {
fill:#33CC33;
stroke-width: 1px;
}
.focused {
fill: #33CC33;
}
.tooltip {
position: absolute;
display: none;
pointer-events: none;
background: #fff;
padding: 5px;
text-align: left;
border: solid #ccc 1px;
color: #666;
}
select.select-country {
position: absolute;
top: 20px;
left: 530px;
border: solid #ccc 1px;
padding: 3px;
box-shadow: inset 1px 1px 2px #ddd8dc;
}
</style>
</head>
<body>
<script type="text/javascript">
var width = 920,
height = 480,
sens = 0.25;
var outerCont = d3.select("body").append("div")
.attr("class", "outer-container")
.style("height", height);
// Right Globe
(function() {
var projection = d3.geo.orthographic()
.scale(235)
.translate([(width - 350) / 2, height / 2])
.clipAngle(90),
path = d3.geo.path().projection(projection),
color = d3.scale.category10();
var container = outerCont.append("div")
.style("width", width)
.style("height", height)
.style("position", "relative");
var tooltip = container.append("div")
.attr("class", "tooltip"),
countryList = container.append("select")
.attr("class", "select-country")
.attr("name", "countries")
var svg = container.append("svg")
.attr("width", width)
.attr("height", height);
// Map Container (Left Side)
var mapCont = svg.append("g")
.attr("class", "pan-canvas"),
panBg = mapCont.append("rect")
.attr("width", width)
.attr("height", height)
.attr("class", "background"),
water = mapCont.append("path")
.datum({type: "Sphere"})
.attr("class", "water")
.attr("transform", "translate(0,0)scale(1)")
.attr("d", path),
map = mapCont.append("g");
// Load data
queue()
.defer(d3.json, "http://www.billdwhite.com/wordpress/wp-content/data/world.json")
.defer(d3.json, "https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/master/all/all.json")
.await(ready);
function ready(error, worldData, countryData) {
var countryById = {};
countryData.forEach(function(country) {
countryById[parseInt(country["country-code"])] = country.name;
option = countryList.append("option");
option.text(country.name);
option.property("value", parseInt(country["country-code"]));
});
var countries = topojson.feature(
worldData,
worldData.objects.countries
).features,
neighbors = topojson.neighbors(
worldData.objects.countries.geometries
);
var world = map.selectAll(".country")
.data(countries).enter()
.append("path")
.attr("id", function(d, i) {
return "country" + d.id
})
.attr("class", "country")
.attr("d", path)
.style("fill-opacity", 1)
.on("mouseover", function(d) {
tooltip.text(countryById[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) {
tooltip.style("opacity", 0)
.style("display", "none");
})
.on("mousemove", function(d) {
tooltip.style("left", (d3.event.pageX + 7) + "px")
.style("top", (d3.event.pageY - 15) + "px");
});
world.call(d3.behavior.drag()
.origin(function() { var r = projection.rotate(); return {x: r[0] / sens, y: -r[1] / sens}; })
.on("drag", function() {
var rotate = projection.rotate();
projection.rotate([d3.event.x * sens, -d3.event.y * sens, rotate[2]]);
world.attr("d", path);
}));
// Select
countryList.on("change", function() {
var rotate = projection.rotate(),
focusedCountry = country(countries, this),
p = d3.geo.centroid(focusedCountry);
svg.selectAll(".focused").classed("focused", focused = false);
//Globe rotating
(function transition() {
d3.transition()
.duration(2500)
.tween("rotate", function() {
var r = d3.interpolate(projection.rotate(), [-p[0], -p[1]]);
return function(t) {
projection.rotate(r(t));
svg.selectAll("path").attr("d", path)
.classed("focused", function(d, i) { return d.id == focusedCountry.id ? focused = d : false; });
};
})
})();
});
function country(cnt, sel) {
for(var i = 0, l = cnt.length; i < l; i++) {
if(cnt[i].id == sel.value) {return cnt[i];}
}
};
// Add label to map
world.each(function(d,i) {
map.append("text")
.text("")
.attr("id", "label" + d.id)
.attr("class", "label")
.attr("x", -200)
.attr("y", -200)
})
// Add text to label country
countryData.forEach(function(country) {
map.select("#label" + parseInt(country["country-code"]))
.text(country["name"])
})
}
})();
// Wrong Globe
(function() {
var projection = d3.geo.orthographic()
.scale(235)
.translate([width / 2, height / 2])
.clipAngle(90),
path = d3.geo.path().projection(projection),
color = d3.scale.category10();
var container = outerCont.append("div")
.style("width", width)
.style("height", height)
.style("position", "relative")
.style("top", "10px");
var svg = container.append("svg")
.attr("width", width)
.attr("height", height);
var mapCont = svg.append("g")
.attr("class", "pan-canvas"),
panBg = mapCont.append("rect")
.attr("width", width)
.attr("height", height)
.attr("class", "background"),
water = mapCont.append("path")
.datum({type: "Sphere"})
.attr("class", "water")
.attr("transform", "translate(0,0)scale(1)")
.attr("d", path),
map = mapCont.append("g");
// Load data
queue()
.defer(d3.json, "http://www.billdwhite.com/wordpress/wp-content/data/world.json")
.defer(d3.json, "https://raw.githubusercontent.com/lukes/ISO-3166-Countries-with-Regional-Codes/master/all/all.json")
.await(ready);
function ready(error, worldData, countryData) {
var countries = topojson.feature(
worldData,
worldData.objects.countries
).features,
neighbors = topojson.neighbors(
worldData.objects.countries.geometries
);
var world = map.selectAll(".country")
.data(countries).enter()
.append("path")
.attr("id", function(d, i) {
return "country" + d.id
})
.attr("class", "country")
.attr("d", path)
.style("fill-opacity", 1);
// Add label to map
world.each(function(d,i) {
map.append("text")
.text("")
.attr("id", "label" + d.id)
.attr("class", "label")
})
// Add text to label country
countryData.forEach(function(country) {
map.select("#label" + parseInt(country["country-code"]))
.text(country["name"])
})
var i = 0;
setInterval(function() {
i = i+0.5;
projection.rotate([i,0,0]);
world.attr('d', path);
world.each(function(d,i) {
var label = map.select("#label" + d.id);
var pos = path.centroid(d)
if(!isNaN(pos[0]) && !isNaN(pos[1])) {
label.attr("x", pos[0])
.attr("y", pos[1])
} else {
label.attr("x", -200)
.attr("y", -200)
}
});
}, 20);
}
})()
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment