D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
drewleonard
Full window
Github gist
Early Warning Project, Interim
<!DOCTYPE html> <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: 100%; float: left; } .chartTwo { width: 50%; height: 100%; 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: 250px; 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:13px; padding: 0; margin: 0; font-weight: 300; font-family: "Karla"; } </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> </head> <body> <div class="chartWrapper" id="wrapper"> <div class="chartOne" id="chartOne"></div> <div class="chartTwo" id="chartTwo"></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 = {}, varName, varData, varVals, valName; // 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: 30, 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.left / 5)) + "," + 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 colorScale = 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"]); // 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 < numCountries; 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]); } } forecast.push(newCountry); reorderedNewCountry = reorderDict(newCountry, reordered); varDict[dictionary_data[i].country] = reorderedNewCountry; } // selecting top country in graph as the current country var current_country = forecast[0].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 varDict) { tempVal = varDict[country][mod]; if (tempList.indexOf(tempVal) === -1) { tempList.push(tempVal) } } // changing zero values of binary variables if (tempList.length === 2) { for (country in varDict) { if (varDict[country][mod] === 0) { var max = Math.max.apply(null, tempList); varDict[country][mod] = (1 - max); } } } } } // function to push currently selected country's data function pushVariableData(country) { varName = []; // new names of variables varData = []; // percentage rankings of variables varVals = []; // values of variables valName = []; // names of variables for (mod in varDict[country]) { // check if variable is relevant if (mod.slice(0, 6) !== "postcw") { if (mod.slice(mod.length - 3, mod.length) === "PER") { varData.push(truncateDecimals(varDict[country][mod] * 100, 1)); varName.push(mod); } else if (mod !== "forecast_year" && mod !== "year" && mod !== "date") { varVals.push(varDict[country][mod]); valName.push(mod); } } } } // 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(varData) .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 = varData.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(varData) .enter() .append("rect") .attr("id", "secondSecondBars") .attr("x", 0) .attr("y", function(d, i) { var numBars = varData.length; return i * height / numBars; }) .attr("width", second_xScale(100)) .attr("height", second_yScale.bandwidth()) .attr("fill", "#a9b1bc") .attr("opacity", 0.2); // create bars for second chart secondChart.selectAll("rect.firstSecondBars") .data(varData) .enter() .append("rect") .attr("id", "firstSecondBars") .attr("x", 0) .attr("y", function(d, i) { var numBars = varData.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); // 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; console.log(current_country) console.log(this) // push data from new country pushVariableData(current_country); // change bars of second chart secondChart.selectAll("#firstSecondBars") .data(varData) .transition() .duration(duration) .attr("width", function(d) { return second_xScale(d); }) .attr("y", function(d, i) { var numBars = varData.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(varData) .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); console.log(bar.clicked); } else if (bar.clicked !== null && this !== bar.clicked) { d3.select(this) .transition() .duration(250) .attr("fill", bar.color.unfilled); console.log("second unfilled") } }); // interactivity for second chart secondChart.selectAll("#firstSecondBars") .on("mouseover", function(d, i) { // create text for tooltip var lineOne = '<p class="ttipText">' + '<b>' + tooltipVar[i] +'</b>' + '</p>', lineTwo = '<p class="ttipText">' + '<b>' + "Metric: " + '</b>' + tooltipMetric[i] + '</p>', lineThree = '<p class="ttipText">' + '<b>' + "Data source: " + '</b>' + tooltipDataSource[i] + '</p>'; var 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 = varData.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 = varData.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 = varData.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