Skip to content

Instantly share code, notes, and snippets.

@milkbread
Last active December 19, 2015 11:38
Show Gist options
  • Save milkbread/5948753 to your computer and use it in GitHub Desktop.
Save milkbread/5948753 to your computer and use it in GitHub Desktop.
HTML: D3: TopoJSON: Demonstrating ArcGeometries II - show arcs on map AND sorted ... depending on length

Demonstrating ArcGeometries

This is a demonstration on how to handle ArcGeometries, exported from a TopoJSON dataset.

Demonstration steps:

  • 1.) The ARCS

    • 1.1 get all TopoJSON-arcs as single LineStrings
    • 1.2 manipulate them, by:
      • linear simplification using 'simplify.js' (here: tolerance=0.01, maxQuality=false)
      • semantic filtering (here: minimum length) using 'crossfilter.js'
  • 2.) Show the ARCS in map

    • 2.1 manipulate them, by:
      • linear simplification using 'simplify.js' (here: tolerance=0.01, maxQuality=false)
      • semantic filtering (here: minimum length) using 'crossfilter.js'
    • 2.2 show arcs sorted, depending on minimal length
    • 2.3 click arc and zoom on it at the map
<!DOCTYPE html>
<html>
<head>
<title>Demonstration fo ArcGeometries 2</title>
<meta charset="utf-8" />
<script src="http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="http://mourner.github.io/simplify-js/simplify.js"></script>
<script src="http://bl.ocks.org/milkbread/raw/5779939/RKMapping_0.4.4.js"></script> <!--http://bl.ocks.org/milkbread/raw/5779939-->
<script src="http://bl.ocks.org/milkbread/raw/5829814/RKAggregation_1.0.js"></script> <!--http://bl.ocks.org/milkbread/raw/5829814 this function needs RKMapping.js additionally-->
<script src="http://bl.ocks.org/milkbread/raw/5917907/slider.js"></script>
<script src="http://bl.ocks.org/milkbread/raw/5947143/crossfilter.min.js"></script>
<style>
@import url(http://cdn.leafletjs.com/leaflet-0.6.1/leaflet.css);
@import url(http://bl.ocks.org/milkbread/raw/5743667/RKToolbox_0.1.css);
.axis text {
font-family: sans-serif;
font-size: 16px;
text-anchor:left;
fill:#0f0;
opacity:0.8;
}
#arcs{
overflow-x: auto;
overflow-y: auto;
padding-bottom: 15px;
max-width: 100%;
}
#info {
background-color:#c8ba09;
}
#overlay{
fill:None;
stroke-width:1.5px;
}
</style>
</head>
<body>
<div id="slider"style="width: 960px; height: 80px"></div>
<div id="info"style="width: 960px;"></div>
<div id="map" style="width: 960px; height: 300px"></div>
<div id="arcs" style="width: 960px; height: 80px; background-color:#c8ba09"></div>
<script>
var strokeColors = ['#b30032','#070']
//Sorted arcs
var svg = d3.select("#arcs").append("svg")
.attr("height",60);
var svgGroup = svg.append('g');
var path = d3.geo.path().projection(normalize_projection);
var arcBounds, sWidth = 50, sOffset = 10;
var scaleLat = d3.scale.linear();
var scaleLng = d3.scale.linear();
//Slider
var slider = new slider(d3.select("#slider"),960,75);
slider.reDefineIndicator(6);
//Leaflet Map
var map = L.map('map').setView([51, 11], 5);
var toolserver = L.tileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png');
var stamen = L.tileLayer('http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png', {attribution: 'Add some attributes here!'}).addTo(map);
var baseLayers = {"stamen": stamen, "toolserver-mapnik":toolserver};
var control = L.control.layers(baseLayers).addTo(map);
//Map overlay
var svgContainer= d3.select(map.getPanes().overlayPane).append("svg");
var group= svgContainer.append("g").attr("class", "leaflet-zoom-hide");
var path2 = d3.geo.path().projection(project);
var selMaxVal = -1, counter;
//show the user some infos
var countInfo = d3.select('#info').append('text').text('Number of Arcs: ')
//1. Open data
d3.json("vg250_states_topo.json", function(error, topology) {
//define a click-action-listener of the slider
slider.addHoverListener(reset0);
slider.addClickListener(reset);
//set the featureStorage and the featureObjects
var pure_features = topology.objects.vg250_bld;
var featureStorage = new featureObjStorage();
pure_features.geometries.forEach(function(d){ featureStorage.addFeatObj(d); })
var featuresObjects = featureStorage.featureObjects;
//define bounds of overlay
var overlayBounds = d3.geo.bounds(topojson.feature(topology, pure_features));
//2. get the ARCS from the featureObjects
var arcCollection = getArcs(topology, featuresObjects);
//we need an array of objects not an object, so loop all arcs ... simplify them AND push them to an array
var arcCollectionArray_ = [], maxLength = 0;
for (arc in arcCollection){
var cache = arcCollection[arc];
//simplify LineStrings of the arcs
cache.coordinates = pointsConversion(simplify(pointsConversion(cache.coordinates, 'toObject'),0.01,false), 'toArray')
arcCollectionArray_.push(cache)
//directly get the maximum value (needed for the slider)
if(maxLength<cache.length)maxLength=cache.length;
}
//now we can define the scalebar of the 'slider', because we know the maximum length
slider.addScalebar([0,maxLength+100000]);
//set up a new 'Crossfilter' and get the length-dimension
var arcCollectionCF = crossfilter(arcCollectionArray_);
var lengthDimension = arcCollectionCF.dimension(function(d){return d.length;})
map.on("viewreset", reset);
reset();
function reset0(){
//1st time: set to O, else set to slider-value
if(selMaxVal!=-1)selMaxVal = slider.getIndicPosition();
else selMaxVal = 0;
//Filtering
lengthDimension.filterAll(); //reset filter
var origCount = arcCollectionCF.groupAll().reduceCount().value(); //count elements initially
lengthDimension.filter(function(d){return (d < selMaxVal)}) //filter the dataset in relation to the slider-value
counter = arcCollectionCF.groupAll().reduceCount().value(); //count elements after filtering
countInfo.text('Number of Arcs: ' + counter +" (Max: "+ origCount +" | Sorting: DESC)") //give infos to user
}
function reset() {
var bottomLeft = project(overlayBounds[0]),
topRight = project(overlayBounds[1]);
svgContainer.attr("width", topRight[0] - bottomLeft[0])
.attr("height", bottomLeft[1] - topRight[1])
.style("margin-left", bottomLeft[0] + "px")
.style("margin-top", topRight[1] + "px");
group.attr("transform", "translate(" + -bottomLeft[0] + "," + -topRight[1] + ")");
//get all filtered elements of the dataset
var arcCollectionArray = lengthDimension.top(counter);
//1. JOIN
var feature = group.selectAll("path").data(arcCollectionArray, function(d){return d.coordinates})
//2. UPDATE
feature.attr("d", setPath).transition().duration(2000)
.attr("stroke",strokeColors[1]).attr('opacity',1);
//3. ENTER
feature.enter()
.append("path")
.attr("id","overlay")
.attr('stroke',strokeColors[0])
.attr("transform","translate(0,0) scale(-2,-2)")
.attr("d", setPath)
.transition().duration(2000)
.attr("transform","translate(0,0) scale(1,1)");
//5. EXIT
feature.exit()
.attr('stroke',strokeColors[0])
.transition().duration(2000)
.attr("transform","translate(-500,0) scale(-1,1)")
.remove();
function setPath(d){return path2({type: 'LineString', coordinates: d.coordinates})}
showSortedArcs(arcCollectionArray);
}
function showSortedArcs(arcCollectionArray__){
//re-define the size of the svg-container ... depending on number of data-elements
svg.attr("width", sWidth*counter)
//1. JOIN ... bind data to the corresponding svg-elements
var arcVis = svgGroup.selectAll('path').data(arcCollectionArray__, function(d) { return d.coordinates; });
//2. UPDATE
arcVis.transition().duration(2000)
.attr('stroke',strokeColors[1])
.attr("transform",function(d,i){return "translate("+(i+1)*sWidth+",0) scale(-1,1)"});
//3. ENTER
arcVis.enter().append("path")
.on('mousedown',goToArc)
.attr("d",setPath) //see function...
.attr('fill','None')
.attr('stroke',strokeColors[0])
.attr('stroke-width',2)
.attr("transform",function(d,i){return "translate(0,0) scale(-1,1)"})
.transition().duration(2000)
.attr("transform",function(d,i){return "translate("+(i+1)*sWidth+",0) scale(-1,1)"});
//5. EXIT
arcVis.exit()
.attr('stroke',strokeColors[0])
.transition().duration(2000)
.attr("transform",function(d,i){return "translate(0,0) scale(-1,1)"})
.remove();
function setPath(d,i){
var geometry = d;
arcBounds=d3.geo.bounds(d); //save arcBounds globally
var new_path = path({type: 'LineString', coordinates: geometry.coordinates})
return new_path;
}
function goToArc(d){
var local_bounds = d3.geo.bounds(d),
center = new L.LatLng(local_bounds[0][1]+((local_bounds[1][1]-local_bounds[0][1])/2),local_bounds[0][0]+((local_bounds[1][0]-local_bounds[0][0])/2)),
southWest = new L.LatLng(local_bounds[0][1], local_bounds[0][0]),
northEast = new L.LatLng(local_bounds[1][1], local_bounds[1][0]);
local_bounds = new L.LatLngBounds(southWest, northEast);
map.setView( center, map.getBoundsZoom( local_bounds));
countInfo.text('This Arcs has a length of: ' + d.length + ' and has '+ d.members.length + ' members!')
}
}
});
function normalize_projection(x) {
scaleLat.domain([arcBounds[0][0],arcBounds[1][0]])
.range([sWidth+sOffset,sOffset]);
scaleLng.domain([arcBounds[0][1],arcBounds[1][1]])
.range([sWidth+sOffset,sOffset]);
return [scaleLat(x[0]), scaleLng(x[1])]
}
function pointsConversion(points, direction){
var cache;
if(direction=='toObject'){
cache = points.map(function(point){return {'y':point[0],'x':point[1]}});
}
else if(direction=='toArray'){
cache = points.map(function(point){return [point.y,point.x]});
}
return cache;
}
function project(point) {
var latlng = new L.LatLng(point[1], point[0]);
var layerPoint = map.latLngToLayerPoint(latlng);
return [layerPoint.x, layerPoint.y];
}
function pointsConversion(points, direction){
var cache;
if(direction=='toObject'){
cache = points.map(function(point){return {'y':point[0],'x':point[1]}});
}
else if(direction=='toArray'){
cache = points.map(function(point){return [point.y,point.x]});
}
return cache;
}
</script>
</body>
</html>
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment