xxxxxxxxxx
<html lang="en">
<head>
<link href='https://fonts.googleapis.com/css?family=Karla|Quantico|Audiowide' rel='stylesheet' type='text/css'>
<meta charset="utf-8">
<style>
.chartWrapper {
width: 100%;
height: 100%;
position: absolute;
}
.chartOne {
width: 50%;
height: 90%;
float: left;
}
.chartTwo {
width: 50%;
height: 90%;
float: right;
}
.xAxis text,
.yAxis text {
font-family: "Karla";
}
.yAxis path,
.yAxis .tick line,
.xAxis .tick line {
stroke: none;
}
.bartext {
font-family: "Karla";
font-weight: bolder;
fill: black; /*does this do anything impt.?*/
}
.ttip {
width: 300px;
position: relative;
background-color: rgba(255, 255, 255, 0.95);
display: none;
border: 1px solid;
padding: 5px;
}
br {
display: block;
content: "";
margin-top: 7.5px;
}
.ttipText {
font-size:14px;
padding: 0;
margin: 0;
font-weight: 300;
font-family: "Karla";
}
h1 {
font-family: "Karla";
font-size: 24px;
font-style: normal;
text-transform: uppercase;
margin-bottom: 10px;
margin-left: 175px;
}
h2 {
font-family: "Karla";
font-size: 24px;
font-style: normal;
text-transform: uppercase;
margin-bottom: 10px;
margin-left: 175px;
}
</style>
<title>Risk Bar Chart</title>
<script src="d3.v4.min.js"></script>
<!--<script src="https://d3js.org/d3.v4.min.js"></script>-->
<script src="jquery.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/d3-legend/1.1.0/d3-legend.js"></script>
</head>
<body>
<div class="chartWrapper" id="wrapper">
<div class="chartOne" id="chartOne">
<h1 id="headerOne">Statistical Risk Assessments</h1>
</div>
<div class="chartTwo" id="chartTwo">
<h2 id="headerTwo">Country:</h2>
</div>
</div>
<script type="text/javascript">
// todo
// (1a) minimum height and width for chart
// (1b) remove text after certain point
// (2) Resize graph correctly (for page enlargement)
// (3) Add color legend
/*------------------------------------
INITIAL SETTINGS
------------------------------------*/
// number of countries to include in graph
var numCountries = 20;
// transition duration
var duration = 750;
// preparing data: files
var dictionary_data = "cjh12sep2016.csv", // all data
secondChartData = "secondChartData.csv"; // order and labels of second chart data
// preparing data: locations
var forecast = [], // for first chart data
reordered = [], // for second chart data
labels = [], // for second chart labels
tooltipVar = [],
tooltipMetric = [],
tooltipDataSource = [],
varDict = {},
varDictFull = {},
varPers,
varVals;
// function to reorder data
function reorderDict(object, orderedList) {
tempDict = {};
for (var i = 0; i < orderedList.length; i++) {
test = object[orderedList[i]];
tempDict[orderedList[i]] = test;
}
return tempDict;
}
// function for processing data
function truncateDecimals (number, digits) {
var multiplier = Math.pow(10, digits),
adjustedNum = number * multiplier,
truncatedNum = Math[adjustedNum < 0 ? 'ceil' : 'floor'](adjustedNum);
return truncatedNum / multiplier;
}
// SVG margins
var margin = {top: 0, right: 10, bottom: 30, left: 175, tooltip: 15},
height,
width;
// function to set dimensions (height and width)
function setDimensions() {
width = (parseInt(d3.select(".chartOne").style("width")) - margin.left - margin.right);
height = parseInt(d3.select(".chartOne").style("height")) - margin.top - margin.bottom;
}
// setting height and width
setDimensions();
// bar attributes
var bar = {padding: 0.1,
color: {unfilled: "#0d2a52", filled: "#FA4C00"},
clicked: null};
// initializing scales for first chart
var first_xScale = d3.scaleLinear(),
first_yScale = d3.scaleBand()
.padding(bar.padding);
// initializing scales for second chart
var second_xScale = d3.scaleLinear(),
second_yScale = d3.scaleBand()
.padding(bar.padding);
// function to set scales' ranges
function setScales(w, h) {
first_xScale.range([0, w - margin.left]);
first_yScale.range([0, h]);
second_xScale.range([0, w - margin.left]);
second_yScale.range([0, h]);
}
// setting scales' ranges
setScales(width, height);
// axes for first chart
var first_xAxis = d3.axisBottom()
.scale(first_xScale)
.ticks(Math.max(width / 75, 3));
var first_yAxis = d3.axisLeft()
.scale(first_yScale);
// axes for second chart
var second_xAxis = d3.axisBottom()
.scale(second_xScale)
.ticks(Math.max(width / 75, 3)); // add ticks format
var second_yAxis = d3.axisLeft()
.scale(second_yScale);
// SVG for first chart
var firstChart = d3.select("#chartOne")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("class", "chartOne")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// SVG for second chart
var secondChart = d3.select("#chartTwo")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("class", "chartTwo")
.attr("transform", "translate(" + (margin.left) + "," + margin.top + ")");
// create tooltip for second chart
var ttip = d3.select("body")
.append("div")
.attr("class", "ttip");
// eight step color scale
var step = d3.scaleLinear()
.domain([0, 7])
.range([0, 1]);
var colors = d3.scaleLinear()
.domain([0, step(1), step(2), step(3), step(4), step(5), step(6), 7])
.range(["#006600", "#00b33c",
"#53ff1a", "#ffff00",
"#ff9900", "#ff6600",
"#e60000", "#ff0000"]);
var thresholds = [.1, .2, .3, .4, .5, .6, .7, .8, .9],
color_intervals = [.05, .15, .25, .35, .45, .55, .65, .75, .85, .95];
var shades = color_intervals.map(function(t) { return colors(t); });
var colorScale = d3.scaleThreshold()
.domain(thresholds)
.range(shades);
// format decimal
var formatDecimal = d3.format(",.1%");
/*------------------------------------
INITIALIZING CHARTS WITH DATA
------------------------------------*/
d3.queue()
.defer(d3.csv, dictionary_data)
.defer(d3.csv, secondChartData)
.await(function(error, dictionary_data, secondChartData) {
if (error) {
console.error(error);
}
else {
// processing data for second chart
for (var i = 0; i < secondChartData["columns"].length; i += 5) {
reordered.push(secondChartData["columns"][i]);
labels.push(secondChartData["columns"][i + 1]);
tooltipVar.push(secondChartData["columns"][i + 2]);
tooltipMetric.push(secondChartData["columns"][i + 3]);
tooltipDataSource.push(secondChartData["columns"][i + 4]);
}
// pushing each country's data to dictionary for first graph
for (var i = 0; i < dictionary_data.length; i++) {
var newCountry = Object();
for (prop in dictionary_data[i]) {
// changes variable names to workable format
var newProp = prop.replace(/\./g, "_");
newCountry[newProp] = dictionary_data[i][prop];
// Converts quantitative variables to numbers
if (prop !== "sftgcode" && prop !== "country") {
newCountry[newProp] = Number(newCountry[newProp]);
}
}
reorderedNewCountry = reorderDict(newCountry, reordered);
varDictFull[dictionary_data[i].country] = reorderedNewCountry;
if (i <= numCountries) {
forecast.push(newCountry);
varDict[dictionary_data[i].country] = reorderedNewCountry;
}
}
// selecting top country in graph as the current country
var current_country = forecast[0].country;
// change heading of second chart with new country
document.getElementById("headerTwo").innerHTML = "Country: " + current_country;
// modifying data for binary variables
for (mod in varDict[current_country]) {
// check if variable is relevant
if ((mod.slice(0, 6) !== "postcw")
&& (mod.slice(mod.length - 3, mod.length)) === "PER") {
// temporary variables for holding variables' data
var tempVal;
var tempList = [];
// recording variables' data
for (country in varDictFull) {
tempVal = varDictFull[country][mod];
if (tempList.indexOf(tempVal) === -1) {
tempList.push(tempVal)
}
}
// checking if variable is binary
if (tempList.length === 2) {
// changing percentile rankings of binary variables with values of 0
for (country in varDict) {
if (varDict[country][mod] === 0) {
var max = Math.max.apply(null, tempList);
varDict[country][mod] = (1 - max);
}
}
// changing values of binary variables to strings for tooltip
for (var i = 0; i < forecast.length; i ++) {
if (forecast[i][mod.slice(0, -3)] === 0) {
forecast[i][mod.slice(0, -3)] = "No";
} else if (forecast[i][mod.slice(0, -3)] === 1) {
forecast[i][mod.slice(0, -3)] = "Yes";
}
}
}
}
}
// function to push currently selected country's data
function pushVariableData(country) {
varPers = []; // percentage rankings of variables
varVals = []; // values of variables
// record percentile ranking of each variable
for (mod in varDict[country]) {
varPers.push(truncateDecimals(varDict[country][mod] * 100, 1));
}
// record value of each variable
for (var i = 0; i < forecast.length; i ++) {
if (forecast[i]["country"] === country) {
for (mod in varDict[country]) {
varVals.push(forecast[i][mod.slice(0, -3)]);
}
}
}
}
// pushing currently selected country's data
pushVariableData(current_country);
// scale range of data in the domain of first chart
first_xScale.domain([0, d3.max(forecast, function(d) { return d.mean_p; })]);
first_yScale.domain(forecast.map(function (d) { return d.country; }));
// scale range of data in the domain of second chart
second_xScale.domain([0, 100]);
second_yScale.domain(labels.map(function (d) { return d; }));
// create text for first chart
firstChart.selectAll("text")
.data(forecast)
.enter()
.append("text")
.attr("class", "bartext")
.attr("id", "firstBarText")
.text(function(d) { return d3.format(".1%")(d.mean_p); })
.attr("text-anchor", "middle")
.attr("font-size", function() { return first_yScale.bandwidth() / 2; })
.attr("x", function(d) {
// this works because most text has same # of chars, but not good in general
var textLength = this.getComputedTextLength();
return first_xScale(d.mean_p) + textLength;
})
.attr("y", function(d) {
return first_yScale(d.country) + (first_yScale.bandwidth() / 1.5);
});
// create text for second chart
secondChart.selectAll("text")
.data(varPers)
.enter()
.append("text")
.attr("class", "bartext")
.attr("id", "secondBarText")
.text(function(d) { return formatDecimal(d / 100); })
.attr("text-anchor", "middle")
.attr("font-size", function() { return second_yScale.bandwidth() / 1.5; })
.attr("x", function() {
var textLength = this.getComputedTextLength();
return second_xScale(100) + (textLength / 2) + (second_yScale.bandwidth() / 2); })
.attr("y", function(d, i) {
var numBars = varPers.length;
return (i * height / numBars) + (second_yScale.bandwidth() / 1.5);
});
// create x axis for first chart
firstChart.append("g")
.attr("class", "xAxis")
.attr("id", "first_xAxis")
.attr("transform", "translate(0," + height + ")")
.call(first_xAxis);
// create bars for first chart
firstChart.selectAll("rect.firstBars")
.data(forecast)
.enter()
.append("rect")
.attr("id", "firstBars")
.attr("x", 0)
.attr("y", function(d) { return first_yScale(d.country) })
.attr("width", function(d) { return first_xScale(d.mean_p); })
.attr("height", first_yScale.bandwidth())
.attr("fill", function(d, i) {
if (i === 0) {
return bar.color.filled; // first country loaded
} else {
return bar.color.unfilled;
}
});
// create semi transparent bars for second chart
secondChart.selectAll("rect.secondSecondBars")
.data(varPers)
.enter()
.append("rect")
.attr("id", "secondSecondBars")
.attr("x", 0)
.attr("y", function(d, i) {
var numBars = varPers.length;
return i * height / numBars;
})
.attr("width", second_xScale(100))
.attr("height", second_yScale.bandwidth())
.attr("fill", "#a9b1bc")
.attr("opacity", 0.2)
.attr("stroke", "black")
.attr("stroke-opacity", 0)
.attr("stroke-width", 2);
// create bars for second chart
secondChart.selectAll("rect.firstSecondBars")
.data(varPers)
.enter()
.append("rect")
.attr("id", "firstSecondBars")
.attr("x", 0)
.attr("y", function(d, i) {
var numBars = varPers.length;
return i * height / numBars;
})
.attr("width", function(d) { return second_xScale(d); })
.attr("height", second_yScale.bandwidth())
.attr("fill", function(d) { return colorScale((d / 100)); })
.attr("stroke", "black")
.attr("stroke-opacity", 0)
.attr("stroke-width", 2);
// create x axis for second chart
secondChart.append("g")
.attr("class", "xAxis")
.attr("id", "second_xAxis")
.attr("transform", "translate(0," + height + ")")
.call(second_xAxis);
// create y axis for first chart
firstChart.append("g")
.attr("class", "yAxis")
.attr("id", "first_yAxis")
.call(first_yAxis);
// create y axis for second chart
secondChart.append("g")
.attr("class", "yAxis")
.attr("id", "second_yAxis")
.call(second_yAxis);
// // create color legend for second chart
// var colorLegend = secondChart.selectAll(".legend")
// .attr("class", "colorLegend")
// .data(shades)
// .enter()
// .append("g")
// .attr("class", "legend")
// .attr("transform", "translate(0,20)");
//
// colorLegend.append("rect")
// .attr("x", function(d, i) { return 35 * i; })
// .attr("y", height)
// .attr("width", second_xScale(12))
// .attr("height", 35 / 3)
// .style("fill", function(d) { return d; });
// interactivity for first chart
firstChart.selectAll("#firstBars")
.on("click", function(d) {
//change color of bars
firstChart.selectAll("#firstBars")
.attr("fill", bar.color.unfilled);
d3.select(this)
.attr("fill", function() { return bar.color.filled });
setDimensions();
// record which bar was clicked
current_country = d.country;
bar.clicked = this;
// change heading of second chart with new country
document.getElementById("headerTwo").innerHTML = "Country: " + current_country;
// push data from new country
pushVariableData(current_country);
// change bars of second chart
secondChart.selectAll("#firstSecondBars")
.data(varPers)
.transition()
.duration(duration)
.attr("width", function(d) { return second_xScale(d); })
.attr("y", function(d, i) {
var numBars = varPers.length;
return i * height / numBars;
})
.attr("height", second_yScale.bandwidth())
.attr("fill", function(d) { return colorScale((d / 100)); });
// change text of second chart
secondChart.selectAll("#secondBarText")
.data(varPers)
.transition()
.duration(duration)
.tween("text", function(d) {
var that = d3.select(this);
var i = d3.interpolate(that.text().replace(/%/g, ""), d);
return function (t) {
that.text(formatDecimal(i(t)/100));
};
});
})
.on("mouseover", function() {
//fill bar
d3.select(this)
.attr("fill", bar.color.filled);
})
.on("mouseout", function() {
var top_bar = d3.min(forecast, function(d) {return first_yScale(d.country); });
if (bar.clicked === null && d3.format(".2f")(this.y.animVal.value) !==
d3.format(".2f")(top_bar)) {
d3.select(this)
.transition()
.duration(250)
.attr("fill", bar.color.unfilled);
} else if (bar.clicked !== null && this !== bar.clicked) {
d3.select(this)
.transition()
.duration(250)
.attr("fill", bar.color.unfilled);
}
});
// interactivity for second chart (first bars)
secondChart.selectAll("#firstSecondBars")
.on("mouseover", function(d, i) {
// create text for tooltip
var lineOne = '<p class="ttipText">' + '<b>' + tooltipVar[i] + ": " + '</b>' +
varVals[i] + '</p>',
lineTwo = '<p class="ttipText">' + '<b>' + "Metric: " + '</b>' +
tooltipMetric[i] + '</p>',
lineThree = '<p class="ttipText">' + '<b>' + "Data source: " + '</b>' +
tooltipDataSource[i] + '</p>',
lineFour = '<p class="ttipText">' + "Among the " + '<b>' + dictionary_data.length + '</b>' +
" countries measured since 1945, " + '<b>' + current_country + '</b>' +
" has a percentile rank of " + '<b>' + d + "%" + '</b>' + " for this variable" + '</p>';
// make tooltip visible and add text
ttip
.style("display", "inline-block")
.html(lineOne + "</br>" + lineTwo + "</br>" + lineThree + "</br>" + lineFour);
// add black stroke to selected bar
d3.select(this)
.style("stroke-opacity", 1);
})
.on("mousemove", function() {
// update height and width variables
setDimensions();
// update tooltip position
ttip
.style("top", function() {
// record tooltip height because it varies
ttipHeight = $(this).height(); // using jQuery
// set position of tooltip based on its and page's height
if ((d3.event.pageY + ttipHeight) <= height) {
return (d3.event.pageY + margin.tooltip) + "px";
} else {
return (d3.event.pageY - margin.tooltip * 2 - ttipHeight) + "px";
}
})
.style("left",(d3.event.pageX / 1.1) + "px"); // figure out why 1.1 is best
})
.on("mouseout", function() {
// make tooltip invisible
ttip.style("display", "none");
// remove bar stroke
d3.select(this)
.style("stroke-opacity", 0);
});
}
});
/*------------------------------------
UPDATING CHARTS ON WINDOW RESIZE
------------------------------------*/
function resize() {
// get new width and height
setDimensions();
// reset scales
setScales(width, height);
// update axes for first chart
firstChart.select("#first_xAxis")
.call(first_xAxis)
.attr("transform", "translate(0," + height + ")");
// update axes for first chart
first_xAxis.ticks(Math.max(width / 75, 3));
second_xAxis.ticks(Math.max(width / 75, 3));
firstChart.select("#first_yAxis")
.call(first_yAxis);
// update axes for second chart
secondChart.select("#second_xAxis")
.call(second_xAxis)
.attr("transform", "translate(0," + height + ")");
secondChart.select("#second_yAxis")
.call(second_yAxis);
// update bars for first chart
firstChart.selectAll("#firstBars")
.attr("y", function(d) { return first_yScale(d.country) })
.attr("width", function(d) { return first_xScale(d.mean_p); })
.attr("height", first_yScale.bandwidth());
// update first bars for second chart
secondChart.selectAll("#firstSecondBars")
.attr("y", function(d, i) {
var numBars = varPers.length;
return i * height / numBars;
})
.attr("width", function(d) { return second_xScale(d); })
.attr("height", second_yScale.bandwidth());
// update second bars for second chart
secondChart.selectAll("#secondSecondBars")
.attr("y", function(d, i) {
var numBars = varPers.length;
return i * height / numBars;
})
.attr("width", second_xScale(100))
.attr("height", second_yScale.bandwidth());
// update bars' texts for first chart
firstChart.selectAll("#firstBarText")
.attr("font-size", function() { return first_yScale.bandwidth() / 2; })
.attr("x", function(d) {
var width = this.getComputedTextLength();
return first_xScale(d.mean_p) + width;
})
.attr("y", function(d) { return first_yScale(d.country) + (first_yScale.bandwidth() / 1.5); });
// update bars' texts for second chart
secondChart.selectAll("#secondBarText")
.attr("font-size", function() { return second_yScale.bandwidth() / 1.5; })
.attr("x", function() {
var textLength = this.getComputedTextLength();
return second_xScale(100) + (textLength / 2) + (second_yScale.bandwidth() / 2); })
.attr("y", function(d, i) {
var numBars = varPers.length;
return (i * height / numBars) + (second_yScale.bandwidth() / 1.5);
});
}
resize();
// call the resize function whenever a resize event occurs
d3.select(window).on('resize', resize);
//window.addEventListener("resize", resize);
</script>
</body>
</html>
https://d3js.org/d3.v4.min.js
https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.1.0/d3-legend.js