|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<style> |
|
body { |
|
font-family: Verdana, Arial, sans-serif; |
|
font-variant: small-caps; |
|
font-size: 12px; |
|
font-weight: bold; |
|
background-color: #9ccef3; |
|
} |
|
|
|
text .cityname.viz { |
|
letter-spacing: 1.2px; |
|
font-weight: bold; |
|
fill: white; |
|
} |
|
|
|
path.i.unselected { |
|
fill: #69B9EC; |
|
} |
|
|
|
li.unselected { |
|
color: #009DE0; |
|
} |
|
|
|
#titles span { |
|
cursor: pointer; |
|
-webkit-touch-callout: none; /* iOS Safari */ |
|
-webkit-user-select: none; /* Chrome/Safari/Opera */ |
|
-khtml-user-select: none; /* Konqueror */ |
|
-moz-user-select: none; /* Firefox */ |
|
-ms-user-select: none; /* Internet Explorer/Edge */ |
|
user-select: none; /* Non-prefixed version, currently |
|
not supported by any browser */ |
|
} |
|
#titles span.indi:hover { |
|
text-decoration: underline; |
|
} |
|
|
|
.Q1_1 { |
|
fill: #006e9a; |
|
color: #006e9a; |
|
} |
|
.Q1_11 { |
|
fill: #8D9120; |
|
color: #8D9120; |
|
} |
|
.Q2_4 { |
|
fill: #9D0064; |
|
color: #9D0064; |
|
} |
|
.Q2_5 { |
|
fill: #E6AD1F; |
|
color: #E6AD1F; |
|
} |
|
.Q2_6 { |
|
fill: #459240; |
|
color: #459240; |
|
} |
|
.Q2_7 { |
|
fill: #C42117; |
|
color: #C42117; |
|
} |
|
.Q2_2 { |
|
fill: #D16A19; |
|
color: #D16A19; |
|
} |
|
|
|
.tooltip { |
|
text-decoration: none; |
|
position: relative; |
|
} |
|
.tooltip span { |
|
display: none; |
|
} |
|
.tooltip:hover span { |
|
display: block; |
|
position: fixed; |
|
overflow: hidden; |
|
} |
|
</style> |
|
<div id="titles"></div> |
|
<div id="viz"></div> |
|
<script src="//d3js.org/d3.v3.min.js" charset="utf-8"></script> |
|
<script src="http://d3js.org/queue.v1.min.js"></script> |
|
<script> |
|
var w = 960, h = 493; |
|
|
|
var padding = 20; |
|
|
|
var width = w, height = h - padding; |
|
|
|
var cities, coln, rown, cheight, cwidth; |
|
|
|
var svg = d3.select("#viz").append("svg") |
|
.attr("width", width) |
|
.attr("height", height) |
|
.attr("id", "root") |
|
.append("g") |
|
.attr("transform", "translate(0," + padding / 2 + ")"); |
|
|
|
function getPathData(radius) { |
|
// adjust the radius a little so our text's baseline isn't sitting directly |
|
// on the circle |
|
var r = radius; |
|
var startX = 0 / 2 - r; |
|
return 'm' + startX + ',' + (0 / 2) + ' ' + 'a' + r + ',' + r + ' 0 0 1 ' + (2 * r) + ',0'; |
|
} |
|
|
|
|
|
queue() |
|
.defer(d3.json, "data.json") |
|
.defer(d3.tsv, "names.tsv") |
|
.await(ready); |
|
|
|
function ready(error, _data, _names) { |
|
|
|
var titles = d3.select("#titles").selectAll("span") |
|
.data(_data.indicators) |
|
.enter().append("span") |
|
.attr("class", function(d) {return "viz title " + d.indicator}) |
|
.text(function(d, i) { |
|
sep = (i > 0) ? ' | ' : ''; |
|
return sep |
|
}) |
|
.append("span").attr("class", function(d) {return "viz title indi " + d.indicator}) |
|
.text(function(d, i) {return d.desc}) |
|
.on("click", function(d, i) { |
|
if (d3.selectAll(".viz.title." + d.indicator).classed("selected") == false) { |
|
sortCharts(i); |
|
d3.selectAll(".i").classed("unselected", true) |
|
d3.selectAll(".i" + i).classed("unselected", false) |
|
d3.selectAll(".i").classed("selected", false) |
|
d3.selectAll(".i" + i).classed("selected", true) |
|
d3.selectAll(".viz.title").classed("selected", false) |
|
d3.selectAll(".viz.title." + d.indicator).classed("selected", true) |
|
d3.selectAll(".viz.title").classed("unselected", true) |
|
d3.selectAll(".viz.title." + d.indicator).classed("unselected", false) |
|
d3.selectAll(".viz.title").classed("selection", true) |
|
} |
|
else { |
|
sortCharts("alpha"); |
|
d3.selectAll(".i").classed("unselected", false) |
|
d3.selectAll(".i").classed("selected", false) |
|
d3.selectAll(".viz.title").classed("selected", false) |
|
d3.selectAll(".viz.title").classed("unselected", false) |
|
d3.selectAll(".viz.title").classed("selection", false) |
|
|
|
} |
|
}) |
|
.on("mouseover", function(d, i) { |
|
if (d3.selectAll(".viz.title." + d.indicator).classed("selection") == true) { |
|
d3.selectAll(".i" + i).classed("unselected", false) |
|
d3.selectAll(".viz.title." + d.indicator + ".unselected").classed("unselected", false) |
|
} |
|
}) |
|
.on("mouseout", function(d, i) { |
|
if (d3.selectAll(".viz.title." + d.indicator).classed("selected") == false && d3.selectAll(".viz.title." + d.indicator).classed("selection") == true) { |
|
d3.selectAll(".viz.title." + d.indicator).classed("unselected", true) |
|
d3.selectAll(".i" + i).classed("unselected", true) |
|
} |
|
}); |
|
|
|
var namesByName = {}; |
|
|
|
_names.forEach(function(d) { |
|
namesByName[d.name1] = d; |
|
}); |
|
|
|
var legendByIndicator = {}; |
|
_data.indicators.forEach(function(d) { |
|
legendByIndicator[d.indicator] = d.desc; |
|
}); |
|
|
|
_data.cities.forEach(function(d, i) { |
|
_data.cities[i]["country"] = namesByName[_data.cities[i].name].country |
|
_data.cities[i]["show"] = namesByName[_data.cities[i].name].show |
|
}); |
|
|
|
var datalen = _data.cities.filter(function(d) { |
|
return d.show == 1 |
|
}).length |
|
cwidth = getSize(width, height, datalen); |
|
cheight = cwidth; |
|
|
|
var chartr = cwidth / 2 - 5; |
|
var barwidth = cwidth / 10; |
|
|
|
coln = (width - (width % cwidth)) / cwidth; |
|
rown = Math.ceil(datalen / coln); |
|
|
|
var charts = svg.append("g").attr("transform", "translate(" + (cwidth / 2) + "," + (cheight / 2) + ")"); |
|
|
|
var indicatorCount = _data.cities[0].values.length; |
|
|
|
cities = charts.selectAll("cities") |
|
.data(_data.cities |
|
.sort(function(a, b) {return d3.ascending(a.name, b.name)}) |
|
.filter(function(d) {return d.show == 1})) |
|
.enter().append("g") |
|
.attr("class", "city") |
|
.attr("id", function(d) {return d.name}) |
|
.attr("transform", function(d, i) { |
|
xtrans = ((i / coln) - Math.floor(i / coln)) * coln * cwidth; |
|
ytrans = Math.floor(i / coln) * cheight; |
|
return "translate(" + xtrans + "," + ytrans + ")" |
|
}); |
|
|
|
var grids = [0, 0.25, 0.5, 0.75, 1]; |
|
|
|
var circles = cities.selectAll("gridlines") |
|
.data(grids) |
|
.enter().append("circle") |
|
.attr("r", function(d) {return d * (chartr - barwidth) + barwidth}) |
|
.attr("class", "gridline") |
|
.style("fill", "none") |
|
.style("stroke", "white") |
|
.style("stroke-width", 0.5); |
|
|
|
var position = { |
|
0 : 1, |
|
1 : 0, |
|
2 : 6, |
|
3 : 3, |
|
4 : 2, |
|
5 : 5, |
|
6 : 4 |
|
}; |
|
|
|
var bars = cities.selectAll("bars") |
|
.data(function(d) {return d.values}) |
|
.enter().append("path") |
|
.attr("class", function(d, i) {return "i i" + i + " " + d.indicator}) |
|
.attr("d", function(d) { |
|
t = d.nd + d.ns + d.rs + d.ru + d.vs |
|
s = (d.rs + d.vs) / t |
|
return describeRadialBar(0, 0, barwidth, 360 / indicatorCount, s * (chartr - barwidth)) |
|
}) |
|
.attr("transform", function(d, i) {return "rotate(" + (180 + position[i] * 360 / indicatorCount) + ")"}); |
|
|
|
var citynamepath = svg.append("defs").selectAll("path") |
|
.data(_data.cities.filter(function(d) {return d.show == 1})) |
|
.enter().append("path") |
|
.attr({ |
|
d : getPathData((chartr - barwidth) + barwidth), |
|
id : function(d) { |
|
return "textp" + d.name.substring(0, 4) |
|
} |
|
}); |
|
|
|
var cityname = cities.append("text").append('textPath') |
|
.attr({ |
|
'xlink:href' : function(d) { |
|
return "#textp" + d.name.substring(0, 4) |
|
}, |
|
startOffset : "50%" |
|
}) |
|
.attr("text-anchor", "middle").attr("class", "cityname viz").text(function(d) {return namesByName[d.name].name2}); |
|
}; |
|
|
|
var sortCharts = function(parameter) { |
|
if (parameter == "alpha") { |
|
cities.sort(function(a, b) { |
|
return d3.ascending(a.name, b.name) |
|
}) |
|
} |
|
else { |
|
cities.sort(function(a, b) { |
|
ta = a.values[parameter].nd + a.values[parameter].ns + a.values[parameter].rs + a.values[parameter].ru + a.values[parameter].vs |
|
sa = (a.values[parameter].rs + a.values[parameter].vs) / ta |
|
tb = b.values[parameter].nd + b.values[parameter].ns + b.values[parameter].rs + b.values[parameter].ru + b.values[parameter].vs |
|
sb = (b.values[parameter].rs + b.values[parameter].vs) / tb |
|
return sb - sa |
|
}); |
|
} |
|
|
|
cities.transition().duration(1000).attr("transform", function(d, i) { |
|
xtrans = ((i / coln) - Math.floor(i / coln)) * coln * cwidth; |
|
ytrans = Math.floor(i / coln) * cheight; |
|
return "translate(" + xtrans + "," + ytrans + ")" |
|
}); |
|
} |
|
var getSize = function(x, y, n) { |
|
console.log(n) |
|
var px = Math.ceil(Math.sqrt(n * x / y)); |
|
var sx, sy; |
|
if (Math.floor(px * y / x) * px < n) { |
|
sx = y / Math.ceil(px * y / x); |
|
} |
|
else { |
|
sx = x / px; |
|
} |
|
|
|
var py = Math.ceil(Math.sqrt(n * y / x)); |
|
|
|
if (Math.floor(py * x / y) * py < n) { |
|
sy = x / Math.ceil(x * py / y); |
|
} |
|
else { |
|
sy = y / py; |
|
} |
|
return sx > sy ? sx : sy; |
|
} |
|
function describeRadialBar(x, y, radius, angle, value) { |
|
var start = polarToCartesian(x, y, radius, -angle / 2); |
|
var end = polarToCartesian(x, y, radius, angle / 2); |
|
|
|
d = ["M", start.x, start.y, "A", radius, radius, 0, 0, 1, end.x, end.y, "v", -value, "a", radius + value, radius + value, 0, 0, 0, -2 * end.x, 0, "v", value, "z"].join(" "); |
|
|
|
return d; |
|
} |
|
|
|
function polarToCartesian(centerX, centerY, radius, angleInDegrees) { |
|
var angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0; |
|
|
|
return { |
|
x : centerX + (radius * Math.cos(angleInRadians)), |
|
y : centerY + (radius * Math.sin(angleInRadians)) |
|
}; |
|
} |
|
</script> |