D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
drewleonard
Full window
Github gist
Early Warning Project, Risk Assessment Visualization, July 25 2017
<!DOCTYPE html> <html lang="en"> <head> <!--font--> <link href='https://fonts.googleapis.com/css?family=Karla|Quantico|Audiowide' rel='stylesheet' type='text/css'> <meta charset="utf-8"> <style> body { /*background-color: #D2D2D2;*/ /*background-image: url("greymap-bb860a34e83ce85e4a66dd53d9ad95ba.png");*/ } :root { --annotation-color: #0d2a52; --google-font: "Karla"; --button-font: 11px; --margin-header-bottom: 10px; --margin-top: 10px; } .buttons { float: left; margin-left: 25%; margin-bottom: var(--margin-header-bottom); } .button { width: 80px; margin: 0 auto; padding: 6px 6px; margin-right: 10px; cursor: pointer; text-align: center; font-size: 11px; font-family: var(--google-font); border: 1px solid #e0e0e0; float: left; -moz-border-radius: 6px; border-radius: 6px; } .button:hover { background: #e0e0e0; } .button.current { background: #000; color: #fff; } .annotation path { stroke: var(--annotation-color); fill: none; } .annotation path.connector-arrow, .title text, .annotation text { fill: var(--annotation-color); font-family: var(--google-font); font-size: 15px; } .annotation-note-bg { fill: rgba(0, 0, 0, 0); } .annotation-note-title, text.title { font-weight: bold; } .chartWrapper { width: 100%; height: 100%; position: absolute; } .chartOne { width: 50%; height: 80%; float: left; } .chartTwo { width: 50%; height: 80%; float: right; } .xAxis text, .yAxis text { font-family: var(--google-font); } .yAxis text { } .yAxis path, .yAxis .tick line, .xAxis .tick line { stroke: none; } .bartext { font-family: var(--google-font); 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: 3px solid; padding: 5px; } br { display: block; content: ""; margin-top: 7.5px; } .ttipText { font-size:14px; padding: 0; margin: 0; font-weight: 300; font-family: var(--google-font); } h1, h2 { font-family: var(--google-font); font-size: 24px; font-style: normal; text-transform: uppercase; margin-bottom: var(--margin-header-bottom); margin-top: var(--margin-top); margin-left: 175px; } h3, h4 { font-family: var(--google-font); font-size: 15px; font-style: italic; text-transform: uppercase; margin-bottom: var(--margin-header-bottom); margin-left: 175px; margin-top: var(--margin-top); padding: 0; } .slider-wrapper { width: 50%; justify-content: center; margin-left: 25%; margin-top: 5px; } .slider { height: 23px; margin-right: 10px; margin-bottom: 10px; float: left; shape-rendering: geometricPrecision; } </style> <title>Risk Bar Chart</title> <!--js files--> <script src="d3.v4.min.js"></script> <script src="jquery.js"></script> <!--<script src="d3-annotation.js"></script>--> </head> <body> <div class="chartWrapper" id="wrapper"> <!--right chart--> <div class="chartOne" id="chartOne"> <h1 id="headerOne">Statistical Risk Assessments</h1> <h3 id="headerThree">Percentage risks of state-led mass killings</h3> <div id="slider-wrapper" class="slider-wrapper"> <svg id="slider" class="slider"></svg> </div> </div> <!--left chart--> <div class="chartTwo" id="chartTwo"> <h2 id="headerTwo">Country:</h2> <h4 id="headerFour">Variables used to generate risk forecast</h4> <!--buttons--> <div id="model" class="buttons"> <div class="button current" data-val="all">All</div> <div class="button" data-val="badRegime">Bad Regime</div> <div class="button" data-val="eliteThreat">Elite Threat</div> <div class="button" data-val="randomForest" style="margin-right:0">Random Forest</div> </div> </div> </div> <script type="text/javascript"> // todo // --- fix binary values of some variables // --- add axis styling - https://bl.ocks.org/mbostock/3371592 // --- add annotations // --- add values for slider // --- fix color scale (paler) // --- add scrollytelling feature // (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; var countryStart = 0, countryEnd = countryStart + numCountries; // transition duration var duration = 1000; // preparing data: files var dictionary_data = "cjh12sep2016.csv", // all data badRegime = "badRegime.csv", eliteThreat = "eliteThreat.csv", randomForest = "randomForest.csv", secondChartData = "secondChartData.csv"; // order and labels of second chart data // SVG margins var margin = {top: 0, right: 10, bottom: 30, left: 180, tooltip: 15}, width, height; // 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(); // 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("id", "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("id", "chartTwo") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // 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; } // bar attributes var bar = {padding: 0.1, clicked: null}; var filled = "#0d2a52", unfilled = "#E0E0E0"; // 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(6) .tickFormat(d3.format(".1%")); var first_yAxis = d3.axisLeft() .scale(first_yScale); firstChart.append("g") .attr("class", "xAxis") .attr("transform", "translate(0," + height + ")"); firstChart.append("g") .attr("class", "yAxis"); // axes for second chart var second_xAxis = d3.axisBottom() .scale(second_xScale) .ticks(Math.max(width / 75, 3)) .tickFormat(function(d) { return d + "%" }); var second_yAxis = d3.axisLeft() .scale(second_yScale); secondChart.append("g") .attr("class", "xAxis") .attr("transform", "translate(0," + height + ")"); secondChart.append("g") .attr("class", "yAxis"); // 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%"); // button selected var selectedButton = "all", buttonArray = null; var badRegimeIndex = [], eliteThreatIndex = [], randomForestIndex = []; // initializing variables for slider to select country range var dragging = false, sliderStart, sliderEnd, sliderPadding, handleStart, handleEnd, handlePos, sliderHandleRadius, sliderScale = d3.scaleLinear(), sliderTrack = d3.select(".slider") .append("rect"), sliderHandle = d3.select(".slider") .append("circle") .attr("id", "sliderHandle"), $slider = $('.slider'); // preparing data: locations var forecast = [], // ordered list forecastFull = [], varDict = {}, // dictionary varDictKeys = [], varDictFull = {}; var reordered = [], // for second chart data labels = [], // for second chart labels tooltipVar = [], tooltipMetric = [], tooltipDataSource = []; var varPers, varVals; /*------------------------------------ INITIALIZING CHARTS WITH DATA ------------------------------------*/ d3.queue() .defer(d3.csv, dictionary_data) .defer(d3.csv, secondChartData) .defer(d3.csv, badRegime) .defer(d3.csv, eliteThreat) .defer(d3.csv, randomForest) .await(function(error, dictionary_data, secondChartData, badRegime, eliteThreat, randomForest) { 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]); } // process all data for first chart 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); // pushing data to relevant locations forecastFull.push(newCountry); varDictFull[dictionary_data[i].country] = reorderedNewCountry; varDictKeys.push(dictionary_data[i].country); } // modifying data for binary variables for (mod in varDictFull[forecastFull[0].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 varDictFull) { if (varDictFull[country][mod] === 0) { var max = Math.max.apply(null, tempList); varDictFull[country][mod] = (1 - max); } } // changing values of binary variables to strings for tooltip for (var i = 0; i < forecastFull.length; i ++) { if (forecastFull[i][mod.slice(0, -3)] === 0) { forecastFull[i][mod.slice(0, -3)] = "No"; } else if (forecastFull[i][mod.slice(0, -3)] === 1) { forecastFull[i][mod.slice(0, -3)] = "Yes"; } } } } } // function to subset data based on range of countries function subsetData(startCountryIndex, endCountryIndex) { // clear existing data forecast = []; varDict = {}; // subset forecasting data forecast = forecastFull.slice(startCountryIndex, endCountryIndex); // subset dictionary data var countries = varDictKeys.slice(startCountryIndex, endCountryIndex); for (var i = 0; i < countries.length; i++) { varDict[countries[i]] = varDictFull[countries[i]]; } } // creating lists of indexes for each model (for buttons) function findIndex(csvFile, list) { for (var i = 0; i < csvFile["columns"].length; i++) { var temp = csvFile["columns"][i].replace(/\./g, "_"); for (var n = 0; n < reordered.length; n++) { if (reordered[n].search(temp) !== -1) { list.push(n) } } } } findIndex(badRegime, badRegimeIndex); findIndex(eliteThreat, eliteThreatIndex); findIndex(randomForest, randomForestIndex); // create slider for country selection // scales according to number of countries in file in argument function createSlider(data) { // record radius of slider handle sliderHandleRadius = (document.getElementById('slider').getBoundingClientRect().height) / 2; sliderPadding = sliderHandleRadius / 2; // change width of slider svg wrapper $slider.css('width', first_xScale(1) + (sliderHandleRadius * 2)); $slider.css({"-webkit-transform":function() { return "translate(" + -sliderHandleRadius + "px,0)"; }}); // record start and end positions of slider sliderStart = 0 + sliderPadding; sliderEnd = sliderStart + document.getElementById('slider').getBoundingClientRect().width - sliderPadding - sliderPadding; // record start and end positions of handle handleStart = sliderStart + sliderHandleRadius; handleEnd = sliderEnd - sliderHandleRadius; // adjust slider scale sliderScale = d3.scaleLinear() .rangeRound([0, data.length - numCountries]) .domain([handleStart, handleEnd]); // create track for slider sliderTrack .attr("width", function() { return document.getElementById('slider').getBoundingClientRect().width - (2 * sliderHandleRadius); }) .style("height", "25%") .style("x", 0 + sliderHandleRadius) .style("y", "37.5%") .style("fill", "black"); sliderHandle .style("cursor", "ew-resize") .attr("cx", function() { return handleStart; }) .style("cy", "50%") .style("r", sliderHandleRadius) .attr("fill", "black") .call(d3.drag() .on("drag", dragged) .on("end", function() { dragging = false; })); } function dragged() { d3.select(this).style("cx", function() { dragging = true; if (d3.event.x + sliderHandleRadius >= sliderEnd) { return sliderEnd - sliderHandleRadius; } else if (d3.event.x - sliderHandleRadius <= sliderStart) { return sliderStart + sliderHandleRadius; } return d3.event.x; }); handlePos = document.getElementById('sliderHandle').style["cx"]; countryStart = sliderScale(handlePos); countryEnd = sliderScale(handlePos) + numCountries; drawFirstChart(countryStart, countryEnd); } createSlider(dictionary_data); // selecting top country in graph as the current country var current_country = forecastFull[0].country; // change heading of second chart with new country document.getElementById("headerTwo").innerHTML = "<span>Selected country: <span style='text-decoration:underline;'>" + current_country + "</span></span>"; // 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 varDictFull[country]) { varPers.push(truncateDecimals(varDictFull[country][mod] * 100, 1)); } // record value of each variable for (var i = 0; i < forecastFull.length; i ++) { if (forecastFull[i]["country"] === country) { for (mod in varDictFull[country]) { varVals.push(forecastFull[i][mod.slice(0, -3)]); } } } } pushVariableData(current_country); function drawFirstChart (countryStart, countryEnd) { subsetData(countryStart, countryEnd); // 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; })); // join data var bar = firstChart.selectAll(".bar") .data(forecast); var text = firstChart.selectAll(".bartext") .data(forecast); // update bar .attr("x", 0) .attr("y", function(d) { return first_yScale(d.country) }) .attr("height", first_yScale.bandwidth()) .attr("fill", function(d) { if (d.country === current_country) { return filled; // first country loaded } else { return unfilled; } }) .transition().duration(50) .attr("width", function(d) { return first_xScale(d.mean_p); }); text .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); }); // enter bar .enter() .append("rect") .attr("class", "bar") .attr("fill", function(d) { if (d.country === current_country) { return filled; // first country loaded } else { return unfilled; } }) .attr("x", 0) .attr("y", function(d) { return first_yScale(d.country) }) .attr("height", first_yScale.bandwidth()) .transition().duration(50) .attr("width", function(d) { return first_xScale(d.mean_p); }); text .enter() .append("text") .attr("class", "bartext") .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); }); // exit bar.exit().remove(); text.exit().remove(); // axes firstChart.select(".xAxis") .call(first_xAxis) .attr("transform", "translate(0," + height + ")"); firstChart.select(".yAxis") .call(first_yAxis); } function drawSecondChart() { // scale range of data in the domain of second chart second_xScale.domain([0, 100]); second_yScale.domain(labels.map(function (d) { return d; })); // join data var text = secondChart.selectAll(".bartext") .data(varPers); var barTransparent = secondChart.selectAll(".barTransparent") .data(varPers); var bar = secondChart.selectAll(".bar") .data(varPers); // update text .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); }); barTransparent .attr("x", 0) .attr("y", function(d, i) { var numBars = varPers.length; return i * height / numBars; }) .attr("height", second_yScale.bandwidth()) .attr("width", second_xScale(100)); bar .attr("x", 0) .attr("y", function(d, i) { var numBars = varPers.length; return i * height / numBars; }) .attr("height", second_yScale.bandwidth()) .transition().duration(50) .attr("width", function(d) { return second_xScale(d); }); // enter text .enter() .append("text") .attr("class", "bartext") .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); }); barTransparent .enter() .append("rect") .attr("class", "barTransparent") .attr("fill", "#a9b1bc") .attr("opacity", 0.2) .attr("stroke", "black") .attr("stroke-opacity", 0) .attr("stroke-width", 2) .attr("x", 0) .attr("y", function(d, i) { var numBars = varPers.length; return i * height / numBars; }) .attr("height", second_yScale.bandwidth()) .attr("width", second_xScale(100)); bar .enter() .append("rect") .attr("class", "bar") .attr("id", "firstSecondBars") .attr("fill", function(d) { return colorScale((d / 100)); }) .attr("stroke", "black") .attr("stroke-opacity", 0) .attr("stroke-width", 2) .attr("x", 0) .attr("y", function(d, i) { var numBars = varPers.length; return i * height / numBars; }) .attr("height", second_yScale.bandwidth()) .transition().duration(50) .attr("width", function(d) { return second_xScale(d); }); // exit text.exit().remove(); barTransparent.exit().remove(); bar.exit().remove(); // axes secondChart.select(".xAxis") .call(second_xAxis) .attr("transform", "translate(0," + height + ")"); secondChart.select(".yAxis") .call(second_yAxis); } drawFirstChart(countryStart, countryEnd); drawSecondChart(); // interactivity for first chart firstChart.selectAll(".bar") .on("click", function(d) { // change color of bars firstChart.selectAll(".bar") .attr("fill", unfilled); d3.select(this) .attr("fill", function() { return 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 = "<span>Selected country: <span style='text-decoration:underline;'>" + current_country + "</span></span>"; // push data from new country pushVariableData(current_country); // change bars of second chart secondChart.selectAll(".bar") .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, i) { if (selectedButton === "allIndex" || buttonArray === null || buttonArray.includes(i)) { return colorScale((d / 100)); } else { return "#a9b1bc" } }); // 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() { if (dragging === false) { //fill bar d3.select(this) .attr("fill", filled); } }) .on("mouseout", function() { var topBar = d3.min(forecast, function(d) {return first_yScale(d.country); }); if (bar.clicked === null && d3.format(".2f")(this.y.animVal.value) !== d3.format(".2f")(topBar)) { d3.select(this) .transition() .duration(250) .attr("fill", unfilled); } else if (bar.clicked !== null && this !== bar.clicked) { d3.select(this) .transition() .duration(250) .attr("fill", unfilled); } }); // interactivity for second chart (first bars) secondChart.selectAll(".bar") .on("mouseover", function(d, i) { // create text for tooltip var lineOne = '<p class="ttipText">' + '<b>' + tooltipVar[i] + ": " + '</b>' + varVals[i] + '</p>', lineThree = '<p class="ttipText">' + '<b>' + "Metric: " + '</b>' + tooltipMetric[i] + '</p>', lineFour = '<p class="ttipText">' + '<b>' + "Data source: " + '</b>' + tooltipDataSource[i] + '</p>', lineTwo = '<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); }); // function for interactivity for buttons function modelUpdate() { d3.selectAll("#firstSecondBars") .data(varPers) .transition() .duration(duration) .attr("fill", function(d, i) { if (selectedButton === "allIndex" || buttonArray.includes(i)) { return colorScale((d / 100)); } else { return "#a9b1bc" } }) } d3.selectAll("#model .button").on("click", function() { selectedButton = d3.select(this).attr("data-val") + "Index"; buttonArray = window[selectedButton]; d3.select("#model .current").classed("current", false); d3.select(this).classed("current", true); modelUpdate(model); }); /*------------------------------------ UPDATING CHARTS ON WINDOW RESIZE ------------------------------------*/ function resize() { // reset dimensions setDimensions(); // reset scales setScales(width, height); // redraw charts drawFirstChart(countryStart, countryEnd); drawSecondChart(); } resize(); // call the resize function whenever a resize event occurs window.addEventListener("resize", resize); } }); /*------------------------------------ EXTRA CODE TO INCLUDE LATER ------------------------------------*/ // // 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; }); </script> </body> </html>