Skip to content

Instantly share code, notes, and snippets.

@martgnz
Created April 30, 2016 15:48
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 martgnz/151839a274c8658ce2fa8f975ccdaf05 to your computer and use it in GitHub Desktop.
Save martgnz/151839a274c8658ce2fa8f975ccdaf05 to your computer and use it in GitHub Desktop.
var StaticCanvasMap;var ZoomableCanvasMap;!function(){"use strict";function inside(pt,polygon){var polys=polygon.geometry.coordinates;if(polygon.geometry.type==="Polygon")polys=[polys];var insidePoly=false;var i=0;while(i<polys.length&&!insidePoly){if(inRing(pt,polys[i][0])){var inHole=false;var k=1;while(k<polys[i].length&&!inHole){if(inRing(pt,polys[i][k])){inHole=true}k++}if(!inHole)insidePoly=true}i++}return insidePoly}function inRing(pt,ring){var isInside=false;for(var i=0,j=ring.length-1;i<ring.length;j=i++){var xi=ring[i][0],yi=ring[i][1];var xj=ring[j][0],yj=ring[j][1];var intersect=yi>pt[1]!==yj>pt[1]&&pt[0]<(xj-xi)*(pt[1]-yi)/(yj-yi)+xi;if(intersect)isInside=!isInside}return isInside}function maxBounds(one,two){var bounds=two;if(one[0][0]<two[0][0])bounds[0][0]=one[0][0];if(one[0][1]<two[0][1])bounds[0][1]=one[0][1];if(one[1][0]>two[1][0])bounds[1][0]=one[1][0];if(one[1][1]>two[1][1])bounds[1][1]=one[1][1];return bounds}function createRTree(element,dataPath){element.lookupTree=rbush(4);var elements=[];for(var j in element.features.features){var bounds=dataPath.bounds(element.features.features[j]);elements.push([bounds[0][0].toFixed(0),bounds[0][1].toFixed(0),Math.ceil(bounds[1][0]),Math.ceil(bounds[1][1]),element.features.features[j]])}element.lookupTree.load(elements)}function paintFeature(element,feature,parameters){parameters.context.beginPath();parameters.path(feature);element.static.paintfeature(parameters,feature)}function paintBackgroundElement(element,parameters){if(!element.static)return;if(element.static.prepaint)element.static.prepaint(parameters);if(element.static.paintfeature){var lookup=element.lookupTree.search([parameters.translate[0],parameters.translate[1],parameters.width/parameters.scale-parameters.translate[0],parameters.height/parameters.scale-parameters.translate[1]]);for(var j in lookup){paintFeature(element,lookup[j][4],parameters)}}if(element.static.postpaint)element.static.postpaint(parameters)}function PartialPainter(data,parameters){var index=0,j=0,element=null,currentLookup=[];this.hasNext=function(){return index<=data.length&&j<currentLookup.length};this.renderNext=function(){if(index>=data.length&&j>=currentLookup.length)return;var start=performance.now();if(!element||j>=currentLookup.length){while(index<data.length&&!data[index].static){index++}if(index>=data.length)return;element=data[index];if(element.static.prepaint)element.static.prepaint(parameters);currentLookup=element.lookupTree.search([-parameters.translate[0],-parameters.translate[1],parameters.width/parameters.scale-parameters.translate[0],parameters.height/parameters.scale-parameters.translate[1]]);j=0;++index}if(element.static.paintfeature){for(;j!=currentLookup.length;++j){var feature=currentLookup[j][4];paintFeature(element,feature,parameters);if(performance.now()-start>10)break}}else{j=currentLookup.length}if(j==currentLookup.length&&element.static.postpaint){element.static.postpaint(parameters)}};this.finish=function(){if(index>=data.length&&j>=currentLookup.length)return;if(j<currentLookup.length)index--;for(;index!=data.length;++index){if(j>=currentLookup.length){while(!data[index].static&&index<data.length){index++}if(index>=data.length)return;element=data[index];if(element.static.prepaint)element.static.prepaint(parameters);currentLookup=element.lookupTree.search([-parameters.translate[0],-parameters.translate[1],parameters.width/parameters.scale-parameters.translate[0],parameters.height/parameters.scale-parameters.translate[1]]);j=0}if(element.static.paintfeature){for(;j!=currentLookup.length;++j){var feature=currentLookup[j][4];paintFeature(element,feature,parameters)}}if(element.static.postpaint)element.static.postpaint(parameters)}}}function translatePoint(point,scale,translate){return[point[0]/scale-translate[0],point[1]/scale-translate[1]]}function extend(extension,obj){var newObj={};for(var elem in obj){newObj[elem]=obj[elem]}for(var elem in extension){if(!newObj.hasOwnProperty(elem))newObj[elem]=extension[elem]}return newObj}function CanvasMap(parameters){var settings=extend({width:d3.select(parameters.element).node().getBoundingClientRect().width,ratio:1,area:0,scale:1,translate:[0,0],background:null,backgroundScale:1,backgroundTranslate:[0,0],map:this},parameters),simplify=d3.geo.transform({point:function(x,y,z){if(!z||z>=settings.area){this.stream.point(x,y)}}}),canvas=null,context=null;if(!parameters.projection){var b=[[Infinity,Infinity],[-Infinity,-Infinity]];for(var i in settings.data){b=maxBounds(b,d3.geo.bounds(settings.data[i].features))}settings.projection=d3.geo.mercator().scale(1).center([(b[1][0]+b[0][0])/2,(b[1][1]+b[0][1])/2])}var dataPath=d3.geo.path().projection({stream:function(s){return simplify.stream(settings.projection.stream(s))}});var b=[[Infinity,Infinity],[-Infinity,-Infinity]];for(var i in settings.data){b=maxBounds(b,dataPath.bounds(settings.data[i].features))}var dx=b[1][0]-b[0][0],dy=b[1][1]-b[0][1];if(!parameters.projection){settings.height=settings.height||Math.ceil(dy*settings.width/dx);settings.projection.scale(.9*(settings.width/dx)).translate([settings.width/2,settings.height/2])}else if(!settings.height){settings.height=Math.ceil(dy*1/.9)}d3.select(settings.parameters).attr("height",settings.height);function init(){canvas=d3.select(settings.element).append("canvas");context=canvas.node().getContext("2d");var devicePixelRatio=window.devicePixelRatio||1,backingStoreRatio=context.webkitBackingStorePixelRatio||context.mozBackingStorePixelRatio||context.msBackingStorePixelRatio||context.oBackingStorePixelRatio||context.backingStorePixelRatio||1;settings.ratio=devicePixelRatio/backingStoreRatio;settings.area=1/settings.projection.scale()/settings.ratio/20;canvas.attr("width",settings.width*settings.ratio);canvas.attr("height",settings.height*settings.ratio);canvas.style("width",settings.width+"px");canvas.style("height",settings.height+"px");context.lineJoin="round";context.lineCap="round";dataPath.context(context);context.clearRect(0,0,settings.width*settings.ratio,settings.height*settings.ratio);context.save();context.scale(settings.ratio,settings.ratio);for(var i in settings.data){createRTree(settings.data[i],dataPath)}settings.background=new Image;settings.backgroundScale=settings.scale;settings.backgroundTranslate=settings.translate;var parameters={path:dataPath,context:context,scale:settings.scale,translate:settings.translate,width:settings.width,height:settings.height,map:settings.map};var callback=function(){for(var i in settings.data){var element=settings.data[i];if(element.dynamic&&element.dynamic.postpaint)element.dynamic.postpaint(parameters,null)}context.restore();canvas.on("click",click).on("mousemove",hover).on("mouseleave",hoverLeave)};for(var i in settings.data){var element=settings.data[i];if(element.dynamic&&element.dynamic.prepaint)element.dynamic.prepaint(parameters,element.hoverElement)}for(var i in settings.data){var element=settings.data[i];paintBackgroundElement(element,parameters)}settings.background.onload=callback;settings.background.src=canvas.node().toDataURL();this.init=function(){}}function paint(){context.save();context.scale(settings.scale*settings.ratio,settings.scale*settings.ratio);context.translate(settings.translate[0],settings.translate[1]);context.clearRect(-settings.translate[0],-settings.translate[1],settings.width*settings.ratio,settings.height*settings.ratio);context.rect(-settings.translate[0],-settings.translate[1],settings.width/settings.scale,settings.height/settings.scale);context.clip();var parameters={path:dataPath,context:dataPath.context(),scale:settings.scale,translate:settings.translate,width:settings.width,height:settings.height,map:settings.map};settings.area=1/settings.projection.scale()/settings.scale/settings.ratio/20;for(var i in settings.data){var element=settings.data[i];if(element.dynamic&&element.dynamic.prepaint)element.dynamic.prepaint(parameters,element.hoverElement)}context.drawImage(settings.background,0,0,settings.width*settings.ratio,settings.height*settings.ratio,-settings.backgroundTranslate[0],-settings.backgroundTranslate[1],settings.width/settings.backgroundScale,settings.height/settings.backgroundScale);for(var i in settings.data){var element=settings.data[i];if(element.dynamic&&element.dynamic.postpaint)element.dynamic.postpaint(parameters,element.hoverElement)}context.restore()}function click(){var point=translatePoint(d3.mouse(this),settings.scale,settings.translate);var parameters={path:dataPath,context:context,scale:settings.scale,translate:settings.translate,width:settings.width,height:settings.height,map:settings.map};for(var i in settings.data){var element=settings.data[i];if(!element.click)continue;var lookup=element.lookupTree.search([point[0],point[1],point[0],point[1]]);var isInside=false;for(var j in lookup){var feature=lookup[j][4];if(inside(settings.projection.invert(point),feature)){element.click(parameters,feature);isInside=true}}isInside||element.click(parameters,null)}}function hoverLeave(){for(var i in settings.data){var element=settings.data[i];element.hoverElement=false}paint()}function hover(){var point=translatePoint(d3.mouse(this),settings.scale,settings.translate),repaint=false;for(var i in settings.data){var element=settings.data[i];if(element.hoverElement&&inside(settings.projection.invert(point),element.hoverElement)){continue}element.hoverElement=false;var lookup=element.lookupTree.search([point[0],point[1],point[0],point[1]]);for(var j in lookup){var feature=lookup[j][4];if(inside(settings.projection.invert(point),feature)){element.hoverElement=feature;break}}repaint=true}repaint&&paint()}this.init=init;this.paint=paint;this.settings=function(){return settings}}StaticCanvasMap=function(parameters){var map=new CanvasMap(parameters);this.init=function(){map.init()};this.paint=function(){map.paint()}};var epsilon=.5;function nearEqual(a,b){return Math.abs(a-b)<epsilon}function ImageCache(parameters){var cache=[],settings=parameters;this.addImage=function(parameters){cache.push(parameters)};this.getImage=function(parameters){for(var i in cache){var element=cache[i];if(nearEqual(element.scale,parameters.scale)&&nearEqual(element.translate[0],parameters.translate[0])&&nearEqual(element.translate[1],parameters.translate[1]))return element}return null};this.getFittingImage=function(bbox){var currentImage=cache.length>0?cache[0]:null;for(var i in cache){var image=cache[i];var imageBB=[-image.translate[0],-image.translate[1],settings.width/image.scale-image.translate[0],settings.height/image.scale-image.translate[1]];if(imageBB[0]<=bbox[0]&&imageBB[1]<=bbox[1]&&imageBB[2]>=bbox[2]&&imageBB[3]>=bbox[3]&&(!currentImage||currentImage.scale<image.scale)){currentImage=image}}return currentImage}}ZoomableCanvasMap=function(parameters){var map=new CanvasMap(parameters),simplify=d3.geo.transform({point:function(x,y,z){if(z>=area)this.stream.point(x,y)}}),area=0,canvas=null,context=null,settings=map.settings(),dataPath=d3.geo.path().projection({stream:function(s){return simplify.stream(settings.projection.stream(s))}}),imageCache=new ImageCache({width:settings.width,height:settings.height});settings.map=this;settings.zoomScale=settings.zoomScale||.5;this.init=function(){map.init();canvas=d3.select(settings.element).append("canvas");context=canvas.node().getContext("2d");area=1/settings.projection.scale()/settings.ratio/20;canvas.attr("width",settings.width*settings.ratio);canvas.attr("height",settings.height*settings.ratio);canvas.style("width",settings.width+"px");canvas.style("height",settings.height+"px");canvas.style("display","none");context.lineJoin="round";context.lineCap="round";dataPath.context(context);imageCache.addImage({image:settings.background,scale:settings.scale,translate:settings.translate})};this.paint=function(){map.paint()};function scaleZoom(scale,translate){if(nearEqual(scale,settings.scale)&&nearEqual(translate[0],settings.translate[0])&&nearEqual(translate[1],settings.translate[1])){scale=1;translate=[0,0]}if(scale==1&&settings.scale==1&&!translate[0]&&!translate[1]&&!settings.translate[0]&&!settings.translate[1]){return}area=1/settings.projection.scale()/scale/settings.ratio/20;context.save();context.scale(scale*settings.ratio,scale*settings.ratio);context.translate(translate[0],translate[1]);context.clearRect(-translate[0],-translate[1],settings.width*settings.ratio,settings.height*settings.ratio);var parameters={path:dataPath,context:context,scale:scale,translate:translate,width:settings.width,height:settings.height,map:settings.map};var image=imageCache.getImage({scale:scale,translate:translate});if(!image){var background=new Image,partialPainter=new PartialPainter(settings.data,parameters)}var translatedOne=translatePoint([settings.width,settings.height],scale,translate),translatedTwo=translatePoint([settings.width,settings.height],settings.scale,settings.translate);var bbox=[Math.min(-translate[0],-settings.translate[0]),Math.min(-translate[1],-settings.translate[1]),Math.max(translatedOne[0],translatedTwo[0]),Math.max(translatedOne[1],translatedTwo[1])];var zoomImage=imageCache.getFittingImage(bbox);if(zoomImage){settings.background=zoomImage.image;settings.backgroundScale=zoomImage.scale;settings.backgroundTranslate=zoomImage.translate}d3.transition().duration(300).ease("linear").tween("zoom",function(){var i=d3.interpolateNumber(settings.scale,scale),oldTranslate=settings.translate,oldScale=settings.scale;return function(t){settings.scale=i(t);var newTranslate=[oldTranslate[0]+(translate[0]-oldTranslate[0])/(scale-oldScale)*(i(t)-oldScale)*scale/i(t),oldTranslate[1]+(translate[1]-oldTranslate[1])/(scale-oldScale)*(i(t)-oldScale)*scale/i(t)];settings.translate=newTranslate;map.paint();!image&&partialPainter.renderNext()}}).each("end",function(){settings.scale=scale;settings.translate=translate;if(image){context.restore();settings.background=image.image;settings.backgroundScale=image.scale;settings.backgroundTranslate=image.translate;map.paint()}else{partialPainter.finish();background.onload=function(){context.restore();imageCache.addImage({image:background,scale:scale,translate:translate});settings.background=background;settings.backgroundScale=scale;settings.backgroundTranslate=translate;map.paint()};background.src=canvas.node().toDataURL()}})}this.zoom=function(d){if(!d){scaleZoom.call(this,1,[0,0]);return}var bounds=dataPath.bounds(d),dx=bounds[1][0]-bounds[0][0],dy=bounds[1][1]-bounds[0][1],bx=(bounds[0][0]+bounds[1][0])/2,by=(bounds[0][1]+bounds[1][1])/2,scale=settings.zoomScale*Math.min(settings.width/dx,settings.height/dy),translate=[-bx+settings.width/scale/2,-by+settings.height/scale/2];scaleZoom.call(this,scale,translate)}}}();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment