Skip to content

Instantly share code, notes, and snippets.

@shshaw
Last active August 29, 2015 14:03
Show Gist options
  • Save shshaw/9e49ea23d68eb761701b to your computer and use it in GitHub Desktop.
Save shshaw/9e49ea23d68eb761701b to your computer and use it in GitHub Desktop.
SDI Map
// jQuery's removeClass doesn't work for SVG, but this does!
// takes the object obj to remove from, and removes class remove
// returns true if successful, false if remove does not exist in obj
(function($){
$.fn.addClassSVG = function(addclass) {
var elem,
i = 0,
len = this.length;
for (; i < len; i++ ) {
elem = this[ i ];
var classes = $(elem).attr('class');
var index = classes.search(addclass);
if (index !== -1) { return this; }
else {
classes = classes + " " + addclass;
$(elem).attr('class', classes);
return this;
}
}
return this;
};
$.fn.removeClassSVG = function(remove) {
var elem,
i = 0,
len = this.length;
for (; i < len; i++ ) {
elem = this[ i ];
var classes = $(elem).attr('class');
var index = classes.search(remove);
if (index === -1) { return this; }
else {
classes = classes.substring(0, index) + classes.substring((index + remove.length), classes.length).trim();
$(elem).attr('class', classes);
return this;
}
}
return this;
};
})(jQuery);
var LocationMap = function(target) {
showLocations = function(country) {
if ( currentCountry !== country ) {
currentCountry = country;
showMarkers(country);
showListing(country);
centerMap(country);
} else {
currentCountry = "";
showMarkers();
resetMap();
hideListing(country);
}
resetPoints();
hideTooltip();
};
hideLocations = function(country) {
currentCountry = "";
showMarkers();
// hideMarkers();
resetMap();
hideListing(country);
resetPoints();
hideTooltip();
};
var $target = $(target);
var width,
height,
mapCenterX,
mapCenterY,
mapOffsetTop;
var map = d3.select(target);
var svgContainer = map
.append("svg").attr("id","Map-SVG");
var svg = svgContainer
.append("g").attr("id","Map-Group");
$("#Map-SVG, #Map-Group").attr("width", "100%").attr("height", "100%");
this.resizeMap = function() {
width = $target.outerWidth();
height = $target.outerHeight();
mapCenterX = width/2;
mapCenterY = height/2;
mapTranslate = [mapCenterX, 205];
mapOffsetTop = $target.offset().top;
svg.attr("width", width).attr("height", height);
// console.log("mapCenter",mapCenterX);
};
this.resizeMap();
var mapScale = 100;
var svgScaleMax = 4;
svgScaleMin = 0.75;
var projection = d3.geo.mercator()
.translate(mapTranslate)
.scale(mapScale);
var path = d3.geo.path()
.projection(projection);
var markers = [],
markerRadius = 2.5,
currentCountry;
var mapCountries = svg.append("g").attr("id","Map-Countries");
var renderMap = function (error, world) {
$target.addClass("is-Loaded");
var land = topojson.object(world,world.objects.land).geometries,
landPaths = mapCountries
.selectAll()
.data(land)
.enter()
.append("path")
.attr("class", "land")
.attr("d", path);
};
queue()
.defer(d3.json, "/content/themes/sdi/js/data/world.json")
.await(renderMap);
var resetPoints = function () {
d3.select(".Location.is-Selected").classed("is-Selected",false).attr("r", markerRadius);
};
var tooltip = $("<div id='Location-Details'></div>").hide().appendTo(".Locations-Map"),
tooltipContent = $("<div class='Content'></div>").appendTo(tooltip),
tooltipCurrently = $("<aside class='Currently'>Currently </aside>").appendTo(tooltip),
currentTime = $("<time class='Time Accent' />").appendTo(tooltipCurrently),
tooltipWeather = $("<span class='Weather'></span>").appendTo(tooltipCurrently),
tooltipVisible = false;
////////////////////////////////////////
// Get Point Weather
var getPointWeather = function (point) {
var $point = $(point);
if ( $point.data("weather") !== undefined ) {
return $point.data("weather");
} else {
////////////////////////////////////////
// Weather AJAX
tooltipWeather.html('').addClass("is-Loading");
var latlng = $point.data("latlng").split(",");
// Yahoo's Geo.PlaceFinder API http://developer.yahoo.com/geo/placefinder/ We are passing the R gflag for reverse geocoding (coordinates to place name)
var geoAPI = 'SELECT woeid FROM geo.placefinder WHERE text="'+latlng[0]+','+latlng[1]+'" and gflags="R"',
geoYQL = 'http://query.yahooapis.com/v1/public/yql?q='+encodeURIComponent(geoAPI)+'&format=json&callback=?';
// console.log("geoAPI",geoAPI);
// Fetch the WOEID from coordinates
$.ajax({url: geoYQL,dataType: 'jsonp',success: function(data){
if ( data.query.count >= 1 ){
var woeid = data.query.results.Result.woeid;
// console.log("woeid",woeid);
// Forming the query for Yahoo's weather API with YQL http://developer.yahoo.com/weather/
var wsql = 'select item.condition from weather.forecast where woeid=WID', // and u="F"
weatherYQL = 'http://query.yahooapis.com/v1/public/yql?q='+encodeURIComponent(wsql)+'&format=json&callback=?';
// Make a weather API request:
$.ajax({url: weatherYQL.replace('WID',woeid),dataType: 'jsonp',
success: function(data){
// console.log("weather data",data);
if (data.query.count === 1) {
var f = data.query.results.channel.item.condition.temp;
var c = Math.round(( f -32 ) * 5 / 9);
$point.data('weather',', <strong class="Temp Farenheit Accent">'+f+'&deg;F</strong> <strong class="Temp Celsius Accent">('+c+'&deg;C)</strong>');
} else {
$point.data('weather',false);
return false;
}
},error: function(){
$point.data('weather',false);
return false;
},complete: function(){
showTooltip(point,true);
}
});
}
},error: function(){
$point.data('weather',false);
return false;
}});
}
};
////////////////////////////////////////
// Get Point Address
var getPointDetails = function (point) {
var $point = $(point),
country = $point.data("country"),
output = false;
showListing(country);
if ( $point.data("details") ) {
output = $point.data("details");
} else {
var address = $("#Location-"+$point.data("id")).html();
output = $("<div class='Address vcard "+country+"'>"+address+"</div>");
}
$point.data("details",output);
return output;
};
var showWeather = function(point,update) {
var weather = getPointWeather(point);
if ( update === true && weather !== undefined ) {
tooltipWeather.stop().fadeOut(maxAnimation,function(){
tooltipWeather
.removeClass("is-Loading")
.html(weather)
.fadeIn(maxAnimation);
});
} else if ( weather !== undefined ) {
tooltipWeather.removeClass("is-Loading").html(weather);
} else {
tooltipWeather.html("");
}
};
var timeUpdating = false;
var timeTimer;
var showTime = function(timezoneid,forceUpdate) {
// console.log("timezone",timezoneid,timeUpdating,forceUpdate);
if ( forceUpdate || !tooltipVisible ) {
// console.log("clearing timeout");
window.clearTimeout(timeTimer);
timeUpdating = false;
}
if ( timezoneid === undefined ) {
currentTime.hide().html("");
} else if ( timezoneid && ( forceUpdate || !timeUpdating) ) {
try {
currentTime.show();
timeUpdating = true;
var time = moment().tz(timezoneid);
currentTime.html(time.format("h") + "<span>:</span>" + time.format("mm a"));
// + " <small>"+ moment.utc().zone(offset).format("dddd, MMMM Do YYYY, h:mm:ss a") + "</small>");
timeTimer = window.setTimeout(function(){
timeUpdating = false;
showTime(timezoneid, false);
},10000);
} catch(err) {
currentTime.hide().html("");
}
}
};
////////////////////////////////////////
// Show Location Tooltip
var showTooltip = function(point,update) {
if ( !tooltipVisible ) {
tooltipVisible = true;
redrawMap();
}
tooltip.addClass($point.data("country"));
tooltipContent.html(getPointDetails(point));
if ( Modernizr.mq('(min-width: 600px)') ) {
showWeather(point);
showTime($(point).data("timezoneid"),true);
}
tooltip.not(":visible").fadeIn(maxAnimation);
};
var hideTooltip = function() {
tooltipVisible = false;
redrawMap();
tooltip.fadeOut(maxAnimation,function(){ tooltip.attr("class",""); });
};
////////////////////////////////////////
// Update Points on Map
this.buildMarkers = function () {
$.each(locations,function(index,location){
var country = index;
var countryMarkersId = country+"Markers";
var countryMarkers = $("#"+countryMarkersId);
if ( ! countryMarkers.length ) {
countryMarkersSvg = svg.append("g").attr("id",countryMarkersId).attr("class","hide Country-Markers "+country);
}
$.each(location.posts,function(index,post){
var el = post;
if ( el.latlng !== "" && typeof el.latlng !== 'undefined' ) {
var latlng = el.latlng.split(","),
coord = projection([latlng[1],latlng[0]]);
point = countryMarkersSvg.append("svg:circle")
.attr("class","Location Marker")
.attr("id", "Marker-"+el.id)
.attr("cx", coord[0]).attr("cy", coord[1])
.attr("r", markerRadius)
.attr("data-id", el.id)
.attr("data-country", country)
.attr("data-latlng",latlng)
.attr("data-timezone", post.custom_fields.timezone_offset)
.attr("data-timezoneid", post.custom_fields.timezone_id)
.on({
click : function() {
highlightPoint(this);
},
mouseover : function() {
d3.select(this).classed("is-Hover",true);
},
mouseout : function() {
d3.select(this).classed("is-Hover",false);
}
});
point.data = { coord: el.latlng };
markers.push(point);
}
});
});
showMarkers();
};
////////////////////////////////////////
// Update Points on Map
var showMarkers = function (country) {
if ( country ) {
var countryMarkersId = country+"Markers";
var countryMarkers = $("#"+countryMarkersId);
d3.select(".Country-Markers.is-Selected").classed("is-Selected",false);
$(".Country-Markers")
.not(countryMarkers)
.fadeOut(maxAnimation);
// .removeClass("is-Selected");
countryMarkers
.fadeIn(maxAnimation)
.addClassSVG("is-Selected");
} else {
d3.select(".Country-Markers.is-Selected").classed("is-Selected",false);
$(".Country-Markers")
.fadeIn(maxAnimation);
// .removeClass("is-Selected");
}
};
var hideMarkers = function() {
d3.select(".Country-Markers.is-Selected").classed("is-Selected",false);
$(".Country-Markers")
.fadeOut(maxAnimation);
// .removeClass("is-Selected");
};
$locationNavItems.hover(function(){
var country = $(this).data("country");
if ( country ) {
var countryMarkersId = country+"Markers";
var countryMarkers = $("#"+countryMarkersId).addClassSVG("is-Hover");
}
},function(){
var country = $(this).data("country");
if ( country ) {
var countryMarkersId = country+"Markers";
var countryMarkers = $("#"+countryMarkersId).removeClassSVG("is-Hover");
}
});
////////////////////////////////////////
// Map Zooming & panning
////////////////////////////////////////
////////////////////////////////////////
// Highlight a Point on Map
var highlightPoint = function (point) {
resetPoints();
d3.select(point).classed("is-Selected",true).attr("r", markerRadius*1.5);
$point = $(point);
$point.parent().append($point);
showTooltip(point);
centerMap(point);
};
////////////////////////////////////////
// Set Min/Max on Scale
var scaleBounds = function(scale) {
if ( scale > svgScaleMax ) scale = svgScaleMax;
if ( scale < svgScaleMin ) scale = svgScaleMin;
return scale;
};
////////////////////////////////////////
// Apply translate & scale to Map SVG
var redrawMap = function(translate,scale,transition) {
if (typeof scale !== 'undefined' ) { myZoom.scale(scaleBounds(scale)); }
if (typeof translate !== 'undefined') { myZoom.translate(translate); }
if (typeof transition === 'undefined') { transition = maxAnimation*1.5; }
var transform = "translate("+( tooltipVisible ? myZoom.translate()[0] - 100 : myZoom.translate()[0] )+","+myZoom.translate()[1]+") scale("+myZoom.scale()+")";
svg.transition().duration(transition).attr("transform",transform);
};
var resetMap = function(){
redrawMap([0,0],1);
};
////////////////////////////////////////
// Map Zooming
var myZoom = d3.behavior.zoom()
.scaleExtent([svgScaleMin, svgScaleMax])
.on("zoomstart", function(){
$target.addClass("is-Moving");
})
.on("zoom", function(){
redrawMap(d3.event.translate,d3.event.scale,25);
})
.on("zoomend", function(){
$target.removeClass("is-Moving");
});
svgContainer.call(myZoom);
////////////////////////////////////////
// Center Map on Country or Point
var centerMap = function(point) {
var $point = $(point);
if ( $point.length ) {
country = $point.data("country");
svgBounds = $point[0].getBBox();
} else {
country = point;
svgBounds = $("#"+country+"Markers")[0].getBBox();
}
if ( svgBounds !== undefined ) {
var translate,
scale = 2.8; // ideal scale
scaleHoriz = ( mapScale / Math.abs(svgBounds.width + 10 ) )*scale;
scaleVert = ( mapScale / Math.abs(svgBounds.height + 10 ) )*scale;
scale = scaleBounds(Math.min(scaleHoriz,scaleVert));
translate = [
(mapCenterX - ((svgBounds.x + svgBounds.width/2) * scale)),
(mapCenterY - ((svgBounds.y + svgBounds.height/2) * scale)),
];
redrawMap(translate,scale);
} else {
resetMap();
}
};
////////////////////////////////////////
// Reveal Marker & Details on Locations List Click
$root.on("click","#Location-Listings .Address",function(){
var id = $(this).data("id");
highlightPoint("#Marker-"+id);
});
};
var RenderedMap = new LocationMap("#Locations-Map");
locationsFetch.then(RenderedMap.buildMarkers);
$(window).resize( $.throttle(500, RenderedMap.resizeMap ) );
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment