Skip to content

Instantly share code, notes, and snippets.

@bmershon
Last active September 18, 2016 17:57
Show Gist options
  • Save bmershon/82a8c2fd343b019becf6 to your computer and use it in GitHub Desktop.
Save bmershon/82a8c2fd343b019becf6 to your computer and use it in GitHub Desktop.
Nevada Wilderness
border: no

A map of Nevada's wilderness areas. See how the combined.json was built from government shapefiles to create this static graphic for web and print use.

You give the map instance a topojson object that looks like this:

objects: [
          nv: Object
          Basin: Object
                .
                .
                .
          wilderness: Object
         ]

Once you've (asynchronously) loaded a json file containing all the features you want (and maybe some you will ignore), you can call draw(data) on the map instance. The options object passed to the "atlas" constructor has a property layers.

layers is an array of layer definitions that each specify an object on the topojson object you want to render, what classes and ids to give that layer, as well as what interactions ("click", "mouseover") or lifecycle events ("enter()", "update()", "exit()") you would like to explicitly set for each layer of topojson features.

In this particular example, we have chosen to use a null projection, because the topojson object coming from combined.json already has baked-in screen coordinates for a 960px x 500px SVG.

'use strict';
(function() {
// d3.chart
(function(){"use strict";var d3=window.d3;var hasOwnProp=Object.hasOwnProperty;var d3cAssert=function(test,message){if(test){return}throw new Error("[d3.chart] "+message)};d3cAssert(d3,"d3.js is required");d3cAssert(typeof d3.version==="string"&&d3.version.match(/^3/),"d3.js version 3 is required");"use strict";var lifecycleRe=/^(enter|update|merge|exit)(:transition)?$/;var Layer=function(base){d3cAssert(base,"Layers must be initialized with a base.");this._base=base;this._handlers={}};Layer.prototype.dataBind=function(){d3cAssert(false,"Layers must specify a `dataBind` method.")};Layer.prototype.insert=function(){d3cAssert(false,"Layers must specify an `insert` method.")};Layer.prototype.on=function(eventName,handler,options){options=options||{};d3cAssert(lifecycleRe.test(eventName),"Unrecognized lifecycle event name specified to `Layer#on`: '"+eventName+"'.");if(!(eventName in this._handlers)){this._handlers[eventName]=[]}this._handlers[eventName].push({callback:handler,chart:options.chart||null});return this._base};Layer.prototype.off=function(eventName,handler){var handlers=this._handlers[eventName];var idx;d3cAssert(lifecycleRe.test(eventName),"Unrecognized lifecycle event name specified to `Layer#off`: '"+eventName+"'.");if(!handlers){return this._base}if(arguments.length===1){handlers.length=0;return this._base}for(idx=handlers.length-1;idx>-1;--idx){if(handlers[idx].callback===handler){handlers.splice(idx,1)}}return this._base};Layer.prototype.draw=function(data){var bound,entering,events,selection,handlers,eventName,idx,len;bound=this.dataBind.call(this._base,data);d3cAssert(bound&&bound.call===d3.selection.prototype.call,"Invalid selection defined by `Layer#dataBind` method.");d3cAssert(bound.enter,"Layer selection not properly bound.");entering=bound.enter();entering._chart=this._base._chart;events=[{name:"update",selection:bound},{name:"enter",selection:this.insert.bind(entering)},{name:"merge",selection:bound},{name:"exit",selection:bound.exit.bind(bound)}];for(var i=0,l=events.length;i<l;++i){eventName=events[i].name;selection=events[i].selection;if(typeof selection==="function"){selection=selection()}if(selection.empty()){continue}d3cAssert(selection&&selection.call===d3.selection.prototype.call,"Invalid selection defined for '"+eventName+"' lifecycle event.");handlers=this._handlers[eventName];if(handlers){for(idx=0,len=handlers.length;idx<len;++idx){selection._chart=handlers[idx].chart||this._base._chart;selection.call(handlers[idx].callback)}}handlers=this._handlers[eventName+":transition"];if(handlers&&handlers.length){selection=selection.transition();for(idx=0,len=handlers.length;idx<len;++idx){selection._chart=handlers[idx].chart||this._base._chart;selection.call(handlers[idx].callback)}}}};"use strict";d3.selection.prototype.layer=function(options){var layer=new Layer(this);var eventName;layer.dataBind=options.dataBind;layer.insert=options.insert;if("events"in options){for(eventName in options.events){layer.on(eventName,options.events[eventName])}}this.on=function(){return layer.on.apply(layer,arguments)};this.off=function(){return layer.off.apply(layer,arguments)};this.draw=function(){return layer.draw.apply(layer,arguments)};return this};"use strict";function extend(object){var argsIndex,argsLength,iteratee,key;if(!object){return object}argsLength=arguments.length;for(argsIndex=1;argsIndex<argsLength;argsIndex++){iteratee=arguments[argsIndex];if(iteratee){for(key in iteratee){object[key]=iteratee[key]}}}return object}var initCascade=function(instance,args){var ctor=this.constructor;var sup=ctor.__super__;if(sup){initCascade.call(sup,instance,args)}if(hasOwnProp.call(ctor.prototype,"initialize")){this.initialize.apply(instance,args)}};var transformCascade=function(instance,data){var ctor=this.constructor;var sup=ctor.__super__;if(this===instance&&hasOwnProp.call(this,"transform")){data=this.transform(data)}if(hasOwnProp.call(ctor.prototype,"transform")){data=ctor.prototype.transform.call(instance,data)}if(sup){data=transformCascade.call(sup,instance,data)}return data};var Chart=function(selection,chartOptions){this.base=selection;this._layers={};this._attached={};this._events={};if(chartOptions&&chartOptions.transform){this.transform=chartOptions.transform}initCascade.call(this,this,[chartOptions])};Chart.prototype.initialize=function(){};Chart.prototype.unlayer=function(name){var layer=this.layer(name);delete this._layers[name];delete layer._chart;return layer};Chart.prototype.layer=function(name,selection,options){var layer;if(arguments.length===1){return this._layers[name]}if(arguments.length===2){if(typeof selection.draw==="function"){selection._chart=this;this._layers[name]=selection;return this._layers[name]}else{d3cAssert(false,"When reattaching a layer, the second argument "+"must be a d3.chart layer")}}layer=selection.layer(options);this._layers[name]=layer;selection._chart=this;return layer};Chart.prototype.attach=function(attachmentName,chart){if(arguments.length===1){return this._attached[attachmentName]}this._attached[attachmentName]=chart;return chart};Chart.prototype.draw=function(data){var layerName,attachmentName,attachmentData;data=transformCascade.call(this,this,data);for(layerName in this._layers){this._layers[layerName].draw(data)}for(attachmentName in this._attached){if(this.demux){attachmentData=this.demux(attachmentName,data)}else{attachmentData=data}this._attached[attachmentName].draw(attachmentData)}return this};Chart.prototype.on=function(name,callback,context){var events=this._events[name]||(this._events[name]=[]);events.push({callback:callback,context:context||this,_chart:this});return this};Chart.prototype.once=function(name,callback,context){var self=this;var once=function(){self.off(name,once);callback.apply(this,arguments)};return this.on(name,once,context)};Chart.prototype.off=function(name,callback,context){var names,n,events,event,i,j;if(arguments.length===0){for(name in this._events){this._events[name].length=0}return this}if(arguments.length===1){events=this._events[name];if(events){events.length=0}return this}names=name?[name]:Object.keys(this._events);for(i=0;i<names.length;i++){n=names[i];events=this._events[n];j=events.length;while(j--){event=events[j];if(callback&&callback===event.callback||context&&context===event.context){events.splice(j,1)}}}return this};Chart.prototype.trigger=function(name){var args=Array.prototype.slice.call(arguments,1);var events=this._events[name];var i,ev;if(events!==undefined){for(i=0;i<events.length;i++){ev=events[i];ev.callback.apply(ev.context,args)}}return this};Chart.extend=function(name,protoProps,staticProps){var parent=this;var child;if(protoProps&&hasOwnProp.call(protoProps,"constructor")){child=protoProps.constructor}else{child=function(){return parent.apply(this,arguments)}}extend(child,parent,staticProps);var Surrogate=function(){this.constructor=child};Surrogate.prototype=parent.prototype;child.prototype=new Surrogate;if(protoProps){extend(child.prototype,protoProps)}child.__super__=parent.prototype;Chart[name]=child;return child};"use strict";d3.chart=function(name){if(arguments.length===0){return Chart}else if(arguments.length===1){return Chart[name]}return Chart.extend.apply(Chart,arguments)};d3.selection.prototype.chart=function(chartName,options){if(arguments.length===0){return this._chart}var ChartCtor=Chart[chartName];d3cAssert(ChartCtor,"No chart registered with name '"+chartName+"'");return new ChartCtor(this,options)};d3.selection.enter.prototype.chart=function(){return this._chart};d3.transition.prototype.chart=d3.selection.enter.prototype.chart})(this);
// d3-chart-atlas https://github.com/bmershon/d3-chart-atlas Copyright Brooks Mershon 2015
(function(global,factory){typeof exports==="object"&&typeof module!=="undefined"?factory():typeof define==="function"&&define.amd?define(factory):factory()})(this,function(){"use strict";var configuration={};function resetAffine(){chart.base.transition().duration(750).attr("transform","")}function zoomToFeature(d){var b=this._path.bounds(d),s=.9/Math.max((b[1][0]-b[0][0])/this._w,(b[1][1]-b[0][1])/this._h),t=[(this._w-s*(b[1][0]+b[0][0]))/2,(this._h-s*(b[1][1]+b[0][1]))/2];chart.base.transition().duration(750).attr("transform","translate("+t+")scale("+s+")")}function layer_exit(){var chart=this.chart();return this.remove()}function layer_merge(){var chart=this.chart();if(chart._projection){chart._projection.scale(chart._scale).rotate(chart._rotation).precision(chart._precision).translate(chart._translate)}chart._path.projection(chart._projection).pointRadius(chart._pointRadius);return this.attr("d",chart._path)}function chart_initialize(options){var chart=this;chart.base=chart.base.append("g").attr("class","base");chart.options=options||{};chart._w=chart.base.attr("width")||960;chart._h=chart.base.attr("height")||500;chart._projection=d3.geo.orthographic().clipAngle(90);chart._path=d3.geo.path();chart._graticule=d3.geo.graticule();chart._precision=Math.sqrt(2);chart._scale=1;chart._translate=[0,0];chart._rotation=[0,0,0];var layerGraticule=chart.base.append("g").attr("class","layer-graticule").append("path");var layerGraticule=chart.base.append("g").attr("class","layer-graticule").append("path");if(options.dispatch){chart.dispatch=options.dispatch;chart.dispatch.on("zoomToFeature",function(d,i){chart.zoomToFeature(d)});chart.dispatch.on("resetAffine",function(d,i){chart.resetAffine()})}chart.options.layers.forEach(function(layer){var layerBase=chart.base.append("g").attr("class","layer-base-"+layer.object);var layerConfig={dataBind:layer.databind||function(data){var chart=this.chart();var toBind=Array.isArray(data[layer.object])?data[layer.object]:[data[layer.object]];toBind=layer.filter?toBind.filter(layer.filter):toBind;return this.selectAll("."+layer.object).data(toBind)},insert:layer.insert||function(){var chart=this.chart();var selection=this.append("path").attr("class",layer.class||layer.object).classed(layer.object,true).attr("id",layer.id||function(d,i){return i});if(layer.interactions){for(var e in layer.interactions){if(layer.interactions.hasOwnProperty(e)){selection.on(e,layer.interactions[e])}}}return selection},events:layer.events||{merge:layer_merge,exit:layer_exit}};chart.layer("layer-"+layer.object,layerBase,layerConfig)});chart.zoomToFeature=zoomToFeature;chart.resetAffine=resetAffine;chart.on("change:projection",function(){if(this.data){if(chart._projection){chart._projection.scale(chart._scale).rotate(chart._rotation).precision(chart._precision).translate(chart._translate);layerGraticule.datum(chart._graticule).attr("d",chart._path).attr("class","graticule")}chart._path.projection(chart._projection).pointRadius(chart._pointRadius);this.draw(this.data)}})}configuration.initialize=chart_initialize;function chart_transform(data){var chart=this;if(!(data.type=="Topology"))return data;var t={};this.options.layers.forEach(function(layer){if(data.objects[layer.object].type=="GeometryCollection"){t[layer.object]=topojson.feature(data,data.objects[layer.object]).features}if(data.objects[layer.object].type=="MultiPolygon"){t[layer.object]=topojson.feature(data,data.objects[layer.object])}});this.topology=data;this.data=t;chart.trigger("change:projection");return t}configuration.transform=chart_transform;function center(_){if(arguments.length===0){return this._scale}if(_)this._center=_;this.trigger("change:projection");return this}configuration.center=center;function graticule(_){if(arguments.length===0){return this._graticule}this._graticule=_;this.trigger("change:projection");return this}configuration.graticule=graticule;function height(_){if(arguments.length===0){return this._h}this._h=_;this.trigger("change:projection");return this}configuration.height=height;function path(_){if(arguments.length===0){return this._path}if(_)this._path=_;this.trigger("change:projection");return this}configuration.path=path;function projection(_){if(arguments.length===0){return this._projection}this._projection=_;this.trigger("change:projection");return this}configuration.projection=projection;function rotate(_){if(arguments.length===0){return this._rotation}if(_)this._rotation=_;this.trigger("change:projection");return this}configuration.rotate=rotate;function scale(_){if(arguments.length===0){return this._scale}if(_)this._center=_;this.trigger("change:projection");return this}configuration.scale=scale;function translate(_){if(arguments.length===0){return this._translate}if(_)this._translate=_;this.trigger("change:projection");return this}configuration.translate=translate;function precision(_){if(arguments.length===0){return this._precision}if(_)this._precision=_;this.trigger("change:projection");return this}configuration.precision=precision;function pointRadius(_){if(arguments.length===0){return this._pointRadius}if(_)this._pointRadius=_;this.trigger("change:projection");return this}configuration.pointRadius=pointRadius;function width(_){if(arguments.length===0){return this._w}this._w=_;this.trigger("change:projection");return this}configuration.width=width;function zoomToLayer(_,_filter){if(arguments.length===0){return this._projection}var f=typeof _filter==="undefined"?function(d){return true}:_filter;var chart=this;if(this.data){var layerObject=_;var collection={type:"FeatureCollection",features:this.data[layerObject].filter(f)};var b=collection.features.length==0?[[-1,-1],[1,1]]:chart._path.bounds(collection),s=.9/Math.max((b[1][0]-b[0][0])/chart._w,(b[1][1]-b[0][1])/chart._h),t=[(chart._w-s*(b[1][0]+b[0][0]))/2,(chart._h-s*(b[1][1]+b[0][1]))/2];chart._scale=s;chart._translate=t}chart.trigger("change:projection");return this}configuration.zoomToLayer=zoomToLayer;function rotateToLayer(_,_filter){if(arguments.length===0){return this._projection}var f=typeof _filter==="undefined"?function(d){return true}:_filter;var chart=this;if(this.data){var layerObject=_;var collection={type:"FeatureCollection",features:this.data[layerObject]};var filteredCollection={type:"FeatureCollection",features:this.data[layerObject].filter(f)};var b=[[-1,-1],[1,1]],c=d3.geo.centroid(filteredCollection),s=.9/Math.max((b[1][0]-b[0][0])/chart._w,(b[1][1]-b[0][1])/chart._h),t=[(chart._w-s*(b[1][0]+b[0][0]))/2,(chart._h-s*(b[1][1]+b[0][1]))/2];chart._scale=s;chart._translate=t;c=[-c[0],-c[1]];chart._rotation=c}chart.trigger("change:projection");return this}configuration.rotateToLayer=rotateToLayer;d3.chart("atlas",configuration)});
var width = 960,
height = 500,
data;
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
var options = {};
options.layers = [];
options.layers.push({
class: "state",
id: function(d, i) {return i},
object: "nv" // identify the topology object for the layer
});
options.layers.push({
class: "basin",
id: "GreatBasin",
object: "basin", // identify the topology object for the layer
interactions: {
"mouseenter": function(d, i) {
d3.select(this).classed("highlight", true);
},
"mouseleave": function(d, i) {
d3.select(this).classed("highlight", false);
}
}
});
options.layers.push({
class: "wilderness",
id: function(d, i) {return "wilderness-area-" + i},
object: "wilderness" // identify the topology object for the layer
});
var m = svg.chart("atlas", options)
.projection(null); // use baked-in screen coordinates
d3.queue(2)
.defer(d3.json, "combined.json")
.await(ready);
function ready(error, topology) {
data = topology;
m.draw(data);
}
})();
Display the source blob
Display the rendered blob
Raw
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.graticule {
stroke: #aaa;
fill :none;
}
.state {
fill: #bfc8af;
stroke-width: .5px;
fill-opacity: .5;
stroke: #fff;
}
.wilderness {
fill: #317701;
stroke-width: .5px;
fill-opacity: .5;
stroke: none;
}
#GreatBasin {
fill: #db8622;
stroke-width: .5px;
fill-opacity: 1;
stroke: #000;
}
#GreatBasin.highlight {
fill: #e8480d;
stroke-width: .5px;
fill-opacity: 1;
stroke: #000;
}
</style>
<body>
</body>
<script src="http://d3js.org/queue.v1.min.js"></script>
<script src="https://d3js.org/d3.v3.js"></script>
<script src="https://d3js.org/d3-queue.v3.min.js"></script>
<script src="http://d3js.org/d3.geo.projection.v0.min.js" charset="utf-8"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script>
<script src="app.js"></script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment