D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
ElaineYu
Full window
Github gist
Metis Health & Wealth of Nations Country Comparison
Built with
blockbuilder.org
<!DOCTYPE html> <head> <meta charset="utf-8"> <script src="https://d3js.org/d3.v4.min.js"></script> <style> body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } text { font: 15px Helvetica, Verdana, Tahoma; } .lineA { fill: none; stroke: blue; stroke-width: 2px; } .lineB { fill: none; stroke: hotpink; stroke-width: 2px; } </style> </head> <body> <select id="dropdown-category"></select> <select id="dropdown-countriesA"></select> <select id="dropdown-countriesB"></select> <script> // HOMEWORK ASSIGNMENT: // 1. pick: pop, $, or death // 2. pick a country // 3. create line chart // 4. create dropdown of countries // 5. create ability to pick different series // 6. create ability to compare two countries // General Instructions and Breakdown // 1. Determine the range. The range is your screen space display. // For x-scale, .range([0 , width]) // For y-scale, .range([height, 0]) // 2. Determine the domain. The domain is your data (e.g. income per capita). // Requires the extent of your data (min and max values). For example // X-axis // var xExtent = d3.extent(data, d => d.income); // .domain([300, xExtent[1]]) // income per capita data // Y-axis // var yExtent = d3.extent(data, d => d.lifeExpectancy); // .domain([0, yExtent[1]]) // life expectancy data // 3. Determine the scale, which requires domain and range // Scales can also be used with virtually any type of data, such as named categorical data or discrete data that requires sensible breaks. // For continuous quantitative data, you typically want a linear scale. // 4. Determine the axes, which requires scale // 5. Draw svg. Append the svg to the body // 6. Append axes // GLOBALS // Margin conventions: https://bl.ocks.org/mbostock/3019563 // Define the margin object with properties for the four sides var margin = {top: 20, right: 40, bottom: 200, left: 70}; // define width and height as the INNER dimensions of the chart area var width = 960 - margin.left - margin.right; var height = 500 - margin.top - margin.bottom; var dropDownSelectForCategory = 'population'; var dropDownSelectForCountryA = 'Afghanistan'; var dropDownSelectForCountryB = 'Zimbabwe'; // Draw svg/ add svg to canvas var svg = d3.select("body") .append("svg") // Append the svg to the body .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); svg.append("text") .attr("class", "axisText") .attr("transform", "translate(" + (width/2) + " ," + (height + margin.top + 20) + ")") .style("text-anchor", "middle") .text("Year"); var xAxis = svg.append('g') .attr("transform", "translate(0," + height + ")") // Move x-axis down translate(tx, ty) / (0, 500 - margin.top - margin.bottom) // Call axes.x later on update on drawChart // .call(axes.x) var yAxis = svg.append('g') // .call(axes.y) var appendLine = svg.append("path") .attr("class", "lineA") var appendLineB = svg.append("path") .attr("class", "lineB") // 1. Determine the range. The range is your screen space display. var loadRange = function() { return { x: [0, width], y: [height, 0] } } // 2. Determine the domain. The domain is your data (e.g. income per capita). var loadDomain = function(xValues, yValues) { return { x: d3.extent(xValues), // year y: d3.extent(yValues) // Example: population } } // 3. Determine the scale, which requires domain and range. var loadScale = function(domain, range) { return { x: d3.scaleLinear().domain(domain.x).range(range.x), y: d3.scaleLinear().domain(domain.y).range(range.y) } } // 4. Determine the axes, which requires scale var loadAxis = function(scale){ return { x: d3.axisBottom().scale(scale.x).tickFormat(d3.format("d")), y: d3.axisLeft().scale(scale.y).ticks(10, ",.1s"), } } function getXAxisValueExtent(nationsData) { var nationYears = nationsData.map(function(nation) { // Determine Min and Max for each var incomeMinMax = d3.extent(nation.income, function(i) {return i[0]}) var lifeMinMax = d3.extent(nation.lifeExpectancy, function(i) {return i[0]}) var popMinMax = d3.extent(nation.population, function(i) {return i[0]}) // Merge the arrays var allMinMax = d3.merge([incomeMinMax, lifeMinMax, popMinMax]) // Find the extent of all the arrays return d3.extent(allMinMax) }) var xValuesYearMinMax = d3.extent(d3.merge(nationYears)); return xValuesYearMinMax // [1800, 2009] } var findByYear = function(data, year) { var resultArry = data.find(function(i) { return i[0] === year }) if(resultArry) { return resultArry[1] } } // 8. Build an object for category var buildObjectForCountry = function(nation, year, categoryStr) { if (categoryStr === "population") { var n = { country: nation[0].name, year: year, category: "population", value: findByYear(nation[0].population, year) } return n } else if (categoryStr === "lifeExpectancy") { var n = { country: nation[0].name, year: year, category: "lifeExpectancy", value: findByYear(nation[0].lifeExpectancy, year) } return n } else if (categoryStr === "income"){ var n = { country: nation[0].name, year: year, category: "income", value: findByYear(nation[0].income, year) } return n } } // 7. Get original data for specified country function getDataForCountry(nationsData, countryName) { return nationsData.filter(function(nation) { if (nation.name === countryName ) { return nation } }); } function getLineDataForCountryAndCategory(xValuesYearMinMax, nationsData, countryName, categoryStr) { var data = []; var originalDataForOneCountry = getDataForCountry(nationsData, countryName); for(var year=xValuesYearMinMax[0]; year<=xValuesYearMinMax[1]; ++year) { var newNation = buildObjectForCountry(originalDataForOneCountry, year, categoryStr) if (newNation.value) { data.push([newNation.year, newNation.value]); } } return data; } function drawLines(svg, scale, lineDataA, lineDataB) { var line = d3.line() .x(function(d) { return scale.x(d[0])}) .y(function(d) { return scale.y(d[1])}) appendLine.datum(lineDataA) .attr("d", line) appendLineB.datum(lineDataB) .attr("d", line); } function getArrayofCategoryValuesForDomainExtent(xValuesYearMinMax, originalDataForOneCountry, categoryStr) { var arrayOfYAxisValuesForCountry = []; for(var year=xValuesYearMinMax[0]; year<=xValuesYearMinMax[1]; ++year) { var newNation = buildObjectForCountry(originalDataForOneCountry, year, categoryStr) if (newNation.value) { arrayOfYAxisValuesForCountry.push(newNation.value); } } return arrayOfYAxisValuesForCountry; } function drawChart(xValuesYearMinMax, nationsData, countryNameA, countryNameB, categoryStr) { var originalDataForOneCountryA = getDataForCountry(nationsData, countryNameA); var originalDataForOneCountryB = getDataForCountry(nationsData, countryNameB); var arrayOfYAxisValuesForCountryA = getArrayofCategoryValuesForDomainExtent(xValuesYearMinMax, originalDataForOneCountryA, categoryStr); var arrayOfYAxisValuesForCountryB = getArrayofCategoryValuesForDomainExtent(xValuesYearMinMax, originalDataForOneCountryB, categoryStr); // Merge arrays to find the min-max of y values for both countries for a single category var mergedArrays = d3.merge([arrayOfYAxisValuesForCountryA, arrayOfYAxisValuesForCountryB]); var domain = loadDomain(xValuesYearMinMax, mergedArrays); var range = loadRange(); var scale = loadScale(domain, range); var axes = loadAxis(scale); xAxis.call(axes.x); yAxis.call(axes.y) var lineDataA = getLineDataForCountryAndCategory(xValuesYearMinMax, nationsData, countryNameA, categoryStr); var lineDataB = getLineDataForCountryAndCategory(xValuesYearMinMax, nationsData, countryNameB, categoryStr); drawLines(svg, scale, lineDataA, lineDataB) } function populateCategoryDropdown() { var dropdownOptions = [{text: "Population", value: "population"}, {text: "Income Per Capita", value: "income"}, {text: "Life Expectancy", value: "lifeExpectancy"}] d3.select("#dropdown-category") .selectAll("option") .data(dropdownOptions) .property("selected", function(d){ return d === 'population'; }) .enter() .append("option") .attr("value", function(option) { return option.value; }) .text(function(option) { return option.text; }) } function populateCountryDropdownA(nationsData) { var dropdownOfCountries = [] nationsData.forEach(function(nation) { dropdownOfCountries.push({ text: nation.name, value: nation.name}) // Alphabetize via sort dropdownOfCountries.sort(function(a, b){ if(a.value < b.value) return -1; if(a.value > b.value) return 1; return 0; }) }); d3.select("#dropdown-countriesA") .selectAll("option") .data(dropdownOfCountries) .property("selected", function(d){ return d === 'Afghanistan'; }) .enter() .append("option") .attr("value", function(option) { return option.value; }) .text(function(option) { return option.text; }) } function populateCountryDropdownB(nationsData) { var dropdownOfCountriesB = [] nationsData.forEach(function(nation) { dropdownOfCountriesB.push({ text: nation.name, value: nation.name}) // Reverse alphabetize via sort dropdownOfCountriesB.sort(function(a, b){ if(a.value > b.value) return -1; if(a.value < b.value) return 1; return 0; }) }); d3.select("#dropdown-countriesB") .selectAll("option") .data(dropdownOfCountriesB) .property("selected", function(d){ return d === 'Zimbabwe'; }) .enter() .append("option") .attr("value", function(option) { return option.value; }) .text(function(option) { return option.text; }) } function updateChartByDropdownCategoryAndCountry(xValuesYearMinMax, nationsData) { populateCategoryDropdown() var dropdownCategory = d3.select("#dropdown-category"); var dropdownCountryA = d3.select("#dropdown-countriesA"); var dropdownCountryB = d3.select("#dropdown-countriesB"); // Initialize drawChart drawChart(xValuesYearMinMax, nationsData, dropDownSelectForCountryA, dropDownSelectForCountryB, dropDownSelectForCategory) dropdownCategory.on("change", function() { dropDownSelectForCategory = this.value; svg.selectAll("text.yAxisText").remove(); svg.append("text") .attr("class", "yAxisText") .attr("transform", "rotate(-90)") .attr("y", 0 - margin.left) .attr("x",0 - (height / 2)) .attr("dy", "1em") .style("text-anchor", "middle") .text(this.value); drawChart(xValuesYearMinMax, nationsData, dropDownSelectForCountryA, dropDownSelectForCountryB, dropDownSelectForCategory) }) dropdownCountryA.on("change", function() { dropDownSelectForCountryA = this.value; drawChart(xValuesYearMinMax, nationsData, dropDownSelectForCountryA, dropDownSelectForCountryB, dropDownSelectForCategory) }) dropdownCountryB.on("change", function() { dropDownSelectForCountryB = this.value; drawChart(xValuesYearMinMax, nationsData, dropDownSelectForCountryA, dropDownSelectForCountryB, dropDownSelectForCategory) }) } d3.json('nations.json', function(error, nations) { // console.log('Original data', nations) var xValuesYearMinMax = getXAxisValueExtent(nations); populateCountryDropdownA(nations); populateCountryDropdownB(nations); updateChartByDropdownCategoryAndCountry(xValuesYearMinMax, nations) }) </script> </body>
https://d3js.org/d3.v4.min.js