Skip to content

Instantly share code, notes, and snippets.

@timproDev
Last active September 28, 2017 20:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timproDev/22c0242095bd6944c3adf8a74ea6952c to your computer and use it in GitHub Desktop.
Save timproDev/22c0242095bd6944c3adf8a74ea6952c to your computer and use it in GitHub Desktop.
World Map Pattern

Map Only

function worldMap() {
	var chartContainer = d3.select(".geo-chart-map").html("");
	var margin = {top:0,left:0,right:0,bottom:0};
	var w = chartContainer.node().clientWidth - margin.left - margin.right,
		h = chartContainer.node().clientHeight;
	
	// height needss to somehow be dynamic

	var zoomScale = 2;
	
	var svg = chartContainer
		.append("svg")
		.attr("width", w + margin.left + margin.right)
		.attr("height", h + margin.top + margin.bottom);

	var zoomIt = d3.zoom()
		.scaleExtent([1,10])
		// [[x0, y0], [x1, y1]]
		.translateExtent([[0,0], [w, h+(h*.25)]])
		.on("zoom", zoomed);	

	var view = svg
		.append("g")		
		.attr("transform","translate("+ margin.left +"," + margin.top +")");
	
	function zoomed() {
		view.attr("transform", d3.event.transform);
	}

	d3.select("[data-ui=zoom-reset]")
	    .on("click", resetted);

	d3.select("[data-ui=zoom-in]")
	    .on("click", zoomIn);

	d3.select("[data-ui=zoom-out]")
	    .on("click", zoomOut);

	function resetted() {
	  svg.transition()
	      .duration(750)
	      .call(zoomIt.transform, d3.zoomIdentity);
	}

	function zoomIn() {
	  svg.transition()
	      .duration(750)
	      .call(zoomIt.scaleBy, zoomScale*(zoomScale/2));
	}

	function zoomOut() {
	  svg.transition()
	      .duration(750)
	      .call(zoomIt.scaleBy, zoomScale/(zoomScale*2));
	}

	svg.call(zoomIt);

///////////////
//// initi ////
///////////////	
	d3.queue()
		.defer(d3.json,"https://unpkg.com/world-atlas@1/world/110m.json")
		// data goes here
		// .defer(d3.csv, "common/data/prm-2018-bmi.csv")
		.await(ready);
	function ready(err, data) {
	  if (err) throw error;

/////////////////////////
//// Data manipulate ////
///////////////	/////////

	 // data manipulation and correlation here
		
////////////////////
//// Render Map ////
///////////////	////
		
		var world = topojson.feature(data, data.objects.countries);		
		
		var path = d3.geoPath()
			.projection(d3.geoMercator()				
				.fitExtent([[0, 0], [w, h+(h*.25)]], world));
			
		var country = view.selectAll(".country-path")
			.data(world.features)
			.enter()
			.append("path")
			.attr("class","country-path")
			.style("cursor","pointer")
			.attr("d", function(d){
				return path(d);
			});
			// .style("fill", function(d) {return threshold(valByCountry[d.properties.UN]); });
	};

}

Map with Functionality

// https://bl.ocks.org/mbostock/2206590
// zoom to bounding box https://bl.ocks.org/mbostock/4699541
// country shape joining and cuts
// https://stackoverflow.com/questions/19041457/how-can-i-remove-a-line-from-the-110m-topojson-world-map

// responsive map
// http://bl.ocks.org/jczaplew/4444770


// missing data
// sudan - BMI has 736 - the code should be 729
// country number 732 - Western Sahara
// -99 - cyprus - latvia? somalia region??
// 238 Falkland Islands (Malvinas)
// 260 French Southern Territories


// helpful links
// https://bl.ocks.org/mbostock/7ec977c95910dd026812

// -99 might mean disputed territory like Kosovo. this topojson has 3
//  also, we need BMI to provide the UN numeric-3 country code: M49 Standard
// http://www.nationsonline.org/oneworld/countrycodes/countrycodes_g.htm
// Naming comventions between un and prm need to match - otherwise, we need BMI to provide he M49 standard number IDs
// https://stackoverflow.com/questions/18326689/javascript-textcontent-is-not-working-in-ie8-or-ie7
// http://nicolasgallagher.com/canvas-fix-svg-scaling-in-internet-explorer/
// https://github.com/d3/d3-selection/blob/master/README.md#selection_insert
// https://bl.ocks.org/mbostock/db6b4335bf1662b413e7968910104f0f

var prmChoroMap = {
	init:function(){
		var chartContainer = d3.select(".geo-chart-map").html("");
		var margin = {top:0,left:0,right:0,bottom:0};
		var w = chartContainer.node().clientWidth - margin.left - margin.right,
				h = chartContainer.node().clientHeight;
	
		var svg = chartContainer
			.append("svg")
			.attr("width", w + margin.left + margin.right)
			.attr("height", h + margin.top + margin.bottom)
			.append("g");		

////////////////////////////////////////////////////// ZOOM

		var zoomScale = 2;
		var transitionSpeed = 900;
		var zoom = d3.zoom()
			.scaleExtent([1,10])
			// [[x0, y0], [x1, y1]]
			.translateExtent([[0,0], [w, h+(h*.25)]])
			.on("zoom", zoomed);	

		var view = svg
			.append("g");
		
		function zoomed() {
			view.attr("transform", d3.event.transform);
		}		

		d3.select("[data-ui=zoom-reset]")
		    .on("click", resetted);

		d3.select("[data-ui=zoom-in]")
		    .on("click", zoomIn);

		d3.select("[data-ui=zoom-out]")
		    .on("click", zoomOut);

		var initialTransform = d3.zoomIdentity
	    .translate(0,0)
	    .scale(1);

		function resetted() {			
		  svg.transition()
		      .duration(transitionSpeed)
		      .call(zoom.transform, initialTransform);
		}

		function zoomIn() {
		  svg.transition()
		      .duration(transitionSpeed)
		      .call(zoom.scaleBy, zoomScale*(zoomScale/2));
		}

		function zoomOut() {
		  svg.transition()
		      .duration(transitionSpeed)
		      .call(zoom.scaleBy, zoomScale/(zoomScale*2));
		}
		
		svg.call(zoom).on("wheel.zoom", null);		
    
// ZOOM END

//// READY

	d3.queue()
		.defer(d3.json,"https://unpkg.com/world-atlas@1/world/50m.json")		
		.defer(d3.csv, "common/data/prm-2018-bmi.csv")
		.await(ready);
	function ready(err, data, prmdata) {
	  if (err) throw error;		
				
//// Ready Variables

		var toggleLinks = d3.selectAll(".dv-wmap-mtoggle-item a");
		var mapTitle = d3.select(".dv-wmap-mtoggle-title");
		var overlay = d3.select(".overlay");
		var overlayDetail = overlay.select(".overlay-detail");
		var overlayTitle = overlay.select(".overlay-detail-title");
		var overlayClose = overlay.select(".overlay-close");
		var overlayBlurb = overlay.select(".overlay-detail-blurb");
		var overlayChartContainer = overlay.select(".overlay-chart-container");
		var active = d3.select(null);
		var newIdentity;		

//// Data manipulate | build x-data reference methods

		var countryByCode = {};
			prmdata.forEach(function(d){
			countryByCode[d["ISO Numeric Code"]] = d.Country;
		});

		var criByCode = {};
			prmdata.forEach(function(d){
			criByCode[d["ISO Numeric Code"]] = +d.CRI;
		});

		var opriByCode = {};
			prmdata.forEach(function(d){
			opriByCode[d["ISO Numeric Code"]] = +d.OPRI;
		});

		var ltpriByCode = {};
			prmdata.forEach(function(d){
			ltpriByCode[d["ISO Numeric Code"]] = +d.LTPRI;
		});

		var lteriByCode = {};
			prmdata.forEach(function(d){
			lteriByCode[d["ISO Numeric Code"]] = +d.LTERI;
		});

		var stpriByCode = {};
			prmdata.forEach(function(d){
			stpriByCode[d["ISO Numeric Code"]] = +d.STPRI;
		});

		var steriByCode = {};
			prmdata.forEach(function(d){
			steriByCode[d["ISO Numeric Code"]] = +d.STERI;
		});

		var blurbByCode = {};
			prmdata.forEach(function(d){
			blurbByCode[d["ISO Numeric Code"]] = d["Political Blurb"];
		});

//// Render Map

		var world = topojson.feature(data, data.objects.countries);		

		var path = d3.geoPath()		
			.projection(d3.geoMercator()
				.rotate([-8,0])
				// .fitExtent([[0, -h+(h*.75)], [w, h+(h*.5)]], world));
			// .fitExtent([[0, 0], [w, h+(h*.25)]], world));
			.fitExtent([[0, 0], [w, h]], world));

		var fullData = world.features;
		
//// Manipulate topo data

		fullData.forEach(function(d){
			d.country = countryByCode[d.id];
			d.steri = steriByCode[d.id];
			d.lteri = lteriByCode[d.id];
			d.stpri = stpriByCode[d.id];
			d.ltpri = ltpriByCode[d.id];
			d.opri = opriByCode[d.id];
			d.cri = criByCode[d.id];
			d.blurb = blurbByCode[d.id];
		});

//// country click function		

		function clicked(d) {
		
//// zoom map

		  if (active.node() === this) return reset(d);
		  active.classed("active", false);
		  view.selectAll(".country-path").style("fill","#ffffff");
		  active = d3.select(this).classed("active", true);
		  active.style("fill",function(d){
		  	if (d.cri == undefined) {
					return "#d8d8d8";
				} else {
					return thresholdCri(d.cri);
				}
		  });

		  var bounds = path.bounds(d),
		      dx = bounds[1][0] - bounds[0][0],
		      dy = bounds[1][1] - bounds[0][1],
		      x = (bounds[0][0] + bounds[1][0]) / 2,
		      y = (bounds[0][1] + bounds[1][1]) / 2,
		      scale = .9 / Math.max(dx / (w/3), dy / h),
		      translate = [w / 4 - scale * x, h / 3 - scale * y];

		  newIdentity = d3.zoomIdentity
		  	.translate(w / 4 - scale * x, h / 3 - scale * y)
		    .scale(.9 / Math.max(dx / (w/3), dy / h));

		  svg.transition()
		      .duration(transitionSpeed)
		      .call(zoom.transform, newIdentity);

// zoom map end
						
  		overlay.classed("is-show", true);
			overlayTitle.html(d.country + "<p class=\'sub-title\'>Risk Index Score</p>");
			overlayBlurb.html(d.blurb);
			overlayChartContainer.html("");
			barChart(d);

			function barChart(d){
				
//// manipuate data | create index only dataset
				
				var barData = d3.entries(d).filter(function(d){
					return d.key == "steri" ||
						d.key == "cri" ||
						d.key == "lteri" ||
						d.key == "opri" ||
						d.key == "ltpri" ||
						d.key == "stpri";
					;
				});
				
//// manipuate data | Rename the labels

				barData.forEach(function(d){
						if (d.key == "steri") {
							d.name = "Short Term Economic Risk";
						}
						if (d.key == "cri") {
							d.name = "Country Risk";
						}
						if (d.key == "lteri") {
							d.name = "Long Term Economic Risk";
						}
						if (d.key == "opri") {
							d.name = "Operational Risk";
						}
						if (d.key == "ltpri") {
							d.name = "Long Term Political Risk";
						}
						if (d.key == "stpri") {
							d.name = "Short Term Political Risk";
						}
				});
				
				var barMargin = {
					top:40,
					left:0,
					right:0,
					bottom:80
				};

				var barW = overlayChartContainer.node().clientWidth - barMargin.left - barMargin.right,
						barH = 400 - barMargin.top - barMargin.bottom;
				
				var barxscale = d3.scaleBand()
					.rangeRound([0,barW])
					.domain(barData.map(function(d){ return d.key;}))
					.paddingInner(0.15);

				var baryscale = d3.scaleLinear()
					.domain([0, 100])
					.range([barH,0]);

				var barxaxis = d3.axisBottom(barxscale);

				var barSvg = overlayChartContainer
					.append("svg")
					.attr("width", (barW + barMargin.left + barMargin.right))
					.attr("height", (barH + barMargin.top + barMargin.bottom))
					.append("g.bar-wrap")					
					.translate([barMargin.left,barMargin.top]);				

				barSvg.append("g.x-axis")
					.call(barxaxis)
					.attr("transform","translate(0," + barH + ")")
					.selectAll("text")
					.style("text-anchor","middle")
					.classed("x-label",true)
					.attr("dy", 0)
					.attr("x", 0)
					.attr("y", 10);

				var gRect = barSvg.selectAll("g.g-rects")				
					.data(barData)
					.enter()
					.append("g.g-rects");

				gRect
					.append("rect.comp-rect")
					.attr("x",function(d){
						return barxscale(d.key);
					})
					.attr("y",function(d){
						return baryscale(d.value) - 5;
					})
					.attr("width",barxscale.bandwidth())
					.attr("height",function(d){
						return barH - baryscale(d.value);
					})
					.style("fill", function(d){					
						return thresholdCri(d.value);
					});

				gRect
					.append("text")
		      .attr("x", function(d) { return barxscale(d.key) + barxscale.bandwidth()/2; })
		      .attr("y", function(d) { return baryscale(d.value) - 15; })
		      .attr("dy", 2)
		      .attr("text-anchor","middle")
		      .text(function(d){ return d.value; });
			}

// end barchart

		}

// end clicked

//// reset function

		function reset(d) {
		  active.classed("active", false);
		  active = d3.select(null);
		  overlayTitle.html("");
			overlayBlurb.html("");
			overlayChartContainer.html("");
			overlay.classed("is-show", false);			
			view.selectAll(".country-path").style("fill", function(d){
				if (d.cri == undefined) {
					return "#d8d8d8";
				} else {
					return thresholdCri(d.cri);
				}
			});
		  resetted();
		}

// end reset

		// color
		// threshold = domain needs one less value that range		
		var thresholdCri = d3.scaleThreshold()	
			.domain([50, 60, 70, 80, 100])
			.range(['#ef4f46','#fcaf17','#ffcc7b','#bae1d3','#7ecab2','#ffffff']);

		var thresholdOpri = d3.scaleThreshold()	
			.domain([50, 60, 70, 80, 100])
			.range(['#004280','#0080b2','#00b0d3','#66d0e5','#9edcea','#ffffff']);

		var thresholdLtpri = d3.scaleThreshold()	
			.domain([50, 60, 70, 80, 100])
			.range(['#8d380a','#c55f24','#f58233','#f9b484','#fdd9bc','#ffffff']);

		var thresholdLteri = d3.scaleThreshold()	
			.domain([50, 60, 70, 80, 100])
			.range(['#00582d','#128c3f','#72bf44','#aad98f','#cbe4b7','#ffffff']);

		var country = view.selectAll(".country-group")
			.data(fullData)
			.enter()
			.append("g.country-group");

		var countryPath = country
			.append("path")
			.attr("class","country-path")
			.attr("d", function(d){
				return path(d);
			})
			.on("click", clicked)
			.style("fill", function(d) {
				if (criByCode[d.id] == undefined) {
					return "#d8d8d8";
				} else {
					return thresholdCri(d.cri);
				}
			});			

		// Events				
		
		country
			.on("mouseenter", function(d){					
				d3.select('body').selectAppend('div.tooltip');
				d3.select(this).call(d3.attachTooltip);
				d3.select('.tooltip')
					.html("<p class=\'tooltip-title\'>" + d.country +						
						"</p><p class=\'tooltip-info\'>Country Risk Index: <span>" + d.cri + "</span>" +
						"</p><p class=\'tooltip-info\'>Political Risk Index: <span>" + d.ltpri + "</span>" +
						"</p><p class=\'tooltip-info\'>Operational Risk Index: <span>" + d.opri + "</span>" +
						"</p><p class=\'tooltip-info\'>Economic Risk Index: <span>" + d.lteri + "</span></p>");
			});

		overlayClose
			.on("click", function(){
				overlayTitle.html("");
				overlayBlurb.html("");
				overlayChartContainer.html("");
				overlay.classed("is-show", false);
				reset();
			});

		toggleLinks.on("click", function(){
			var d3this = d3.select(this);
			var dataVal = d3this.attr("data-map");
			toggleLinks.classed("is-inactive", false);
			d3this.classed("is-inactive", true);
			mapTitle.text(d3this.text());
			svg.selectAll(".country-path")
				.transition().duration(transitionSpeed)
				.style("fill", function(d) {
					// if no data, color grey
					if (criByCode[d.id] == undefined) {
						return "#d8d8d8";
					} else {
						if (dataVal == "lteri") {
							return thresholdLteri(d.lteri);
						}
						if (dataVal == "ltpri") {
							return thresholdLtpri(d.ltpri);
						}
						if (dataVal == "opri") {
							return thresholdOpri(d.opri);
						}
						if (dataVal == "cri") {
							return thresholdCri(d.cri);
						}
					}
				});
				mxDropMenu.close(".dv-wmap-mtoggle-list", ".dv-wmap-mtoggle-select", ".geo-chart-map");
			});
		}; // end of ready
	}
};

var mxDropMenu = {	
	open:function(elem, btn, map){
		$(map).addClass("is-blur");
		$(btn).addClass("is-active");
		$(elem).addClass('is-drop');		
	},
	close:function(elem, btn, map){
		$(map).removeClass("is-blur");
		$(btn).removeClass("is-active");
		$(elem).removeClass('is-drop');		
	},
	init: function(settings){		
		$(settings.button).on('click', function(){			
			if ($(settings.list).hasClass('is-drop')) {
				// if open
				mxDropMenu.close(settings.list, settings.button, settings.map);
			} else {
				// if closed				
				mxDropMenu.open(settings.list, settings.button, settings.map);
			}			
		});
	}
};
var settings = {
	list: ".dv-wmap-mtoggle-list",
	button: ".dv-wmap-mtoggle-select",
	map:".geo-chart-map"
}
mxDropMenu.init(settings);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment